From af0cd1c8405e0470d88927debc3a0dfd33fa920b Mon Sep 17 00:00:00 2001 From: Luc SORIGNET Date: Mon, 18 Nov 2024 10:02:58 +0100 Subject: [PATCH] =?UTF-8?q?chore:=20Initial=20Commit=20feat:=20Gestion=20d?= =?UTF-8?q?es=20inscriptions=20[#1]=20feat(frontend):=20Cr=C3=A9ation=20de?= =?UTF-8?q?s=20vues=20pour=20le=20param=C3=A9trage=20de=20l'=C3=A9cole=20[?= =?UTF-8?q?#2]=20feat:=20Gestion=20du=20login=20[#6]=20fix:=20Correction?= =?UTF-8?q?=20lors=20de=20la=20migration=20des=20mod=C3=A8le=20[#8]=20feat?= =?UTF-8?q?:=20R=C3=A9vision=20du=20menu=20principal=20[#9]=20feat:=20Ajou?= =?UTF-8?q?t=20d'un=20footer=20[#10]=20feat:=20Cr=C3=A9ation=20des=20docke?= =?UTF-8?q?rs=20compose=20pour=20les=20environnements=20de=20d=C3=A9velopp?= =?UTF-8?q?ement=20et=20de=20production=20[#12]=20doc(ci):=20Mise=20en=20p?= =?UTF-8?q?lace=20de=20Husky=20et=20d'un=20suivi=20de=20version=20automati?= =?UTF-8?q?que=20[#14]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 9 + .husky/commit-msg | 1 + .husky/pre-commit | 0 .husky/prepare-commit-msg | 4 + .versionrc | 27 + Back-End/Dockerfile | 20 + Back-End/GestionEnseignants/__init__.py | 1 + Back-End/GestionEnseignants/admin.py | 3 + Back-End/GestionEnseignants/apps.py | 6 + Back-End/GestionEnseignants/models.py | 32 + Back-End/GestionEnseignants/serializers.py | 83 + Back-End/GestionEnseignants/tests.py | 3 + Back-End/GestionEnseignants/urls.py | 17 + Back-End/GestionEnseignants/views.py | 180 + .../Configuration/application.default.json | 4 + .../Configuration/automate.json | 63 + .../Configuration/inscriptions.json | 18 + Back-End/GestionInscriptions/__init__.py | 1 + Back-End/GestionInscriptions/admin.py | 11 + Back-End/GestionInscriptions/apps.py | 10 + Back-End/GestionInscriptions/automate.py | 45 + Back-End/GestionInscriptions/mailManager.py | 74 + Back-End/GestionInscriptions/models.py | 123 + Back-End/GestionInscriptions/pagination.py | 20 + Back-End/GestionInscriptions/serializers.py | 176 + Back-End/GestionInscriptions/signals.py | 43 + Back-End/GestionInscriptions/tasks.py | 44 + .../ajouterFicheEleve.html | 52 + .../GestionInscriptions/configure.html | 16 + .../GestionInscriptions/creationDossier.html | 162 + .../GestionInscriptions/editStudent.html | 42 + .../templates/GestionInscriptions/index.html | 126 + .../GestionInscriptions/templates/base.html | 31 + .../templates/pdfs/dossier_inscription.html | 97 + .../templatetags/myTemplateTag.py | 23 + Back-End/GestionInscriptions/urls.py | 31 + Back-End/GestionInscriptions/util.py | 181 + Back-End/GestionInscriptions/views.py | 289 + Back-End/GestionLogin/__init__.py | 0 Back-End/GestionLogin/admin.py | 3 + Back-End/GestionLogin/apps.py | 7 + Back-End/GestionLogin/backends.py | 20 + Back-End/GestionLogin/models.py | 25 + Back-End/GestionLogin/serializers.py | 28 + .../templates/GestionLogin/login.html | 61 + .../templates/GestionLogin/new-password.html | 64 + .../GestionLogin/reset-password.html | 37 + .../templates/GestionLogin/subscribe.html | 64 + Back-End/GestionLogin/urls.py | 22 + Back-End/GestionLogin/validator.py | 120 + Back-End/GestionLogin/views.py | 264 + Back-End/GestionMessagerie/__init__.py | 1 + Back-End/GestionMessagerie/admin.py | 3 + Back-End/GestionMessagerie/apps.py | 5 + Back-End/GestionMessagerie/models.py | 15 + Back-End/GestionMessagerie/serializers.py | 16 + Back-End/GestionMessagerie/urls.py | 9 + Back-End/GestionMessagerie/views.py | 32 + Back-End/GestionNotification/__init__.py | 1 + Back-End/GestionNotification/admin.py | 3 + Back-End/GestionNotification/apps.py | 9 + Back-End/GestionNotification/models.py | 28 + Back-End/GestionNotification/signals.py | 23 + Back-End/GestionNotification/urls.py | 7 + Back-End/GestionNotification/views.py | 15 + Back-End/N3wtSchool/__init__.py | 5 + Back-End/N3wtSchool/apps.py | 8 + Back-End/N3wtSchool/asgi.py | 16 + Back-End/N3wtSchool/bdd.py | 86 + Back-End/N3wtSchool/celery.py | 20 + Back-End/N3wtSchool/error.py | 31 + Back-End/N3wtSchool/redis_client.py | 10 + Back-End/N3wtSchool/renderers.py | 14 + Back-End/N3wtSchool/settings.py | 247 + Back-End/N3wtSchool/signals.py | 21 + Back-End/N3wtSchool/urls.py | 49 + Back-End/N3wtSchool/wsgi.py | 16 + Back-End/__version__.py | 1 + Back-End/db.sqlite3 | Bin 0 -> 131072 bytes Back-End/manage.py | 22 + Back-End/requirements.txt | Bin 0 -> 2414 bytes Back-End/saveDB.py | 15 + Back-End/start.py | 37 + Back-End/static/css/headings.css | 22 + Back-End/static/css/icons.css | 80 + Back-End/static/css/main.css | 506 ++ Back-End/static/img/icons/arrow-square-up.svg | 4 + Back-End/static/img/icons/book.svg | 6 + Back-End/static/img/icons/briefcase.svg | 7 + Back-End/static/img/icons/calculator.svg | 10 + Back-End/static/img/icons/directbox-send.svg | 7 + Back-End/static/img/icons/edit.svg | 5 + Back-End/static/img/icons/key.svg | 4 + Back-End/static/img/icons/profile-add.svg | 6 + Back-End/static/img/icons/receipt-edit.svg | 7 + Back-End/static/img/icons/teacher.svg | 5 + Back-End/static/img/icons/user-add.svg | 7 + Back-End/static/img/icons/user-line.svg | 4 + Back-End/static/img/icons/user-minus.svg | 6 + Back-End/static/img/icons/user-search.svg | 6 + Back-End/static/img/icons/user.svg | 4 + Back-End/static/img/logo_min.svg | 8 + Back-End/static/index.html | 48 + Back-End/updateApp.py | 5 + CHANGELOG.md | 0 Front-End/.env | 2 + Front-End/.eslintrc.json | 3 + Front-End/.gitignore | 36 + Front-End/.vscode/settings.json | 6 + Front-End/README.md | 36 + Front-End/jsconfig.json | 7 + .../messages/en/ResponsableInputFields.json | 13 + Front-End/messages/en/dashboard.json | 9 + Front-End/messages/en/homePage.json | 5 + Front-End/messages/en/pagination.json | 6 + Front-End/messages/en/sidebar.json | 9 + Front-End/messages/en/students.json | 30 + .../messages/fr/ResponsableInputFields.json | 13 + Front-End/messages/fr/dashboard.json | 9 + Front-End/messages/fr/homePage.json | 5 + Front-End/messages/fr/pagination.json | 6 + Front-End/messages/fr/sidebar.json | 9 + Front-End/messages/fr/students.json | 30 + Front-End/next.config.mjs | 8 + Front-End/package-lock.json | 5642 +++++++++++++++++ Front-End/package.json | 36 + Front-End/postcss.config.js | 6 + Front-End/project.inlang/.gitignore | 1 + Front-End/project.inlang/project_id | 1 + Front-End/project.inlang/settings.json | 12 + Front-End/scripts/check-hardcoded-strings.js | 192 + .../src/app/[locale]/admin/classes/page.js | 49 + .../src/app/[locale]/admin/grades/page.js | 10 + Front-End/src/app/[locale]/admin/layout.js | 96 + Front-End/src/app/[locale]/admin/page.js | 143 + .../src/app/[locale]/admin/planning/page.js | 65 + .../src/app/[locale]/admin/settings/page.js | 105 + .../src/app/[locale]/admin/structure/page.js | 159 + .../admin/students/editInscription/page.js | 91 + .../src/app/[locale]/admin/students/page.js | 334 + .../src/app/[locale]/admin/teachers/page.js | 23 + Front-End/src/app/[locale]/page.js | 18 + .../[locale]/parents/editInscription/page.js | 113 + Front-End/src/app/[locale]/parents/layout.js | 92 + .../app/[locale]/parents/messagerie/page.js | 106 + Front-End/src/app/[locale]/parents/page.js | 90 + .../src/app/[locale]/parents/settings/page.js | 74 + .../src/app/[locale]/users/login/page.js | 124 + .../app/[locale]/users/password/new/page.js | 107 + .../app/[locale]/users/password/reset/page.js | 144 + .../src/app/[locale]/users/subscribe/page.js | 180 + Front-End/src/app/favicon.ico | Bin 0 -> 251966 bytes Front-End/src/app/favicon.svg | 42 + Front-End/src/app/layout.js | 41 + Front-End/src/app/lib/actions.js | 39 + Front-End/src/app/not-found.js | 15 + Front-End/src/components/AlertMessage.js | 17 + Front-End/src/components/AlertWithModal.js | 29 + Front-End/src/components/AlphabetLinks.js | 37 + Front-End/src/components/Button.js | 27 + Front-End/src/components/Calendar.js | 212 + .../src/components/Calendar/MonthView.js | 86 + .../src/components/Calendar/PlanningView.js | 111 + Front-End/src/components/Calendar/WeekView.js | 208 + Front-End/src/components/Calendar/YearView.js | 52 + Front-End/src/components/ClassForm.js | 188 + Front-End/src/components/ClassesSection.js | 111 + Front-End/src/components/DjangoCSRFToken.js | 16 + Front-End/src/components/DropdownMenu.js | 52 + Front-End/src/components/EventModal.js | 320 + Front-End/src/components/InputPhone.js | 37 + Front-End/src/components/InputText.js | 21 + Front-End/src/components/InputTextIcon.js | 25 + .../components/Inscription/InscriptionForm.js | 375 ++ .../Inscription/InscriptionFormShared.js | 181 + .../Inscription/ResponsableInputFields.js | 103 + Front-End/src/components/Loader.js | 9 + Front-End/src/components/Logo.js | 13 + Front-End/src/components/Modal.js | 33 + Front-End/src/components/Pagination.js | 47 + Front-End/src/components/Popup.js | 21 + .../src/components/ScheduleNavigation.js | 170 + Front-End/src/components/SelectChoice.js | 20 + Front-End/src/components/Sidebar.js | 55 + Front-End/src/components/Slider.js | 59 + .../src/components/SpecialitiesSection.js | 87 + Front-End/src/components/SpecialityForm.js | 50 + Front-End/src/components/StatusLabel.js | 59 + Front-End/src/components/Tab.js | 10 + Front-End/src/components/TabContent.js | 9 + Front-End/src/components/Table.js | 58 + Front-End/src/components/TeacherForm.js | 111 + Front-End/src/components/TeachersSection.js | 94 + Front-End/src/components/ToggleView.js | 34 + Front-End/src/context/PlanningContext.js | 93 + Front-End/src/css/tailwind.css | 3 + Front-End/src/data/mockData.js | 30 + Front-End/src/data/mockFicheInscription.js | 322 + Front-End/src/data/mockStudent.js | 22 + Front-End/src/data/mockUsersData.js | 8 + Front-End/src/fonts/GeistMonoVF.woff | Bin 0 -> 67864 bytes Front-End/src/fonts/GeistVF.woff | Bin 0 -> 66268 bytes Front-End/src/hooks/useCalendar.js | 26 + Front-End/src/hooks/useCsrfToken.js | 29 + Front-End/src/hooks/useEvents.js | 63 + Front-End/src/hooks/useLocalStorage.js | 31 + Front-End/src/hooks/useSchedules.js | 60 + Front-End/src/i18n/request.js | 42 + Front-End/src/i18n/routing.js | 17 + Front-End/src/img/logo_min.svg | 42 + Front-End/src/middleware.js | 25 + Front-End/src/utils/Date.js | 97 + Front-End/src/utils/Telephone.js | 35 + Front-End/src/utils/Url.js | 72 + Front-End/src/utils/constants.js | 30 + Front-End/src/utils/events.js | 110 + Front-End/src/utils/i18n.js | 21 + Front-End/tailwind.config.js | 13 + README.md | 62 + commitlint.config.js | 12 + docker-compose.yml | 57 + docs/CODING_GUIDELINES.md | 122 + docs/Installation_Manuelle.md | 67 + package-lock.json | 5246 +++++++++++++++ package.json | 20 + scripts/commit-template.txt | 17 + scripts/prepare-commit-msg.js | 30 + scripts/update-version.js | 26 + 228 files changed, 22694 insertions(+) create mode 100644 .gitignore create mode 100644 .husky/commit-msg create mode 100644 .husky/pre-commit create mode 100644 .husky/prepare-commit-msg create mode 100644 .versionrc create mode 100644 Back-End/Dockerfile create mode 100644 Back-End/GestionEnseignants/__init__.py create mode 100644 Back-End/GestionEnseignants/admin.py create mode 100644 Back-End/GestionEnseignants/apps.py create mode 100644 Back-End/GestionEnseignants/models.py create mode 100644 Back-End/GestionEnseignants/serializers.py create mode 100644 Back-End/GestionEnseignants/tests.py create mode 100644 Back-End/GestionEnseignants/urls.py create mode 100644 Back-End/GestionEnseignants/views.py create mode 100644 Back-End/GestionInscriptions/Configuration/application.default.json create mode 100644 Back-End/GestionInscriptions/Configuration/automate.json create mode 100644 Back-End/GestionInscriptions/Configuration/inscriptions.json create mode 100644 Back-End/GestionInscriptions/__init__.py create mode 100644 Back-End/GestionInscriptions/admin.py create mode 100644 Back-End/GestionInscriptions/apps.py create mode 100644 Back-End/GestionInscriptions/automate.py create mode 100644 Back-End/GestionInscriptions/mailManager.py create mode 100644 Back-End/GestionInscriptions/models.py create mode 100644 Back-End/GestionInscriptions/pagination.py create mode 100644 Back-End/GestionInscriptions/serializers.py create mode 100644 Back-End/GestionInscriptions/signals.py create mode 100644 Back-End/GestionInscriptions/tasks.py create mode 100644 Back-End/GestionInscriptions/templates/GestionInscriptions/ajouterFicheEleve.html create mode 100644 Back-End/GestionInscriptions/templates/GestionInscriptions/configure.html create mode 100644 Back-End/GestionInscriptions/templates/GestionInscriptions/creationDossier.html create mode 100644 Back-End/GestionInscriptions/templates/GestionInscriptions/editStudent.html create mode 100644 Back-End/GestionInscriptions/templates/GestionInscriptions/index.html create mode 100644 Back-End/GestionInscriptions/templates/base.html create mode 100644 Back-End/GestionInscriptions/templates/pdfs/dossier_inscription.html create mode 100644 Back-End/GestionInscriptions/templatetags/myTemplateTag.py create mode 100644 Back-End/GestionInscriptions/urls.py create mode 100644 Back-End/GestionInscriptions/util.py create mode 100644 Back-End/GestionInscriptions/views.py create mode 100644 Back-End/GestionLogin/__init__.py create mode 100644 Back-End/GestionLogin/admin.py create mode 100644 Back-End/GestionLogin/apps.py create mode 100644 Back-End/GestionLogin/backends.py create mode 100644 Back-End/GestionLogin/models.py create mode 100644 Back-End/GestionLogin/serializers.py create mode 100644 Back-End/GestionLogin/templates/GestionLogin/login.html create mode 100644 Back-End/GestionLogin/templates/GestionLogin/new-password.html create mode 100644 Back-End/GestionLogin/templates/GestionLogin/reset-password.html create mode 100644 Back-End/GestionLogin/templates/GestionLogin/subscribe.html create mode 100644 Back-End/GestionLogin/urls.py create mode 100644 Back-End/GestionLogin/validator.py create mode 100644 Back-End/GestionLogin/views.py create mode 100644 Back-End/GestionMessagerie/__init__.py create mode 100644 Back-End/GestionMessagerie/admin.py create mode 100644 Back-End/GestionMessagerie/apps.py create mode 100644 Back-End/GestionMessagerie/models.py create mode 100644 Back-End/GestionMessagerie/serializers.py create mode 100644 Back-End/GestionMessagerie/urls.py create mode 100644 Back-End/GestionMessagerie/views.py create mode 100644 Back-End/GestionNotification/__init__.py create mode 100644 Back-End/GestionNotification/admin.py create mode 100644 Back-End/GestionNotification/apps.py create mode 100644 Back-End/GestionNotification/models.py create mode 100644 Back-End/GestionNotification/signals.py create mode 100644 Back-End/GestionNotification/urls.py create mode 100644 Back-End/GestionNotification/views.py create mode 100644 Back-End/N3wtSchool/__init__.py create mode 100644 Back-End/N3wtSchool/apps.py create mode 100644 Back-End/N3wtSchool/asgi.py create mode 100644 Back-End/N3wtSchool/bdd.py create mode 100644 Back-End/N3wtSchool/celery.py create mode 100644 Back-End/N3wtSchool/error.py create mode 100644 Back-End/N3wtSchool/redis_client.py create mode 100644 Back-End/N3wtSchool/renderers.py create mode 100644 Back-End/N3wtSchool/settings.py create mode 100644 Back-End/N3wtSchool/signals.py create mode 100644 Back-End/N3wtSchool/urls.py create mode 100644 Back-End/N3wtSchool/wsgi.py create mode 100644 Back-End/__version__.py create mode 100644 Back-End/db.sqlite3 create mode 100644 Back-End/manage.py create mode 100644 Back-End/requirements.txt create mode 100644 Back-End/saveDB.py create mode 100644 Back-End/start.py create mode 100644 Back-End/static/css/headings.css create mode 100644 Back-End/static/css/icons.css create mode 100644 Back-End/static/css/main.css create mode 100644 Back-End/static/img/icons/arrow-square-up.svg create mode 100644 Back-End/static/img/icons/book.svg create mode 100644 Back-End/static/img/icons/briefcase.svg create mode 100644 Back-End/static/img/icons/calculator.svg create mode 100644 Back-End/static/img/icons/directbox-send.svg create mode 100644 Back-End/static/img/icons/edit.svg create mode 100644 Back-End/static/img/icons/key.svg create mode 100644 Back-End/static/img/icons/profile-add.svg create mode 100644 Back-End/static/img/icons/receipt-edit.svg create mode 100644 Back-End/static/img/icons/teacher.svg create mode 100644 Back-End/static/img/icons/user-add.svg create mode 100644 Back-End/static/img/icons/user-line.svg create mode 100644 Back-End/static/img/icons/user-minus.svg create mode 100644 Back-End/static/img/icons/user-search.svg create mode 100644 Back-End/static/img/icons/user.svg create mode 100644 Back-End/static/img/logo_min.svg create mode 100644 Back-End/static/index.html create mode 100644 Back-End/updateApp.py create mode 100644 CHANGELOG.md create mode 100644 Front-End/.env create mode 100644 Front-End/.eslintrc.json create mode 100644 Front-End/.gitignore create mode 100644 Front-End/.vscode/settings.json create mode 100644 Front-End/README.md create mode 100644 Front-End/jsconfig.json create mode 100644 Front-End/messages/en/ResponsableInputFields.json create mode 100644 Front-End/messages/en/dashboard.json create mode 100644 Front-End/messages/en/homePage.json create mode 100644 Front-End/messages/en/pagination.json create mode 100644 Front-End/messages/en/sidebar.json create mode 100644 Front-End/messages/en/students.json create mode 100644 Front-End/messages/fr/ResponsableInputFields.json create mode 100644 Front-End/messages/fr/dashboard.json create mode 100644 Front-End/messages/fr/homePage.json create mode 100644 Front-End/messages/fr/pagination.json create mode 100644 Front-End/messages/fr/sidebar.json create mode 100644 Front-End/messages/fr/students.json create mode 100644 Front-End/next.config.mjs create mode 100644 Front-End/package-lock.json create mode 100644 Front-End/package.json create mode 100644 Front-End/postcss.config.js create mode 100644 Front-End/project.inlang/.gitignore create mode 100644 Front-End/project.inlang/project_id create mode 100644 Front-End/project.inlang/settings.json create mode 100644 Front-End/scripts/check-hardcoded-strings.js create mode 100644 Front-End/src/app/[locale]/admin/classes/page.js create mode 100644 Front-End/src/app/[locale]/admin/grades/page.js create mode 100644 Front-End/src/app/[locale]/admin/layout.js create mode 100644 Front-End/src/app/[locale]/admin/page.js create mode 100644 Front-End/src/app/[locale]/admin/planning/page.js create mode 100644 Front-End/src/app/[locale]/admin/settings/page.js create mode 100644 Front-End/src/app/[locale]/admin/structure/page.js create mode 100644 Front-End/src/app/[locale]/admin/students/editInscription/page.js create mode 100644 Front-End/src/app/[locale]/admin/students/page.js create mode 100644 Front-End/src/app/[locale]/admin/teachers/page.js create mode 100644 Front-End/src/app/[locale]/page.js create mode 100644 Front-End/src/app/[locale]/parents/editInscription/page.js create mode 100644 Front-End/src/app/[locale]/parents/layout.js create mode 100644 Front-End/src/app/[locale]/parents/messagerie/page.js create mode 100644 Front-End/src/app/[locale]/parents/page.js create mode 100644 Front-End/src/app/[locale]/parents/settings/page.js create mode 100644 Front-End/src/app/[locale]/users/login/page.js create mode 100644 Front-End/src/app/[locale]/users/password/new/page.js create mode 100644 Front-End/src/app/[locale]/users/password/reset/page.js create mode 100644 Front-End/src/app/[locale]/users/subscribe/page.js create mode 100644 Front-End/src/app/favicon.ico create mode 100644 Front-End/src/app/favicon.svg create mode 100644 Front-End/src/app/layout.js create mode 100644 Front-End/src/app/lib/actions.js create mode 100644 Front-End/src/app/not-found.js create mode 100644 Front-End/src/components/AlertMessage.js create mode 100644 Front-End/src/components/AlertWithModal.js create mode 100644 Front-End/src/components/AlphabetLinks.js create mode 100644 Front-End/src/components/Button.js create mode 100644 Front-End/src/components/Calendar.js create mode 100644 Front-End/src/components/Calendar/MonthView.js create mode 100644 Front-End/src/components/Calendar/PlanningView.js create mode 100644 Front-End/src/components/Calendar/WeekView.js create mode 100644 Front-End/src/components/Calendar/YearView.js create mode 100644 Front-End/src/components/ClassForm.js create mode 100644 Front-End/src/components/ClassesSection.js create mode 100644 Front-End/src/components/DjangoCSRFToken.js create mode 100644 Front-End/src/components/DropdownMenu.js create mode 100644 Front-End/src/components/EventModal.js create mode 100644 Front-End/src/components/InputPhone.js create mode 100644 Front-End/src/components/InputText.js create mode 100644 Front-End/src/components/InputTextIcon.js create mode 100644 Front-End/src/components/Inscription/InscriptionForm.js create mode 100644 Front-End/src/components/Inscription/InscriptionFormShared.js create mode 100644 Front-End/src/components/Inscription/ResponsableInputFields.js create mode 100644 Front-End/src/components/Loader.js create mode 100644 Front-End/src/components/Logo.js create mode 100644 Front-End/src/components/Modal.js create mode 100644 Front-End/src/components/Pagination.js create mode 100644 Front-End/src/components/Popup.js create mode 100644 Front-End/src/components/ScheduleNavigation.js create mode 100644 Front-End/src/components/SelectChoice.js create mode 100644 Front-End/src/components/Sidebar.js create mode 100644 Front-End/src/components/Slider.js create mode 100644 Front-End/src/components/SpecialitiesSection.js create mode 100644 Front-End/src/components/SpecialityForm.js create mode 100644 Front-End/src/components/StatusLabel.js create mode 100644 Front-End/src/components/Tab.js create mode 100644 Front-End/src/components/TabContent.js create mode 100644 Front-End/src/components/Table.js create mode 100644 Front-End/src/components/TeacherForm.js create mode 100644 Front-End/src/components/TeachersSection.js create mode 100644 Front-End/src/components/ToggleView.js create mode 100644 Front-End/src/context/PlanningContext.js create mode 100644 Front-End/src/css/tailwind.css create mode 100644 Front-End/src/data/mockData.js create mode 100644 Front-End/src/data/mockFicheInscription.js create mode 100644 Front-End/src/data/mockStudent.js create mode 100644 Front-End/src/data/mockUsersData.js create mode 100644 Front-End/src/fonts/GeistMonoVF.woff create mode 100644 Front-End/src/fonts/GeistVF.woff create mode 100644 Front-End/src/hooks/useCalendar.js create mode 100644 Front-End/src/hooks/useCsrfToken.js create mode 100644 Front-End/src/hooks/useEvents.js create mode 100644 Front-End/src/hooks/useLocalStorage.js create mode 100644 Front-End/src/hooks/useSchedules.js create mode 100644 Front-End/src/i18n/request.js create mode 100644 Front-End/src/i18n/routing.js create mode 100644 Front-End/src/img/logo_min.svg create mode 100644 Front-End/src/middleware.js create mode 100644 Front-End/src/utils/Date.js create mode 100644 Front-End/src/utils/Telephone.js create mode 100644 Front-End/src/utils/Url.js create mode 100644 Front-End/src/utils/constants.js create mode 100644 Front-End/src/utils/events.js create mode 100644 Front-End/src/utils/i18n.js create mode 100644 Front-End/tailwind.config.js create mode 100644 README.md create mode 100644 commitlint.config.js create mode 100644 docker-compose.yml create mode 100644 docs/CODING_GUIDELINES.md create mode 100644 docs/Installation_Manuelle.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/commit-template.txt create mode 100644 scripts/prepare-commit-msg.js create mode 100644 scripts/update-version.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a37a203 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +Back-End/*/Configuration/application.json +.venv/ +__pycache__/ +node_modules/ +Back-End/*/migrations/* +Back-End/documents +Back-End/*.dmp +Back-End/staticfiles +hardcoded-strings-report.md \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..34eed8b --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit $1 \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..e69de29 diff --git a/.husky/prepare-commit-msg b/.husky/prepare-commit-msg new file mode 100644 index 0000000..bfb96ca --- /dev/null +++ b/.husky/prepare-commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +node scripts/prepare-commit-msg.js "$1" "$2" \ No newline at end of file diff --git a/.versionrc b/.versionrc new file mode 100644 index 0000000..338c587 --- /dev/null +++ b/.versionrc @@ -0,0 +1,27 @@ +{ + "header": "# Changelog\n\nToutes les modifications notables apportées à ce projet seront documentées dans ce fichier.\n", + "tagPrefix": "", + "bumpFiles": [ + { + "filename": "package.json", + "type": "json" + }, + { + "filename": "Front-End/package.json", + "updater": "scripts/update-version.js" + }, + { + "filename": "Back-End/__version__.py", + "updater": "scripts/update-version.js" + } + ], + "types": [ + { "type": "feat", "section": "Nouvelles fonctionnalités", "hidden": false }, + { "type": "fix", "section": "Corrections de bugs", "hidden": false }, + { "type": "docs", "section": "Documentation", "hidden": false }, + { "type": "style", "section": "Mises en forme", "hidden": true }, + { "type": "refactor", "section": "Refactorisations", "hidden": false }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "chore", "section": "Tâches diverses", "hidden": true } + ] +} \ No newline at end of file diff --git a/Back-End/Dockerfile b/Back-End/Dockerfile new file mode 100644 index 0000000..7f46014 --- /dev/null +++ b/Back-End/Dockerfile @@ -0,0 +1,20 @@ +# Dockerfile + +# The first instruction is what image we want to base our container on +# We Use an official Python runtime as a parent image +FROM python:3.12.7 + +# Allows docker to cache installed dependencies between builds +COPY requirements.txt requirements.txt +RUN pip install -r requirements.txt + +# Mounts the application code to the image +COPY . . +WORKDIR /Back-End + +EXPOSE 8080 + +ENV DJANGO_SETTINGS_MODULE N3wtSchool.settings +ENV DJANGO_SUPERUSER_PASSWORD=admin +ENV DJANGO_SUPERUSER_USERNAME=admin +ENV DJANGO_SUPERUSER_EMAIL=admin@n3wtschool.com diff --git a/Back-End/GestionEnseignants/__init__.py b/Back-End/GestionEnseignants/__init__.py new file mode 100644 index 0000000..04932af --- /dev/null +++ b/Back-End/GestionEnseignants/__init__.py @@ -0,0 +1 @@ +default_app_config = 'GestionEnseignants.apps.GestionenseignantsConfig' diff --git a/Back-End/GestionEnseignants/admin.py b/Back-End/GestionEnseignants/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Back-End/GestionEnseignants/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Back-End/GestionEnseignants/apps.py b/Back-End/GestionEnseignants/apps.py new file mode 100644 index 0000000..16c4573 --- /dev/null +++ b/Back-End/GestionEnseignants/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GestionenseignantsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'GestionEnseignants' diff --git a/Back-End/GestionEnseignants/models.py b/Back-End/GestionEnseignants/models.py new file mode 100644 index 0000000..6a2afb8 --- /dev/null +++ b/Back-End/GestionEnseignants/models.py @@ -0,0 +1,32 @@ +from django.db import models + +class Specialite(models.Model): + nom = models.CharField(max_length=100) + dateCreation = models.DateTimeField(auto_now=True) + codeCouleur = models.CharField(max_length=7, default='#FFFFFF') + + def __str__(self): + return self.nom + +class Enseignant(models.Model): + nom = models.CharField(max_length=100) + prenom = models.CharField(max_length=100) + mail = models.EmailField(unique=True) + specialite = models.ForeignKey(Specialite, on_delete=models.SET_NULL, null=True, blank=True, related_name='enseignants') + + def __str__(self): + return f"{self.nom} {self.prenom}" + +class Classe(models.Model): + nom_ambiance = models.CharField(max_length=255) + tranche_age = models.JSONField() + nombre_eleves = models.PositiveIntegerField() + langue_enseignement = models.CharField(max_length=255) + annee_scolaire = models.CharField(max_length=9) + dateCreation = models.DateTimeField(auto_now_add=True) + specialites = models.ManyToManyField(Specialite, related_name='classes') + enseignant_principal = models.ForeignKey(Enseignant, on_delete=models.SET_NULL, null=True, blank=True, related_name='classes_principal') + + def __str__(self): + return self.nom_ambiance + diff --git a/Back-End/GestionEnseignants/serializers.py b/Back-End/GestionEnseignants/serializers.py new file mode 100644 index 0000000..a3e3a38 --- /dev/null +++ b/Back-End/GestionEnseignants/serializers.py @@ -0,0 +1,83 @@ +from rest_framework import serializers +from .models import Enseignant, Specialite, Classe +from N3wtSchool import settings +from django.utils import timezone +import pytz + +class SpecialiteSerializer(serializers.ModelSerializer): + dateCreation_formattee = serializers.SerializerMethodField() + class Meta: + model = Specialite + fields = '__all__' + + def get_dateCreation_formattee(self, obj): + utc_time = timezone.localtime(obj.dateCreation) # Convertir en heure locale + local_tz = pytz.timezone(settings.TZ_APPLI) + local_time = utc_time.astimezone(local_tz) + + return local_time.strftime("%d-%m-%Y %H:%M") + +class ClasseSerializer(serializers.ModelSerializer): + specialites = SpecialiteSerializer(many=True, read_only=True) + specialites_ids = serializers.PrimaryKeyRelatedField(queryset=Specialite.objects.all(), many=True, source='specialites') + dateCreation_formattee = serializers.SerializerMethodField() + enseignant_principal = serializers.SerializerMethodField() + enseignant_principal_id = serializers.PrimaryKeyRelatedField(queryset=Enseignant.objects.all(), source='enseignant_principal', write_only=False, read_only=False) + + class Meta: + model = Classe + fields = ['id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement', 'specialites', 'specialites_ids', 'enseignant_principal', 'enseignant_principal_id', 'annee_scolaire', 'dateCreation', 'dateCreation_formattee'] + + def get_enseignant_principal(self, obj): + from .serializers import EnseignantDetailSerializer + if obj.enseignant_principal: + return EnseignantDetailSerializer(obj.enseignant_principal).data + return None + + def create(self, validated_data): + specialites_data = validated_data.pop('specialites', []) + classe = Classe.objects.create(**validated_data) + classe.specialites.set(specialites_data) + return classe + + def update(self, instance, validated_data): + specialites_data = validated_data.pop('specialites', []) + instance.nom_ambiance = validated_data.get('nom_ambiance', instance.nom_ambiance) + instance.tranche_age = validated_data.get('tranche_age', instance.tranche_age) + instance.nombre_eleves = validated_data.get('nombre_eleves', instance.nombre_eleves) + instance.langue_enseignement = validated_data.get('langue_enseignement', instance.langue_enseignement) + instance.annee_scolaire = validated_data.get('annee_scolaire', instance.annee_scolaire) + instance.enseignant_principal = validated_data.get('enseignant_principal', instance.enseignant_principal) + instance.save() + instance.specialites.set(specialites_data) + return instance + + def get_dateCreation_formattee(self, obj): + utc_time = timezone.localtime(obj.dateCreation) # Convertir en heure locale + local_tz = pytz.timezone(settings.TZ_APPLI) + local_time = utc_time.astimezone(local_tz) + + return local_time.strftime("%d-%m-%Y %H:%M") + +class EnseignantSerializer(serializers.ModelSerializer): + specialite = SpecialiteSerializer(read_only=True) + specialite_id = serializers.PrimaryKeyRelatedField(queryset=Specialite.objects.all(), source='specialite', write_only=False, read_only=False) + classes_principal = ClasseSerializer(many=True, read_only=True) + + class Meta: + model = Enseignant + fields = ['id', 'nom', 'prenom', 'mail', 'specialite', 'specialite_id', 'classes_principal'] + + def create(self, validated_data): + specialite = validated_data.pop('specialite', None) + enseignant = Enseignant.objects.create(**validated_data) + enseignant.specialite = specialite + enseignant.save() + return enseignant + +class EnseignantDetailSerializer(serializers.ModelSerializer): + specialite = SpecialiteSerializer(read_only=True) + + class Meta: + model = Enseignant + fields = ['id', 'nom', 'prenom', 'mail', 'specialite'] diff --git a/Back-End/GestionEnseignants/tests.py b/Back-End/GestionEnseignants/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Back-End/GestionEnseignants/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Back-End/GestionEnseignants/urls.py b/Back-End/GestionEnseignants/urls.py new file mode 100644 index 0000000..41acf66 --- /dev/null +++ b/Back-End/GestionEnseignants/urls.py @@ -0,0 +1,17 @@ +from django.urls import path, re_path + +from GestionEnseignants.views import EnseignantsView, EnseignantView, SpecialitesView, SpecialiteView, ClassesView, ClasseView + +urlpatterns = [ + re_path(r'^enseignants$', EnseignantsView.as_view(), name="enseignants"), + re_path(r'^enseignant$', EnseignantView.as_view(), name="enseignant"), + re_path(r'^enseignant/([0-9]+)$', EnseignantView.as_view(), name="enseignant"), + + re_path(r'^specialites$', SpecialitesView.as_view(), name="specialites"), + re_path(r'^specialite$', SpecialiteView.as_view(), name="specialite"), + re_path(r'^specialite/([0-9]+)$', SpecialiteView.as_view(), name="specialite"), + + re_path(r'^classes$', ClassesView.as_view(), name="classes"), + re_path(r'^classe$', ClasseView.as_view(), name="classe"), + re_path(r'^classe/([0-9]+)$', ClasseView.as_view(), name="classe"), +] \ No newline at end of file diff --git a/Back-End/GestionEnseignants/views.py b/Back-End/GestionEnseignants/views.py new file mode 100644 index 0000000..7007523 --- /dev/null +++ b/Back-End/GestionEnseignants/views.py @@ -0,0 +1,180 @@ +from django.http.response import JsonResponse +from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect +from django.utils.decorators import method_decorator +from rest_framework.parsers import JSONParser +from rest_framework.views import APIView +from django.core.cache import cache +from .models import Enseignant, Specialite, Classe +from .serializers import EnseignantSerializer, SpecialiteSerializer, ClasseSerializer +from N3wtSchool import bdd + +class EnseignantsView(APIView): + def get(self, request): + enseignantsList=bdd.getAllObjects(Enseignant) + enseignants_serializer=EnseignantSerializer(enseignantsList, many=True) + + return JsonResponse(enseignants_serializer.data, safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class EnseignantView(APIView): + def get (self, request, _id): + enseignant = bdd.getObject(_objectName=Enseignant, _columnName='id', _value=_id) + enseignant_serializer=EnseignantSerializer(enseignant) + + return JsonResponse(enseignant_serializer.data, safe=False) + + def post(self, request): + enseignant_data=JSONParser().parse(request) + enseignant_serializer = EnseignantSerializer(data=enseignant_data) + + if enseignant_serializer.is_valid(): + enseignant_serializer.save() + + return JsonResponse(enseignant_serializer.data, safe=False) + + return JsonResponse(enseignant_serializer.errors, safe=False) + + def put(self, request, _id): + enseignant_data=JSONParser().parse(request) + enseignant = bdd.getObject(_objectName=Enseignant, _columnName='id', _value=_id) + enseignant_serializer = EnseignantSerializer(enseignant, data=enseignant_data) + if enseignant_serializer.is_valid(): + enseignant_serializer.save() + return JsonResponse(enseignant_serializer.data, safe=False) + + return JsonResponse(enseignant_serializer.errors, safe=False) + + def delete(self, request, _id): + enseignant = bdd.getObject(_objectName=Enseignant, _columnName='id', _value=_id) + if enseignant != None: + enseignant.delete() + + return JsonResponse("La suppression de la spécialité a été effectuée avec succès", safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class SpecialitesView(APIView): + def get(self, request): + specialitesList=bdd.getAllObjects(Specialite) + specialites_serializer=SpecialiteSerializer(specialitesList, many=True) + + return JsonResponse(specialites_serializer.data, safe=False) + + def post(self, request): + specialites_data=JSONParser().parse(request) + all_valid = True + for specialite_data in specialites_data: + specialite_serializer = SpecialiteSerializer(data=specialite_data) + + if specialite_serializer.is_valid(): + specialite_serializer.save() + else: + all_valid = False + break + if all_valid: + specialitesList = bdd.getAllObjects(Specialite) + specialites_serializer = SpecialiteSerializer(specialitesList, many=True) + + return JsonResponse(specialite_serializer.errors, safe=False) + + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class SpecialiteView(APIView): + def get (self, request, _id): + specialite = bdd.getObject(_objectName=Specialite, _columnName='id', _value=_id) + specialite_serializer=SpecialiteSerializer(specialite) + + return JsonResponse(specialite_serializer.data, safe=False) + + def post(self, request): + specialite_data=JSONParser().parse(request) + specialite_serializer = SpecialiteSerializer(data=specialite_data) + + if specialite_serializer.is_valid(): + specialite_serializer.save() + return JsonResponse(specialite_serializer.data, safe=False) + + return JsonResponse(specialite_serializer.errors, safe=False) + + def put(self, request, _id): + specialite_data=JSONParser().parse(request) + specialite = bdd.getObject(_objectName=Specialite, _columnName='id', _value=_id) + specialite_serializer = SpecialiteSerializer(specialite, data=specialite_data) + if specialite_serializer.is_valid(): + specialite_serializer.save() + return JsonResponse(specialite_serializer.data, safe=False) + + return JsonResponse(specialite_serializer.errors, safe=False) + + def delete(self, request, _id): + specialite = bdd.getObject(_objectName=Specialite, _columnName='id', _value=_id) + if specialite != None: + specialite.delete() + + return JsonResponse("La suppression de la spécialité a été effectuée avec succès", safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class ClassesView(APIView): + def get(self, request): + classesList=bdd.getAllObjects(Classe) + classes_serializer=ClasseSerializer(classesList, many=True) + return JsonResponse(classes_serializer.data, safe=False) + + def post(self, request): + all_valid = True + classes_data=JSONParser().parse(request) + for classe_data in classes_data: + classe_serializer = ClasseSerializer(data=classe_data) + + if classe_serializer.is_valid(): + classe_serializer.save() + else: + all_valid = False + break + + if all_valid: + classesList = bdd.getAllObjects(Classe) + classes_serializer = ClasseSerializer(classesList, many=True) + + return JsonResponse(classes_serializer.data, safe=False) + + return JsonResponse(classe_serializer.errors, safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class ClasseView(APIView): + def get (self, request, _id): + classe = bdd.getObject(_objectName=Classe, _columnName='id', _value=_id) + classe_serializer=ClasseSerializer(classe) + + return JsonResponse(classe_serializer.data, safe=False) + + def post(self, request): + classe_data=JSONParser().parse(request) + classe_serializer = ClasseSerializer(data=classe_data) + + if classe_serializer.is_valid(): + classe_serializer.save() + return JsonResponse(classe_serializer.data, safe=False) + + return JsonResponse(classe_serializer.errors, safe=False) + + def put(self, request, _id): + classe_data=JSONParser().parse(request) + classe = bdd.getObject(_objectName=Classe, _columnName='id', _value=_id) + classe_serializer = ClasseSerializer(classe, data=classe_data) + if classe_serializer.is_valid(): + classe_serializer.save() + return JsonResponse(classe_serializer.data, safe=False) + + return JsonResponse(classe_serializer.errors, safe=False) + + def delete(self, request, _id): + classe = bdd.getObject(_objectName=Classe, _columnName='id', _value=_id) + if classe != None: + classe.delete() + + return JsonResponse("La suppression de la classe a été effectuée avec succès", safe=False) \ No newline at end of file diff --git a/Back-End/GestionInscriptions/Configuration/application.default.json b/Back-End/GestionInscriptions/Configuration/application.default.json new file mode 100644 index 0000000..9d2cefb --- /dev/null +++ b/Back-End/GestionInscriptions/Configuration/application.default.json @@ -0,0 +1,4 @@ +{ + "mailFrom":"", + "password":"" +} \ No newline at end of file diff --git a/Back-End/GestionInscriptions/Configuration/automate.json b/Back-End/GestionInscriptions/Configuration/automate.json new file mode 100644 index 0000000..0249aa3 --- /dev/null +++ b/Back-End/GestionInscriptions/Configuration/automate.json @@ -0,0 +1,63 @@ +{ + "states": [ + "ABSENT", + "CREE", + "ENVOYE", + "EN_VALIDATION", + "A_RELANCER", + "VALIDE", + "ARCHIVE" + ], + "transitions": [ + { + "name": "creationDI", + "from": "ABSENT", + "to": "CREE" + }, + { + "name": "envoiDI", + "from": "CREE", + "to": "ENVOYE" + }, + { + "name": "archiveDI", + "from": "CREE", + "to": "ARCHIVE" + }, + { + "name": "saisiDI", + "from": "ENVOYE", + "to": "EN_VALIDATION" + }, + { + "name": "relanceDI", + "from": "ENVOYE", + "to": "A_RELANCER" + }, + { + "name": "archiveDI", + "from": "A_RELANCER", + "to": "ARCHIVE" + }, + { + "name": "archiveDI", + "from": "ENVOYE", + "to": "ARCHIVE" + }, + { + "name": "valideDI", + "from": "EN_VALIDATION", + "to": "VALIDE" + }, + { + "name": "archiveDI", + "from": "EN_VALIDATION", + "to": "ARCHIVE" + }, + { + "name": "archiveDI", + "from": "VALIDE", + "to": "ARCHIVE" + } + ] +} diff --git a/Back-End/GestionInscriptions/Configuration/inscriptions.json b/Back-End/GestionInscriptions/Configuration/inscriptions.json new file mode 100644 index 0000000..0170a43 --- /dev/null +++ b/Back-End/GestionInscriptions/Configuration/inscriptions.json @@ -0,0 +1,18 @@ +{ + "activationMailRelance": "Oui", + "delaiRelance": "30", + "ambiances": [ + "2-3 ans", + "3-6 ans", + "6-12 ans" + ], + "genres": [ + "Fille", + "Garçon" + ], + "modesPaiement": [ + "Chèque", + "Virement", + "Prélèvement SEPA" + ] +} \ No newline at end of file diff --git a/Back-End/GestionInscriptions/__init__.py b/Back-End/GestionInscriptions/__init__.py new file mode 100644 index 0000000..b290843 --- /dev/null +++ b/Back-End/GestionInscriptions/__init__.py @@ -0,0 +1 @@ +default_app_config = 'GestionInscriptions.apps.GestionInscriptionsConfig' \ No newline at end of file diff --git a/Back-End/GestionInscriptions/admin.py b/Back-End/GestionInscriptions/admin.py new file mode 100644 index 0000000..2d81adb --- /dev/null +++ b/Back-End/GestionInscriptions/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from .models import * + +admin.site.register(Eleve) +admin.site.register(Responsable) + +class EleveAdmin(admin.ModelAdmin): + def save_model(self, request, obj, form, change): + obj.user = request.user + super().save_model(request, obj, form, change) diff --git a/Back-End/GestionInscriptions/apps.py b/Back-End/GestionInscriptions/apps.py new file mode 100644 index 0000000..f88ea7f --- /dev/null +++ b/Back-End/GestionInscriptions/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig +from django.conf import settings + +class GestioninscriptionsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'GestionInscriptions' + + def ready(self): + from GestionInscriptions.signals import clear_cache + clear_cache() diff --git a/Back-End/GestionInscriptions/automate.py b/Back-End/GestionInscriptions/automate.py new file mode 100644 index 0000000..510876e --- /dev/null +++ b/Back-End/GestionInscriptions/automate.py @@ -0,0 +1,45 @@ +# state_machine.py +import json +from GestionInscriptions.models import FicheInscription +from GestionInscriptions.signals import clear_cache + +state_mapping = { + "ABSENT": FicheInscription.EtatDossierInscription.DI_ABSENT, + "CREE": FicheInscription.EtatDossierInscription.DI_CREE, + "ENVOYE": FicheInscription.EtatDossierInscription.DI_ENVOYE, + "EN_VALIDATION": FicheInscription.EtatDossierInscription.DI_EN_VALIDATION, + "A_RELANCER": FicheInscription.EtatDossierInscription.DI_A_RELANCER, + "VALIDE": FicheInscription.EtatDossierInscription.DI_VALIDE, + "ARCHIVE": FicheInscription.EtatDossierInscription.DI_ARCHIVE +} + +def load_config(config_file): + with open(config_file, 'r') as file: + config = json.load(file) + return config + +def getStateMachineObject(etat) : + return Automate_DI_Inscription(etat) + +def getStateMachineObjectState(etat): + return Automate_DI_Inscription(etat).state + +def updateStateMachine(di, transition) : + automateModel = load_config('GestionInscriptions/Configuration/automate.json') + state_machine = getStateMachineObject(di.etat) + print(f'etat DI : {state_machine.state}') + if state_machine.trigger(transition, automateModel): + di.etat = state_machine.state + di.save() + clear_cache() + +class Automate_DI_Inscription: + def __init__(self, initial_state): + self.state = initial_state + + def trigger(self, transition_name, config): + for transition in config["transitions"]: + if transition["name"] == transition_name and self.state == state_mapping[transition["from"]]: + self.state = state_mapping[transition["to"]] + return True + return False \ No newline at end of file diff --git a/Back-End/GestionInscriptions/mailManager.py b/Back-End/GestionInscriptions/mailManager.py new file mode 100644 index 0000000..653658d --- /dev/null +++ b/Back-End/GestionInscriptions/mailManager.py @@ -0,0 +1,74 @@ +from django.core.mail import send_mail +import re +from N3wtSchool import settings + +def envoieReinitMotDePasse(recipients, code): + send_mail( + settings.EMAIL_REINIT_SUBJECT, + settings.EMAIL_REINIT_CORPUS%(str(code)), + settings.EMAIL_HOST_USER, + [recipients], + fail_silently=False, + ) + +def envoieDossierInscription(recipients): + errorMessage = '' + try: + print(f'{settings.EMAIL_HOST_USER}') + send_mail( + settings.EMAIL_INSCRIPTION_SUBJECT, + settings.EMAIL_INSCRIPTION_CORPUS%[recipients], + settings.EMAIL_HOST_USER, + [recipients], + fail_silently=False, + ) + except Exception as e: + errorMessage = str(e) + + return errorMessage + +def envoieRelanceDossierInscription(recipients, code): + errorMessage = '' + try: + send_mail( + settings.EMAIL_RELANCE_SUBJECT, + settings.EMAIL_RELANCE_CORPUS%str(code), + settings.EMAIL_HOST_USER, + [recipients], + fail_silently=False, + ) + except Exception as e: + errorMessage = str(e) + + return errorMessage + + +def envoieSEPA(recipients, ref): + send_mail( + settings.EMAIL_SEPA_SUBJECT%str(ref), + settings.EMAIL_SEPA_CORPUS, + settings.EMAIL_HOST_USER, + [recipients], + fail_silently=False, + ) + +def isValid(message, fiche_inscription): + # Est-ce que la référence du dossier est VALIDE + subject = message.subject + print ("++++ " + subject) + responsableMail = message.from_header + result = re.search('<(.*)>', responsableMail) + + if result: + responsableMail = result.group(1) + + result = re.search(r'.*\[Ref(.*)\].*', subject) + idMail = -1 + if result: + idMail = result.group(1).strip() + + eleve = fiche_inscription.eleve + responsable = eleve.getResponsablePrincipal() + mailReponsableAVerifier = responsable.mail + + return responsableMail == mailReponsableAVerifier and str(idMail) == str(fiche_inscription.eleve.id) \ No newline at end of file diff --git a/Back-End/GestionInscriptions/models.py b/Back-End/GestionInscriptions/models.py new file mode 100644 index 0000000..67bd194 --- /dev/null +++ b/Back-End/GestionInscriptions/models.py @@ -0,0 +1,123 @@ +from django.db import models +from django.utils.timezone import now +from django.conf import settings +from django.utils.translation import gettext_lazy as _ + +from GestionLogin.models import Profil + +class Langue(models.Model): + id = models.AutoField(primary_key=True) + libelle = models.CharField(max_length=200, default="") + + def __str__(self): + return "LANGUE" + +class Responsable(models.Model): + nom = models.CharField(max_length=200, default="") + prenom = models.CharField(max_length=200, default="") + dateNaissance = models.CharField(max_length=200, default="", blank=True) + adresse = models.CharField(max_length=200, default="", blank=True) + mail = models.CharField(max_length=200, default="", blank=True) + telephone = models.CharField(max_length=200, default="", blank=True) + profession = models.CharField(max_length=200, default="", blank=True) + profilAssocie = models.ForeignKey(Profil, on_delete=models.CASCADE) + + def __str__(self): + return self.nom + "_" + self.prenom + +class Frere(models.Model): + id = models.AutoField(primary_key=True) + nom = models.CharField(max_length=200, default="") + prenom = models.CharField(max_length=200, default="") + dateNaissance = models.CharField(max_length=200, default="", blank=True) + + def __str__(self): + return "FRERE" + +class Eleve(models.Model): + + class GenreEleve(models.IntegerChoices): + NONE = 0, _('Sélection du genre') + MALE = 1, _('Garçon') + FEMALE = 2, _('Fille') + + class NiveauEleve(models.IntegerChoices): + NONE = 0, _('Sélection du niveau') + TPS = 1, _('TPS - Très Petite Section') + PS = 2, _('PS - Petite Section') + MS = 3, _('MS - Moyenne Section') + GS = 4, _('GS - Grande Section') + + class ModePaiement(models.IntegerChoices): + NONE = 0, _('Sélection du mode de paiement') + PRELEVEMENT_SEPA = 1, _('Prélèvement SEPA') + CHEQUE = 2, _('Chèque') + + nom = models.CharField(max_length=200, default="") + prenom = models.CharField(max_length=200, default="") + genre = models.IntegerField(choices=GenreEleve, default=GenreEleve.NONE, blank=True) + niveau = models.IntegerField(choices=NiveauEleve, default=NiveauEleve.NONE, blank=True) + nationalite = models.CharField(max_length=200, default="", blank=True) + adresse = models.CharField(max_length=200, default="", blank=True) + dateNaissance = models.CharField(max_length=200, default="", blank=True) + lieuNaissance = models.CharField(max_length=200, default="", blank=True) + codePostalNaissance = models.IntegerField(default=0, blank=True) + medecinTraitant = models.CharField(max_length=200, default="", blank=True) + modePaiement = models.IntegerField(choices=ModePaiement, default=ModePaiement.NONE, blank=True) + + # Relation N-N + profils = models.ManyToManyField(Profil, blank=True) + + # Relation N-N + responsables = models.ManyToManyField(Responsable, blank=True) + + # Relation N-N + freres = models.ManyToManyField(Frere, blank=True) + + # Relation N-N + languesParlees = models.ManyToManyField(Langue, blank=True) + + def __str__(self): + return self.nom + "_" + self.prenom + + def getLanguesParlees(self): + return self.languesParlees.all() + + def getResponsablePrincipal(self): + return self.responsables.all()[0] + + def getResponsables(self): + return self.responsables.all() + + def getProfils(self): + return self.profils.all() + + def getFreres(self): + return self.freres.all() + + def getNbFreres(self): + return self.freres.count() + +class FicheInscription(models.Model): + + class EtatDossierInscription(models.IntegerChoices): + DI_ABSENT = 0, _('Pas de dossier d\'inscription') + DI_CREE = 1, _('Dossier d\'inscription créé') + DI_ENVOYE = 2, _('Dossier d\'inscription envoyé') + DI_EN_VALIDATION = 3, _('Dossier d\'inscription en cours de validation') + DI_A_RELANCER = 4, _('Dossier d\'inscription à relancer') + DI_VALIDE = 5, _('Dossier d\'inscription validé') + DI_ARCHIVE = 6, _('Dossier d\'inscription archivé') + + # Relation 1-1 + eleve = models.OneToOneField(Eleve, on_delete=models.CASCADE, primary_key=True) + etat = models.IntegerField(choices=EtatDossierInscription, default=EtatDossierInscription.DI_ABSENT) + dateMAJ = models.DateTimeField(auto_now=True) + notes = models.CharField(max_length=200, blank=True) + codeLienInscription = models.CharField(max_length=200, default="", blank=True) + fichierInscription = models.FileField(upload_to=settings.DOCUMENT_DIR, default="", blank=True) + di_associe = models.CharField(max_length=200, default="", blank=True) + + def __str__(self): + return "FI_" + self.eleve.nom + "_" + self.eleve.prenom + \ No newline at end of file diff --git a/Back-End/GestionInscriptions/pagination.py b/Back-End/GestionInscriptions/pagination.py new file mode 100644 index 0000000..db1809c --- /dev/null +++ b/Back-End/GestionInscriptions/pagination.py @@ -0,0 +1,20 @@ +from rest_framework.pagination import PageNumberPagination + +from N3wtSchool import settings + +class CustomPagination(PageNumberPagination): + page_size_query_param = 'page_size' + max_page_size = settings.NB_MAX_PAGE + page_size = settings.NB_RESULT_PER_PAGE + + def get_paginated_response(self, data): + return ({ + 'links': { + 'next': self.get_next_link(), + 'previous': self.get_previous_link() + }, + 'count': self.page.paginator.count, + 'page_size': self.page_size, + 'max_page_size' : self.max_page_size, + 'fichesInscriptions': data } + ) \ No newline at end of file diff --git a/Back-End/GestionInscriptions/serializers.py b/Back-End/GestionInscriptions/serializers.py new file mode 100644 index 0000000..fbcd6e4 --- /dev/null +++ b/Back-End/GestionInscriptions/serializers.py @@ -0,0 +1,176 @@ +from rest_framework import serializers +from GestionInscriptions.models import FicheInscription, Eleve, Responsable, Frere, Langue +from GestionLogin.models import Profil +from GestionLogin.serializers import ProfilSerializer +from GestionMessagerie.models import Messagerie +from GestionNotification.models import Notification +from N3wtSchool import settings +from django.utils import timezone +import pytz + +class LanguesSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: + model = Langue + fields = '__all__' + +class FrereSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: + model = Frere + fields = '__all__' + +class ResponsableSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + profil_associe = serializers.SerializerMethodField() + class Meta: + model = Responsable + fields = '__all__' + + def get_profil_associe(self, obj): + return obj.profilAssocie.email + +class EleveSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + responsables = ResponsableSerializer(many=True, required=False) + freres = FrereSerializer(many=True, required=False) + langues = LanguesSerializer(many=True, required=False) + class Meta: + model = Eleve + fields = '__all__' + + def get_or_create_packages(self, responsables_data): + responsables_ids = [] + for responsable_data in responsables_data: + responsable_instance, created = Responsable.objects.get_or_create( id=responsable_data.get('id'), + defaults=responsable_data) + responsables_ids.append(responsable_instance.id) + return responsables_ids + + def create(self, validated_data): + responsables_data = validated_data.pop('responsables', []) + freres_data = validated_data.pop('freres', []) + langues_data = validated_data.pop('languesParlees', []) + eleve = Eleve.objects.create(**validated_data) + eleve.responsables.set(self.get_or_create_packages(responsables_data)) + eleve.freres.set(self.get_or_create_packages(freres_data)) + eleve.languesParlees.set(self.get_or_create_packages(langues_data)) + + return eleve + + def create_or_update_packages(self, responsables_data): + responsables_ids = [] + + + for responsable_data in responsables_data: + responsable_instance, created = Responsable.objects.update_or_create( id=responsable_data.get('id'), + defaults=responsable_data) + + responsables_ids.append(responsable_instance.id) + return responsables_ids + + def update(self, instance, validated_data): + responsables_data = validated_data.pop('responsables', []) + freres_data = validated_data.pop('freres', []) + langues_data = validated_data.pop('languesParlees', []) + if responsables_data: + instance.responsables.set(self.create_or_update_packages(responsables_data)) + if freres_data: + instance.freres.set(self.create_or_update_packages(freres_data)) + if langues_data: + instance.freres.set(self.create_or_update_packages(langues_data)) + + for field in self.fields: + try: + setattr(instance, field, validated_data[field]) + except KeyError: + pass + instance.save() + + return instance + +class FicheInscriptionSerializer(serializers.ModelSerializer): + eleve = EleveSerializer(many=False, required=True) + fichierInscription = serializers.FileField(required=False) + etat_label = serializers.SerializerMethodField() + dateMAJ_formattee = serializers.SerializerMethodField() + class Meta: + model = FicheInscription + fields = '__all__' + + def create(self, validated_data): + eleve_data = validated_data.pop('eleve') + eleve = EleveSerializer.create(EleveSerializer(), eleve_data) + ficheEleve = FicheInscription.objects.create(eleve=eleve, **validated_data) + return ficheEleve + + def update(self, instance, validated_data): + eleve_data = validated_data.pop('eleve') + eleve = instance.eleve + eleve_serializer = EleveSerializer.update(EleveSerializer(), eleve, eleve_data) + + for field in self.fields: + try: + setattr(instance, field, validated_data[field]) + except KeyError: + pass + instance.save() + + return instance + + def get_etat_label(self, obj): + return obj.get_etat_display() + + def get_dateMAJ_formattee(self, obj): + utc_time = timezone.localtime(obj.dateMAJ) # Convertir en heure locale + local_tz = pytz.timezone(settings.TZ_APPLI) + local_time = utc_time.astimezone(local_tz) + + return local_time.strftime("%d-%m-%Y %H:%M") + +class EleveByParentSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: + model = Eleve + fields = ['id', 'nom', 'prenom'] + + def __init__(self, *args, **kwargs): + super(EleveByParentSerializer , self).__init__(*args, **kwargs) + for field in self.fields: + self.fields[field].required = False + +class FicheInscriptionByParentSerializer(serializers.ModelSerializer): + eleve = EleveByParentSerializer(many=False, required=True) + class Meta: + model = FicheInscription + fields = ['eleve', 'etat'] + + def __init__(self, *args, **kwargs): + super(FicheInscriptionByParentSerializer, self).__init__(*args, **kwargs) + for field in self.fields: + self.fields[field].required = False + +class ResponsableByDICreationSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: + model = Responsable + fields = ['id', 'nom', 'prenom', 'mail', 'profilAssocie'] + +class EleveByDICreationSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + responsables = ResponsableByDICreationSerializer(many=True, required=False) + class Meta: + model = Eleve + fields = ['id', 'nom', 'prenom', 'responsables'] + + def __init__(self, *args, **kwargs): + super(EleveByDICreationSerializer , self).__init__(*args, **kwargs) + for field in self.fields: + self.fields[field].required = False + +class NotificationSerializer(serializers.ModelSerializer): + typeNotification_label = serializers.ReadOnlyField() + class Meta: + model = Notification + fields = '__all__' + \ No newline at end of file diff --git a/Back-End/GestionInscriptions/signals.py b/Back-End/GestionInscriptions/signals.py new file mode 100644 index 0000000..b1d2bcc --- /dev/null +++ b/Back-End/GestionInscriptions/signals.py @@ -0,0 +1,43 @@ +from django.db.models.signals import post_save, post_delete, m2m_changed +from django.dispatch import receiver +from django.core.cache import cache + +from GestionInscriptions.models import FicheInscription, Eleve, Responsable +from GestionLogin.models import Profil +from N3wtSchool import settings +from N3wtSchool.redis_client import redis_client + +def clear_cache(): + # Préfixes des clés à supprimer + prefixes = ['N3WT_'] + + for prefix in prefixes: + # Utiliser le motif pour obtenir les clés correspondant au préfixe + pattern = f'*{prefix}*' + print(f'pattern : {pattern}') + for key in redis_client.scan_iter(pattern): + redis_client.delete(key) + print(f'deleting : {key}') + +@receiver(post_save, sender=FicheInscription) +@receiver(post_delete, sender=FicheInscription) +def clear_cache_after_change(sender, instance, **kwargs): + clear_cache() + +@receiver(m2m_changed, sender=Eleve.responsables.through) +def check_orphan_reponsables(sender, **kwargs): + action = kwargs.pop('action', None) + instance = kwargs.pop('instance', None) + # pre_clear : lors de la suppression d'une FI (on fait un "clear" sur chaque relation) + if action in ('post_remove', 'post_clear'): + if instance.responsables.all(): + Responsable.objects.filter(eleve=None).delete() + +@receiver(m2m_changed, sender=Eleve.profils.through) +def check_orphan_profils(sender, **kwargs): + action = kwargs.pop('action', None) + instance = kwargs.pop('instance', None) + # pre_clear : lors de la suppression d'une FI (on fait un "clear" sur chaque relation) + if action in ('post_remove', 'post_clear'): + if instance.profils.all(): + Profil.objects.filter(eleve=None).delete() diff --git a/Back-End/GestionInscriptions/tasks.py b/Back-End/GestionInscriptions/tasks.py new file mode 100644 index 0000000..cae2266 --- /dev/null +++ b/Back-End/GestionInscriptions/tasks.py @@ -0,0 +1,44 @@ +# tasks.py +from celery import shared_task +from django.utils import timezone +from GestionInscriptions.automate import Automate_DI_Inscription, updateStateMachine +from .models import FicheInscription +from GestionMessagerie.models import Messagerie +from N3wtSchool import settings, bdd +import requests +import logging +logger = logging.getLogger(__name__) +logger.setLevel(logging.WARNING) + +@shared_task +def check_for_signature_deadlines(): + now = timezone.now() + deadline = now - timezone.timedelta(days=settings.EXPIRATION_DI_NB_DAYS) + # deadline = now - timezone.timedelta(seconds=settings.EXPIRATION_DI_NB_DAYS) + + dossiers_en_attente = FicheInscription.objects.filter(etat=FicheInscription.EtatDossierInscription.DI_ENVOYE, dateMAJ__lt=deadline) + + for dossier in dossiers_en_attente: + send_notification(dossier) + +def send_notification(dossier): + print(f'Dossier en attente.... {dossier} - Positionnement à l\'état A_RELANCER') + + # Changer l'état de l'automate + updateStateMachine(dossier, 'relanceDI') + + url = settings.URL_DJANGO + 'GestionMessagerie/message' + + destinataires = dossier.eleve.profils.all() + for destinataire in destinataires: + message = { + "objet": "[RELANCE]", + "destinataire" : destinataire.id, + "corpus": "RELANCE pour le dossier d'inscription" + } + + response = requests.post(url, json=message) + + # subject = f"Dossier d'inscription non signé - {dossier.objet}" + # message = f"Le dossier d'inscription avec l'objet '{dossier.objet}' n'a pas été signé depuis {dossier.created_at}." + # send_mail(subject, message, settings.EMAIL_HOST_USER, [dossier.destinataire.email]) diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/ajouterFicheEleve.html b/Back-End/GestionInscriptions/templates/GestionInscriptions/ajouterFicheEleve.html new file mode 100644 index 0000000..b2b5a97 --- /dev/null +++ b/Back-End/GestionInscriptions/templates/GestionInscriptions/ajouterFicheEleve.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% load rest_framework %} +{% block content %} +

Création d'une nouvelle fiche d'inscription

+
+
+ {% csrf_token %} + + +
+ +
+{% endblock %} \ No newline at end of file diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/configure.html b/Back-End/GestionInscriptions/templates/GestionInscriptions/configure.html new file mode 100644 index 0000000..c719143 --- /dev/null +++ b/Back-End/GestionInscriptions/templates/GestionInscriptions/configure.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% block content %} +

Configuration des dossiers d'inscriptions

+
+
+ {% csrf_token %} + + +
+{% endblock %} \ No newline at end of file diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/creationDossier.html b/Back-End/GestionInscriptions/templates/GestionInscriptions/creationDossier.html new file mode 100644 index 0000000..50f3656 --- /dev/null +++ b/Back-End/GestionInscriptions/templates/GestionInscriptions/creationDossier.html @@ -0,0 +1,162 @@ +{% extends "base.html" %} +{% block content %} +

Création du dossier d'inscription

+
+
+ {% csrf_token %} + {% with responsable=eleve.getResponsablePrincipal %} + + + {% endwith %} +
+{% endblock %} \ No newline at end of file diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/editStudent.html b/Back-End/GestionInscriptions/templates/GestionInscriptions/editStudent.html new file mode 100644 index 0000000..f33652e --- /dev/null +++ b/Back-End/GestionInscriptions/templates/GestionInscriptions/editStudent.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} +{% block content %} +

Edition d'une fiche d'inscription

+
+
+ {% csrf_token %} + {% with responsable=eleve.getResponsablePrincipal %} + + + {% endwith %} +
+{% endblock %} \ No newline at end of file diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/index.html b/Back-End/GestionInscriptions/templates/GestionInscriptions/index.html new file mode 100644 index 0000000..c428315 --- /dev/null +++ b/Back-End/GestionInscriptions/templates/GestionInscriptions/index.html @@ -0,0 +1,126 @@ +{% extends "base.html" %} +{% load myTemplateTag %} + +{% block content %} +

Inscriptions 2024/2025

+
+
+ +
+
+
+ +
+ +
+
+ + + + +
+
+ + Ajouter + +
+
+
+ {% for letter in "*ABCDEFGHIJKLMNOPQRSTUVWXYZ" %} + {{ letter }} + {% endfor %} +
+
+ + + + + + + + + + + + + + + + + {% for ficheInscription in ficheInscriptions_list %} + {% with eleve=ficheInscription.eleve %} + {% with responsable=eleve.getResponsablePrincipal %} + {% with fichiers=ficheInscription|recupereFichiersDossierInscription %} + + + + + + + + + + + {% endwith %} + {% endwith %} + {% endwith %} + {% endfor %} + + + + + + + + +
NomPrenomMailTéléphoneMàJ LeStatutFichiersAction
{{ eleve.nom }}{{ eleve.prenom }}{{ responsable.mail }}{{ responsable.telephone }}{{ ficheInscription.dateMAJ }} + {% if ficheInscription.etat == 0 %} + Créé + {% elif ficheInscription.etat == 1 %} + Envoyé + {% elif ficheInscription.etat == 2 %} + En Validation + {% else %} + Validé + {% endif %} + + {% for fichier in fichiers %} + {{ fichier.nom }} + {% endfor %} + + + + +
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/Back-End/GestionInscriptions/templates/base.html b/Back-End/GestionInscriptions/templates/base.html new file mode 100644 index 0000000..bd5eb72 --- /dev/null +++ b/Back-End/GestionInscriptions/templates/base.html @@ -0,0 +1,31 @@ +{% load static %} + + + + + + + + Monteschool + + + +
+{% block content %} +{% endblock %} +
+ + \ No newline at end of file diff --git a/Back-End/GestionInscriptions/templates/pdfs/dossier_inscription.html b/Back-End/GestionInscriptions/templates/pdfs/dossier_inscription.html new file mode 100644 index 0000000..b083a38 --- /dev/null +++ b/Back-End/GestionInscriptions/templates/pdfs/dossier_inscription.html @@ -0,0 +1,97 @@ + + + + + {{ pdf_title }} + + + + {% load myTemplateTag %} +
+
+

{{ pdf_title }}

+
+
+
+ Signé le : {{ dateSignature }}
+ A : {{ heureSignature }} +
+

ELEVE

+ {% with niveau=eleve|recupereNiveauEleve %} + {% with genre=eleve|recupereGenreEleve %} + NOM : {{ eleve.nom }}
+ PRENOM : {{ eleve.prenom }}
+ ADRESSE : {{ eleve.adresse }}
+ GENRE : {{ genre }}
+ NE(E) LE : {{ eleve.dateNaissance }}
+ A : {{ eleve.lieuNaissance }} ({{ eleve.codePostalNaissance }})
+ NATIONALITE : {{ eleve.nationalite }}
+ NIVEAU : {{ niveau }}
+ MEDECIN TRAITANT : {{ eleve.medecinTraitant }}
+ {% endwith %} + {% endwith %} +
+

RESPONSABLES

+ {% with responsables_List=eleve.getResponsables %} + {% with freres_List=eleve.getFreres %} + {% for responsable in responsables_List%} +

Responsable {{ forloop.counter }}

+ NOM : {{ responsable.nom }}
+ PRENOM : {{ responsable.prenom }}
+ ADRESSE : {{ responsable.adresse }}
+ NE(E) LE : {{ responsable.dateNaissance }}
+ MAIL : {{ responsable.mail }}
+ TEL : {{ responsable.telephone }}
+ PROFESSION : {{ responsable.profession }}
+ {% endfor %} +
+

FRATRIE

+ {% for frere in freres_List%} +

Frère - Soeur {{ forloop.counter }}

+ NOM : {{ frere.nom }}
+ PRENOM : {{ frere.prenom }}
+ NE(E) LE : {{ frere.dateNaissance }}
+ {% endfor %} +
+

MODALITES DE PAIEMENT

+ {% with modePaiement=eleve|recupereModePaiement %} + {{ modePaiement }}
+ {% endwith %} + {% endwith %} + {% endwith %} +
+
+ + \ No newline at end of file diff --git a/Back-End/GestionInscriptions/templatetags/myTemplateTag.py b/Back-End/GestionInscriptions/templatetags/myTemplateTag.py new file mode 100644 index 0000000..094cf28 --- /dev/null +++ b/Back-End/GestionInscriptions/templatetags/myTemplateTag.py @@ -0,0 +1,23 @@ +from GestionInscriptions.models import FicheInscription, Eleve +from django import template +register = template.Library() + +# @register.filter +# def recupereFichiersDossierInscription(pk): + # fichiers_list = FicheInscription.objects.filter(fiche_inscription=pk) + # return fichiers_list + +@register.filter +def recupereModePaiement(pk): + ficheInscription = FicheInscription.objects.get(eleve=pk) + return Eleve.ModePaiement(int(ficheInscription.eleve.modePaiement)).label + +@register.filter +def recupereNiveauEleve(pk): + ficheInscription = FicheInscription.objects.get(eleve=pk) + return Eleve.NiveauEleve(int(ficheInscription.eleve.niveau)).label + +@register.filter +def recupereGenreEleve(pk): + ficheInscription = FicheInscription.objects.get(eleve=pk) + return Eleve.GenreEleve(int(ficheInscription.eleve.genre)).label \ No newline at end of file diff --git a/Back-End/GestionInscriptions/urls.py b/Back-End/GestionInscriptions/urls.py new file mode 100644 index 0000000..4fd699e --- /dev/null +++ b/Back-End/GestionInscriptions/urls.py @@ -0,0 +1,31 @@ +from django.urls import path, re_path + +from . import views +from GestionInscriptions.views import ListFichesInscriptionView, FicheInscriptionView, EleveView, ResponsableView, ListeEnfantsView, ListeElevesView + +urlpatterns = [ + re_path(r'^fichesInscription/([a-zA-z]+)$', ListFichesInscriptionView.as_view(), name="listefichesInscriptions"), + re_path(r'^ficheInscription$', FicheInscriptionView.as_view(), name="fichesInscriptions"), + re_path(r'^ficheInscription/([0-9]+)$', FicheInscriptionView.as_view(), name="fichesInscriptions"), + + # Page de formulaire d'inscription - ELEVE + re_path(r'^eleve/([0-9]+)$', EleveView.as_view(), name="eleves"), + + # Page de formulaire d'inscription - RESPONSABLE + re_path(r'^recupereDernierResponsable$', ResponsableView.as_view(), name="recupereDernierResponsable"), + + # Envoi d'un dossier d'inscription + re_path(r'^send/([0-9]+)$', views.send, name="send"), + + # Archivage d'un dossier d'inscription + re_path(r'^archive/([0-9]+)$', views.archive, name="archive"), + + # Envoi d'une relance de dossier d'inscription + re_path(r'^sendRelance/([0-9]+)$', views.relance, name="relance"), + + # Page PARENT - Liste des enfants + re_path(r'^enfants/([0-9]+)$', ListeEnfantsView.as_view(), name="enfants"), + + # Page INSCRIPTION - Liste des élèves + re_path(r'^eleves$', ListeElevesView.as_view(), name="enfants"), +] \ No newline at end of file diff --git a/Back-End/GestionInscriptions/util.py b/Back-End/GestionInscriptions/util.py new file mode 100644 index 0000000..45be230 --- /dev/null +++ b/Back-End/GestionInscriptions/util.py @@ -0,0 +1,181 @@ +from django.shortcuts import render,get_object_or_404,get_list_or_404 +from .models import FicheInscription, Eleve, Responsable, Frere +import time +from datetime import date, datetime, timedelta +from zoneinfo import ZoneInfo +from django.conf import settings +from N3wtSchool import renderers +from N3wtSchool import bdd + +from io import BytesIO +from django.core.files import File +from pathlib import Path +import os +from enum import Enum + +import random +import string +from rest_framework.parsers import JSONParser + +def recupereListeFichesInscription(): + context = { + "ficheInscriptions_list": bdd.getAllObjects(FicheInscription), + } + return context + +def recupereListeFichesInscriptionEnAttenteSEPA(): + + ficheInscriptionsSEPA_list = FicheInscription.objects.filter(modePaiement="Prélèvement SEPA").filter(etat=FicheInscription.EtatDossierInscription['SEPA_ENVOYE']) + return ficheInscriptionsSEPA_list + +def updateEleve(eleve, inputs, erase=False): + eleve.nom = inputs["nomEleve"] + eleve.prenom = inputs["prenomEleve"] + eleve.ambiance = inputs["ambiance"] + eleve.genre = inputs["genre"] + eleve.adresse = inputs["adresseEleve"] + eleve.dateNaissance = inputs["dateNaissanceEleve"] + eleve.lieuNaissance = inputs["lieuNaissanceEleve"] + eleve.codePostalNaissance = inputs["codePostalNaissanceEleve"] + eleve.nationalite = inputs["nationaliteEleve"] + eleve.medecinTraitant = inputs["medecinTraitantEleve"] + + + responsable=eleve.getResponsablePrincipal() + responsable.adresse = inputs["adresseResponsable1"] + responsable.dateNaissance = inputs["dateNaissanceResponsable1"] + responsable.profession = inputs["professionResponsable1"] + responsable.save() + + # Création du 2ème responsable + if inputs["nomResponsable2"] != "" and inputs["prenomResponsable2"] != "": + responsable2 = Responsable.objects.create(nom=inputs["nomResponsable2"], + prenom=inputs["prenomResponsable2"], + dateNaissance=inputs["dateNaissanceResponsable2"], + adresse=inputs["adresseResponsable2"], + mail=inputs["mailResponsable2"], + telephone=inputs["telephoneResponsable2"], + profession=inputs["professionResponsable2"]) + responsable2.save() + eleve.responsables.add(responsable2) + + # Création du 1er frère + if inputs["nomFrere1"] != "" and inputs["prenomFrere1"] != "": + frere1 = Frere.objects.create(nom=inputs["nomFrere1"], + prenom=inputs["prenomFrere1"], + dateNaissance=inputs["dateNaissanceFrere1"]) + frere1.save() + eleve.freres.add(frere1) + + # Création du 2ème frère + if inputs["nomFrere2"] != "" and inputs["prenomFrere2"] != "": + frere2 = Frere.objects.create(nom=inputs["nomFrere2"], + prenom=inputs["prenomFrere2"], + dateNaissance=inputs["dateNaissanceFrere2"]) + frere2.save() + eleve.freres.add(frere2) + + eleve.save() + +def _now(): + return datetime.now(ZoneInfo(settings.TZ_APPLI)) + +def convertToStr(dateValue, dateFormat): + return dateValue.strftime(dateFormat) + +def convertToDate(date_time): + format = '%d-%m-%Y %H:%M' + datetime_str = datetime.strptime(date_time, format) + + return datetime_str + +def convertTelephone(telephoneValue, separator='-'): + return f"{telephoneValue[:2]}{separator}{telephoneValue[2:4]}{separator}{telephoneValue[4:6]}{separator}{telephoneValue[6:8]}{separator}{telephoneValue[8:10]}" + +def generePDF(ficheEleve): + data = { + 'pdf_title': "Dossier d'inscription de %s"%ficheEleve.eleve.prenom, + 'dateSignature': convertToStr(_now(), '%d-%m-%Y'), + 'heureSignature': convertToStr(_now(), '%H:%M'), + 'eleve':ficheEleve.eleve, + } + + pdf = renderers.render_to_pdf('pdfs/dossier_inscription.html', data) + + nomFichierPDF = "Dossier_Inscription_%s_%s.pdf"%(ficheEleve.eleve.nom, ficheEleve.eleve.prenom) + pathFichier = Path(settings.DOCUMENT_DIR + "/" + nomFichierPDF) + if os.path.exists(str(pathFichier)): + os.remove(str(pathFichier)) + + receipt_file = BytesIO(pdf.content) + # fichier = Fichier.objects.create(fiche_inscription=ficheEleve) + # fichier.document = File(receipt_file, nomFichierPDF) + # fichier.save() + +def genereRandomCode(length): + return ''.join(random.choice(string.ascii_letters) for i in range(length)) + +def calculeDatePeremption(_start, nbDays): + return convertToStr(_start + timedelta(days=nbDays), settings.DATE_FORMAT) + +# Fonction permettant de retourner la valeur du QueryDict +# QueryDict [ index ] -> Dernière valeur d'une liste +# dict (QueryDict [ index ]) -> Toutes les valeurs de la liste +def _(liste): + return liste[0] + +def toNewEleveJSONRequest(jsonOrigin): + etat=FicheInscription.EtatDossierInscription.DI_CREE + telephone = convertTelephone(_(jsonOrigin['telephoneResponsable'])) + finalJSON = { + "eleve": + { + "nom" : _(jsonOrigin['nomEleve']), + "prenom" : _(jsonOrigin['prenomEleve']), + "responsables" : [ + { + "nom" : _(jsonOrigin['nomResponsable']), + "prenom" : _(jsonOrigin['prenomResponsable']), + "mail" : _(jsonOrigin['mailResponsable']), + "telephone" : telephone + } + ], + "profils" : [ + ], + }, + "etat": str(etat), + "dateMAJ": str(convertToStr(_now(), '%d-%m-%Y %H:%M')), + } + print(finalJSON) + return finalJSON + +def toEditEleveJSONRequest(jsonOrigin): + telephone = convertTelephone(_(jsonOrigin['telephoneResponsable']), '.') + finalJSON = { + "eleve": + { + "id" : _(jsonOrigin['fiche_id']), + "nom" : _(jsonOrigin['nomEleve']), + "prenom" : _(jsonOrigin['prenomEleve']), + "responsables" : [ + { + "id" : _(jsonOrigin['responsable_id']), + "nom" : _(jsonOrigin['nomResponsable']), + "prenom" : _(jsonOrigin['prenomResponsable']), + "mail" : _(jsonOrigin['mailResponsable']), + "telephone" : telephone + } + ], + "profils" : [ + ], + }, + "dateMAJ": str(convertToStr(_now(), '%d-%m-%Y %H:%M')), + } + print(finalJSON) + return finalJSON + +def getArgFromRequest(_argument, _request): + resultat = None + data=JSONParser().parse(_request) + resultat = data[_argument] + return resultat diff --git a/Back-End/GestionInscriptions/views.py b/Back-End/GestionInscriptions/views.py new file mode 100644 index 0000000..410edac --- /dev/null +++ b/Back-End/GestionInscriptions/views.py @@ -0,0 +1,289 @@ +from django.http.response import JsonResponse +from django.contrib.auth import login, authenticate, get_user_model +from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect +from django.utils.decorators import method_decorator +from django.core.cache import cache +from django.core.paginator import Paginator +from django.core.files import File +from django.db.models import Q # Ajout de cet import +from rest_framework.parsers import JSONParser +from rest_framework.views import APIView +from rest_framework import status + +import json +from pathlib import Path +import os +from io import BytesIO + +import GestionInscriptions.mailManager as mailer +import GestionInscriptions.util as util +from GestionInscriptions.serializers import FicheInscriptionSerializer, EleveSerializer, FicheInscriptionByParentSerializer, EleveByDICreationSerializer +from GestionInscriptions.pagination import CustomPagination +from GestionInscriptions.signals import clear_cache +from .models import Eleve, Responsable, FicheInscription +from GestionInscriptions.automate import Automate_DI_Inscription, load_config, getStateMachineObjectState, updateStateMachine + +from GestionLogin.models import Profil + +from N3wtSchool import settings, renderers, bdd + +class ListFichesInscriptionView(APIView): + pagination_class = CustomPagination + + def get(self, request, _filter): + if _filter == 'all': + # Récupération des paramètres + search = request.GET.get('search', '').strip() + page_size = request.GET.get('page_size', None) + + # Gestion du page_size + if page_size is not None: + try: + page_size = int(page_size) + except ValueError: + page_size = settings.NB_RESULT_PER_PAGE + + cached_page_size = cache.get('N3WT_page_size') + if cached_page_size != page_size: + clear_cache() + cache.set('N3WT_page_size', page_size) + + # Gestion du cache + page_number = request.GET.get('page', 1) + cache_key = f'N3WT_ficheInscriptions_page_{page_number}_search_{search}' + cached_page = cache.get(cache_key) + if cached_page: + return JsonResponse(cached_page, safe=False) + + # Filtrage des résultats + if search: + # Utiliser la nouvelle fonction de recherche + ficheInscriptions_List = bdd.searchObjects( + FicheInscription, + search, + _excludeState=6 # Exclure les fiches archivées + ) + else: + # Récupère toutes les fiches non archivées + ficheInscriptions_List = bdd.getObjects(FicheInscription, 'etat', 6, _reverseCondition=True) + + # Pagination + paginator = self.pagination_class() + page = paginator.paginate_queryset(ficheInscriptions_List, request) + if page is not None: + ficheInscriptions_serializer = FicheInscriptionSerializer(page, many=True) + response_data = paginator.get_paginated_response(ficheInscriptions_serializer.data) + cache.set(cache_key, response_data, timeout=60*15) + return JsonResponse(response_data, safe=False) + + elif _filter == 'archived' : + page_size = request.GET.get('page_size', None) + if page_size is not None: + try: + page_size = int(page_size) + except ValueError: + page_size = settings.NB_RESULT_PER_PAGE + + cached_page_size = cache.get('N3WT_archived_page_size') + + # Comparer avec le nouveau page_size + if cached_page_size != page_size: + # Appeler cached_page() et mettre à jour le cache + clear_cache() + cache.set('N3WT_archived_page_size',page_size) + + page_number = request.GET.get('page', 1) + cache_key_page = f'N3WT_ficheInscriptions_archives_page_{page_number}' + cached_page = cache.get(cache_key_page) + if cached_page: + return JsonResponse(cached_page, safe=False) + + ficheInscriptions_List=bdd.getObjects(FicheInscription, 'etat', 6) + paginator = self.pagination_class() + page = paginator.paginate_queryset(ficheInscriptions_List, request) + if page is not None: + ficheInscriptions_serializer = FicheInscriptionSerializer(page, many=True) + response_data = paginator.get_paginated_response(ficheInscriptions_serializer.data) + cache.set(cache_key_page, response_data, timeout=60*15) + + return JsonResponse(response_data, safe=False) + + return JsonResponse(status=status.HTTP_404_NOT_FOUND) + + def post(self, request): + fichesEleve_data=JSONParser().parse(request) + for ficheEleve_data in fichesEleve_data: + # Ajout de la date de mise à jour + ficheEleve_data["dateMAJ"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + json.dumps(ficheEleve_data) + # Ajout du code d'inscription + code = util.genereRandomCode(12) + ficheEleve_data["codeLienInscription"] = code + ficheEleve_serializer = FicheInscriptionSerializer(data=ficheEleve_data) + + if ficheEleve_serializer.is_valid(): + ficheEleve_serializer.save() + + return JsonResponse(ficheEleve_serializer.errors, safe=False) + + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class FicheInscriptionView(APIView): + pagination_class = CustomPagination + + def get(self, request, _id): + ficheInscription=bdd.getObject(FicheInscription, "eleve__id", _id) + fiche_serializer=FicheInscriptionSerializer(ficheInscription) + return JsonResponse(fiche_serializer.data, safe=False) + + def post(self, request): + ficheEleve_data=JSONParser().parse(request) + # Ajout de la date de mise à jour + ficheEleve_data["dateMAJ"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + json.dumps(ficheEleve_data) + # Ajout du code d'inscription + code = util.genereRandomCode(12) + ficheEleve_data["codeLienInscription"] = code + + responsablesId = ficheEleve_data.pop('idResponsables', []) + ficheEleve_serializer = FicheInscriptionSerializer(data=ficheEleve_data) + + if ficheEleve_serializer.is_valid(): + di = ficheEleve_serializer.save() + + # Mise à jour de l'automate + updateStateMachine(di, 'creationDI') + + # Récupération du reponsable associé + for responsableId in responsablesId: + responsable = Responsable.objects.get(id=responsableId) + di.eleve.responsables.add(responsable) + di.save() + + ficheInscriptions_List=bdd.getAllObjects(FicheInscription) + return JsonResponse({'totalInscrits':len(ficheInscriptions_List)}, safe=False) + + return JsonResponse(ficheEleve_serializer.errors, safe=False) + + def put(self, request, id): + ficheEleve_data=JSONParser().parse(request) + admin = ficheEleve_data.pop('admin', 1) + ficheEleve_data["dateMAJ"] = str(util.convertToStr(util._now(), '%d-%m-%Y %H:%M')) + ficheEleve = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) + currentState = getStateMachineObjectState(ficheEleve.etat) + if admin == 0 and currentState == FicheInscription.EtatDossierInscription.DI_ENVOYE: + json.dumps(ficheEleve_data) + + # Ajout du fichier d'inscriptions + data = { + 'pdf_title': "Dossier d'inscription de %s"%ficheEleve.eleve.prenom, + 'dateSignature': util.convertToStr(util._now(), '%d-%m-%Y'), + 'heureSignature': util.convertToStr(util._now(), '%H:%M'), + 'eleve':ficheEleve.eleve, + } + + pdf = renderers.render_to_pdf('pdfs/dossier_inscription.html', data) + + nomFichierPDF = "Dossier_Inscription_%s_%s.pdf"%(ficheEleve.eleve.nom, ficheEleve.eleve.prenom) + pathFichier = Path(settings.DOCUMENT_DIR + "/" + nomFichierPDF) + if os.path.exists(str(pathFichier)): + print(f'File exists : {str(pathFichier)}') + os.remove(str(pathFichier)) + + receipt_file = BytesIO(pdf.content) + ficheEleve.fichierInscription = File(receipt_file, nomFichierPDF) + + # Mise à jour de l'automate + updateStateMachine(di, 'saisiDI') + + ficheEleve_serializer = FicheInscriptionSerializer(ficheEleve, data=ficheEleve_data) + if ficheEleve_serializer.is_valid(): + di = ficheEleve_serializer.save() + return JsonResponse("Updated Successfully", safe=False) + + return JsonResponse(ficheEleve_serializer.errors, safe=False) + + def delete(self, request, id): + fiche_inscription = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) + if fiche_inscription != None: + eleve = fiche_inscription.eleve + eleve.responsables.clear() + eleve.profils.clear() + eleve.delete() + clear_cache() + + return JsonResponse("La suppression du dossier a été effectuée avec succès", safe=False) + + return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) + +class EleveView(APIView): + def get(self, request, _id): + eleve = bdd.getObject(_objectName=Eleve, _columnName='id', _value=_id) + eleve_serializer = EleveSerializer(eleve) + return JsonResponse(eleve_serializer.data, safe=False) + +class ResponsableView(APIView): + def get(self, request): + lastResponsable = bdd.getLastId(Responsable) + return JsonResponse({"lastid":lastResponsable}, safe=False) + +def send(request, id): + fiche_inscription = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) + if fiche_inscription != None: + eleve = fiche_inscription.eleve + responsable = eleve.getResponsablePrincipal() + mail = responsable.mail + errorMessage = mailer.envoieDossierInscription(mail) + if errorMessage == '': + fiche_inscription.dateMAJ=util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + # Mise à jour de l'automate + updateStateMachine(fiche_inscription, 'envoiDI') + + return JsonResponse({"errorMessage":errorMessage}, safe=False) + + return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) + +def archive(request, id): + fiche_inscription = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) + if fiche_inscription != None: + fiche_inscription.dateMAJ=util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + # Mise à jour de l'automate + updateStateMachine(fiche_inscription, 'archiveDI') + + return JsonResponse({"errorMessage":''}, safe=False) + + return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) + +def relance(request, id): + fiche_inscription = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) + if fiche_inscription != None: + eleve = fiche_inscription.eleve + responsable = eleve.getResponsablePrincipal() + mail = responsable.mail + errorMessage = mailer.envoieRelanceDossierInscription(mail, fiche_inscription.codeLienInscription) + if errorMessage == '': + fiche_inscription.etat=FicheInscription.EtatDossierInscription.DI_ENVOYE + fiche_inscription.dateMAJ=util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + fiche_inscription.save() + + return JsonResponse({"errorMessage":errorMessage}, safe=False) + + return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) + +# API utilisée pour la vue parent +class ListeEnfantsView(APIView): + # Récupération des élèves d'un parent + # idProfile : identifiant du profil connecté rattaché aux fiches d'élèves + def get(self, request, _idProfile): + students = bdd.getObjects(_objectName=FicheInscription, _columnName='eleve__responsables__profilAssocie__id', _value=_idProfile) + students_serializer = FicheInscriptionByParentSerializer(students, many=True) + return JsonResponse(students_serializer.data, safe=False) + +# API utilisée pour la vue de création d'un DI +class ListeElevesView(APIView): + # Récupération de la liste des élèves inscrits ou en cours d'inscriptions + def get(self, request): + students = bdd.getAllObjects(_objectName=Eleve) + students_serializer = EleveByDICreationSerializer(students, many=True) + return JsonResponse(students_serializer.data, safe=False) diff --git a/Back-End/GestionLogin/__init__.py b/Back-End/GestionLogin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Back-End/GestionLogin/admin.py b/Back-End/GestionLogin/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Back-End/GestionLogin/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Back-End/GestionLogin/apps.py b/Back-End/GestionLogin/apps.py new file mode 100644 index 0000000..8aa6c1e --- /dev/null +++ b/Back-End/GestionLogin/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.db.models.signals import post_migrate + +class GestionloginConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'GestionLogin' + diff --git a/Back-End/GestionLogin/backends.py b/Back-End/GestionLogin/backends.py new file mode 100644 index 0000000..0db862f --- /dev/null +++ b/Back-End/GestionLogin/backends.py @@ -0,0 +1,20 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend +from GestionLogin.models import Profil +from N3wtSchool import bdd + +class EmailBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + + if username is None: + username = kwargs.get(Profil.USERNAME_FIELD) + + try: + user = Profil.objects.get(email=username) + + # Vérifie le mot de passe de l'utilisateur + if user.check_password(password): + return user + except Profil.DoesNotExist: + return None + diff --git a/Back-End/GestionLogin/models.py b/Back-End/GestionLogin/models.py new file mode 100644 index 0000000..f16dcbb --- /dev/null +++ b/Back-End/GestionLogin/models.py @@ -0,0 +1,25 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django.core.validators import EmailValidator + +class Profil(AbstractUser): + class Droits(models.IntegerChoices): + PROFIL_UNDEFINED = -1, _('Profil non défini') + PROFIL_ECOLE = 0, _('Profil école') + PROFIL_PARENT = 1, _('Profil parent') + PROFIL_ADMIN = 2, _('Profil administrateur') + + email = models.EmailField(max_length=255, unique=True, default="", validators=[EmailValidator()]) + + USERNAME_FIELD = 'email' + + REQUIRED_FIELDS = ('password', ) + + code = models.CharField(max_length=200, default="", blank=True) + datePeremption = models.CharField(max_length=200, default="", blank=True) + droit = models.IntegerField(choices=Droits, default=Droits.PROFIL_UNDEFINED) + estConnecte = models.BooleanField(default=False, blank=True) + + def __str__(self): + return self.email + " - " + str(self.droit) diff --git a/Back-End/GestionLogin/serializers.py b/Back-End/GestionLogin/serializers.py new file mode 100644 index 0000000..e5653de --- /dev/null +++ b/Back-End/GestionLogin/serializers.py @@ -0,0 +1,28 @@ +from rest_framework import serializers +from GestionLogin.models import Profil +from django.core.exceptions import ValidationError + +class ProfilSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + password = serializers.CharField(write_only=True) + + class Meta: + model = Profil + fields = ['id', 'password', 'email', 'code', 'datePeremption', 'estConnecte', 'droit', 'username', 'is_active'] + extra_kwargs = {'password': {'write_only': True}} + + def create(self, validated_data): + user = Profil( + username=validated_data['username'], + email=validated_data['email'], + is_active=validated_data['is_active'], + droit=validated_data['droit'] + ) + user.set_password(validated_data['password']) + user.save() + return user + + def to_representation(self, instance): + ret = super().to_representation(instance) + ret['password'] = '********' + return ret diff --git a/Back-End/GestionLogin/templates/GestionLogin/login.html b/Back-End/GestionLogin/templates/GestionLogin/login.html new file mode 100644 index 0000000..46945c6 --- /dev/null +++ b/Back-End/GestionLogin/templates/GestionLogin/login.html @@ -0,0 +1,61 @@ +{% load static %} + + + + + + + + Monteschool + + + +
+
+ +
+

Authentification

+ +
+ + diff --git a/Back-End/GestionLogin/templates/GestionLogin/new-password.html b/Back-End/GestionLogin/templates/GestionLogin/new-password.html new file mode 100644 index 0000000..0c84b92 --- /dev/null +++ b/Back-End/GestionLogin/templates/GestionLogin/new-password.html @@ -0,0 +1,64 @@ +{% load static %} + + + + + + + Monteschool + + +
+
+ +
+

Nouveau Mot de Passe

+ +
+ + \ No newline at end of file diff --git a/Back-End/GestionLogin/templates/GestionLogin/reset-password.html b/Back-End/GestionLogin/templates/GestionLogin/reset-password.html new file mode 100644 index 0000000..299e0de --- /dev/null +++ b/Back-End/GestionLogin/templates/GestionLogin/reset-password.html @@ -0,0 +1,37 @@ +{% load static %} + + + + + + + + Monteschool + + +
+
+ +
+

Réinitialiser Mot de Passe

+ +
+ + \ No newline at end of file diff --git a/Back-End/GestionLogin/templates/GestionLogin/subscribe.html b/Back-End/GestionLogin/templates/GestionLogin/subscribe.html new file mode 100644 index 0000000..a216d13 --- /dev/null +++ b/Back-End/GestionLogin/templates/GestionLogin/subscribe.html @@ -0,0 +1,64 @@ +{% load static %} + + + + + + + Monteschool + + +
+
+ +
+

S'inscrire

+ +
+ + \ No newline at end of file diff --git a/Back-End/GestionLogin/urls.py b/Back-End/GestionLogin/urls.py new file mode 100644 index 0000000..d0f29b7 --- /dev/null +++ b/Back-End/GestionLogin/urls.py @@ -0,0 +1,22 @@ +from django.urls import path, re_path + +from . import views +import GestionLogin.views +from GestionLogin.views import ProfilView, ListProfilView, SessionView, LoginView, SubscribeView, NewPasswordView, ResetPasswordView + +urlpatterns = [ + re_path(r'^csrf$', GestionLogin.views.csrf, name='csrf'), + + re_path(r'^login$', LoginView.as_view(), name="login"), + re_path(r'^subscribe$', SubscribeView.as_view(), name='subscribe'), + re_path(r'^newPassword$', NewPasswordView.as_view(), name='newPassword'), + re_path(r'^resetPassword/([a-zA-Z]+)$', ResetPasswordView.as_view(), name='resetPassword'), + re_path(r'^infoSession$', GestionLogin.views.infoSession, name='infoSession'), + + re_path(r'^profils$', ListProfilView.as_view(), name="profil"), + re_path(r'^profil$', ProfilView.as_view(), name="profil"), + re_path(r'^profil/([0-9]+)$', ProfilView.as_view(), name="profil"), + + # Test SESSION VIEW + re_path(r'^session$', SessionView.as_view(), name="session"), +] \ No newline at end of file diff --git a/Back-End/GestionLogin/validator.py b/Back-End/GestionLogin/validator.py new file mode 100644 index 0000000..6f71da7 --- /dev/null +++ b/Back-End/GestionLogin/validator.py @@ -0,0 +1,120 @@ +from django.core.exceptions import ValidationError +from abc import ABC, abstractmethod +from N3wtSchool import bdd, error + +class Validator(ABC): + + formName="" + data = {} + errorFields={} + + @abstractmethod + def validate(self): + pass + + def getErrorFields(self): + return self.errorFields + +class ValidatorAuthentication(Validator): + + def __init__(self, **kwargs): + self.data = kwargs['data'] + self.errorFields = {'email':'','password':''} + + def __str__ (self): + return "VALIDATOR_AUTHENTICATION : " + self.data + + def validate(self): + email = self.data.get("email") + password = self.data.get("password") + + if email == None or not email: + self.errorFields['email'] = error.returnMessage[error.INCOMPLETE] + + if password == None or not password: + self.errorFields['password'] = error.returnMessage[error.INCOMPLETE] + + + return (not self.errorFields['email'] and not self.errorFields['password']), self.errorFields + + def getErrorFields(self): + return super().getErrorFields() + + +class ValidatorSubscription(Validator): + + def __init__(self, **kwargs): + self.data = kwargs['data'] + self.errorFields = {'email':'','password1':'', 'password2':''} + + def __str__ (self): + return "VALIDATOR_SUBSCRIPTION : " + self.data + + def validate(self): + email = self.data.get("email") + password1 = self.data.get("password1") + password2 = self.data.get("password2") + + if password1 != password2: + self.errorFields['password1'] = error.returnMessage[error.DIFFERENT_PASWWORD] + self.errorFields['password2'] = error.returnMessage[error.DIFFERENT_PASWWORD] + else: + if email == None or not email: + self.errorFields['email'] = error.returnMessage[error.INCOMPLETE] + if password1 == None or not password1: + self.errorFields['password1'] = error.returnMessage[error.INCOMPLETE] + if password2 == None or not password2: + self.errorFields['password2'] = error.returnMessage[error.INCOMPLETE] + + return (not self.errorFields['email'] and not self.errorFields['password1'] and not self.errorFields['password2']), self.errorFields + + def getErrorFields(self): + return super().getErrorFields() + +class ValidatorNewPassword(Validator): + + def __init__(self, **kwargs): + self.data = kwargs['data'] + self.errorFields = {'email':''} + + def __str__ (self): + return "VALIDATOR_NEW_PASSWORD : " + self.data + + def validate(self): + email = self.data.get("email") + + if email == None or not email: + self.errorFields['email'] = error.returnMessage[error.INCOMPLETE] + + return not self.errorFields['email'], self.errorFields + + def getErrorFields(self): + return super().getErrorFields() + +class ValidatorResetPassword(Validator): + + def __init__(self, **kwargs): + self.data = kwargs['data'] + self.errorFields = {'password1':'', 'password2':''} + + def __str__ (self): + return "VALIDATOR_RESET_PASSWORD : " + self.data + + def validate(self): + password1 = self.data.get("password1") + password2 = self.data.get("password2") + + if password1 != password2: + self.errorFields['password1'] = error.returnMessage[error.DIFFERENT_PASWWORD] + self.errorFields['password2'] = error.returnMessage[error.DIFFERENT_PASWWORD] + + else: + if password1 == None or not password1: + self.errorFields['password1'] = error.returnMessage[error.INCOMPLETE] + if password2 == None or not password2: + self.errorFields['password2'] = error.returnMessage[error.INCOMPLETE] + + return (not self.errorFields['password1'] and not self.errorFields['password2']), self.errorFields + + def getErrorFields(self): + return super().getErrorFields() diff --git a/Back-End/GestionLogin/views.py b/Back-End/GestionLogin/views.py new file mode 100644 index 0000000..76590cd --- /dev/null +++ b/Back-End/GestionLogin/views.py @@ -0,0 +1,264 @@ +from django.conf import settings +from django.contrib.auth import login, authenticate, get_user_model +from django.http.response import JsonResponse +from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt, csrf_protect +from django.utils.decorators import method_decorator +from django.core.exceptions import ValidationError +from django.core.cache import cache +from django.middleware.csrf import get_token +from rest_framework.views import APIView +from rest_framework.parsers import JSONParser +from rest_framework import status + +from datetime import datetime +import jwt +import json + +from . import validator +from .models import Profil + +from GestionInscriptions.models import FicheInscription +from GestionInscriptions.serializers import ProfilSerializer +from GestionInscriptions.signals import clear_cache +import GestionInscriptions.mailManager as mailer +import GestionInscriptions.util as util + +from N3wtSchool import bdd, error + +def csrf(request): + token = get_token(request) + return JsonResponse({'csrfToken': token}) + +class SessionView(APIView): + + def post(self, request): + token = request.META.get('HTTP_AUTHORIZATION', '').split('Bearer ')[-1] + + try: + decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) + print(f'decode : {decoded_token}') + user_id = decoded_token.get('id') + user = Profil.objects.get(id=user_id) + + response_data = { + 'user': { + 'id': user.id, + 'email': user.email, + 'role': user.droit, # Assure-toi que le champ 'droit' existe et contient le rôle + } + } + return JsonResponse(response_data, status=status.HTTP_200_OK) + except jwt.ExpiredSignatureError: + return JsonResponse({"error": "Token has expired"}, status=status.HTTP_401_UNAUTHORIZED) + except jwt.InvalidTokenError: + return JsonResponse({"error": "Invalid token"}, status=status.HTTP_401_UNAUTHORIZED) + +class ListProfilView(APIView): + def get(self, request): + profilsList = bdd.getAllObjects(_objectName=Profil) + profils_serializer = ProfilSerializer(profilsList, many=True) + return JsonResponse(profils_serializer.data, safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class ProfilView(APIView): + def get(self, request, _id): + profil=bdd.getObject(Profil, "id", _id) + profil_serializer=ProfilSerializer(profil) + return JsonResponse(profil_serializer.data, safe=False) + + def post(self, request): + profil_data=JSONParser().parse(request) + print(f'{profil_data}') + profil_serializer = ProfilSerializer(data=profil_data) + + if profil_serializer.is_valid(): + profil_serializer.save() + + return JsonResponse(profil_serializer.data, safe=False) + + + return JsonResponse(profil_serializer.errors, safe=False) + + def put(self, request, _id): + data=JSONParser().parse(request) + profil = Profil.objects.get(id=_id) + profil_serializer = ProfilSerializer(profil, data=data) + if profil_serializer.is_valid(): + profil_serializer.save() + return JsonResponse("Updated Successfully", safe=False) + + return JsonResponse(profil_serializer.errors, safe=False) + +def infoSession(request): + profilCache = cache.get('session_cache') + if profilCache: + return JsonResponse({"cacheSession":True,"typeProfil":profilCache.droit, "username":profilCache.email}, safe=False) + else: + return JsonResponse({"cacheSession":False,"typeProfil":Profil.Droits.PROFIL_UNDEFINED, "username":""}, safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class LoginView(APIView): + + def get(self, request): + return JsonResponse({ + 'errorFields':'', + 'errorMessage':'', + 'profil':0, + }, safe=False) + + def post(self, request): + data=JSONParser().parse(request) + validatorAuthentication = validator.ValidatorAuthentication(data=data) + retour = error.returnMessage[error.WRONG_ID] + validationOk, errorFields = validatorAuthentication.validate() + user = None + if validationOk: + user = authenticate( + email=data.get('email'), + password=data.get('password'), + ) + if user is not None: + if user.is_active: + login(request, user) + user.estConnecte = True + user.save() + clear_cache() + retour = '' + else: + retour = error.returnMessage[error.PROFIL_INACTIVE] + + # Génération du token JWT + # jwt_token = jwt.encode({ + # 'id': user.id, + # 'email': user.email, + # 'role': "admin" + # }, settings.SECRET_KEY, algorithm='HS256') + else: + retour = error.returnMessage[error.WRONG_ID] + + + return JsonResponse({ + 'errorFields':errorFields, + 'errorMessage':retour, + 'profil':user.id if user else -1, + #'jwtToken':jwt_token if profil != -1 else '' + }, safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class SubscribeView(APIView): + + def get(self, request): + return JsonResponse({ + 'message':'', + 'errorFields':'', + 'errorMessage':'' + }, safe=False) + + def post(self, request): + retourErreur = error.returnMessage[error.BAD_URL] + retour = '' + newProfilConnection=JSONParser().parse(request) + + validatorSubscription = validator.ValidatorSubscription(data=newProfilConnection) + validationOk, errorFields = validatorSubscription.validate() + + + if validationOk: + + # On vérifie que l'email existe : si ce n'est pas le cas, on retourne une erreur + profil = bdd.getProfile(Profil.objects.all(), newProfilConnection.get('email')) + if profil == None: + retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS] + else: + if profil.is_active: + retourErreur=error.returnMessage[error.PROFIL_ACTIVE] + return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False) + else: + try: + profil.set_password(newProfilConnection.get('password1')) + profil.is_active = True + profil.full_clean() + profil.save() + clear_cache() + retour = error.returnMessage[error.MESSAGE_ACTIVATION_PROFILE] + retourErreur='' + return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False) + except ValidationError as e: + retourErreur = error.returnMessage[error.WRONG_MAIL_FORMAT] + return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields}, safe=False) + + return JsonResponse({'message':retour, 'errorMessage':retourErreur, "errorFields":errorFields, "id":-1}, safe=False) + + + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class NewPasswordView(APIView): + def get(self, request): + return JsonResponse({ + 'message':'', + 'errorFields':'', + 'errorMessage':'' + }, safe=False) + + def post(self, request): + retourErreur = error.returnMessage[error.BAD_URL] + retour = '' + newProfilConnection=JSONParser().parse(request) + + validatorNewPassword = validator.ValidatorNewPassword(data=newProfilConnection) + validationOk, errorFields = validatorNewPassword.validate() + if validationOk: + + profil = bdd.getProfile(Profil.objects.all(), newProfilConnection.get('email')) + if profil == None: + retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS] + else: + # Génération d'une URL provisoire pour modifier le mot de passe + profil.code = util.genereRandomCode(12) + profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS) + profil.save() + clear_cache() + retourErreur = '' + retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD]%(newProfilConnection.get('email')) + mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code) + + return JsonResponse({'message':retour, 'errorMessage':retourErreur, "errorFields":errorFields}, safe=False) + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class ResetPasswordView(APIView): + def get(self, request, _uuid): + return JsonResponse({ + 'message':'', + 'errorFields':'', + 'errorMessage':'' + }, safe=False) + + def post(self, request, _uuid): + retourErreur = error.returnMessage[error.BAD_URL] + retour = '' + newProfilConnection=JSONParser().parse(request) + + validatorResetPassword = validator.ValidatorResetPassword(data=newProfilConnection) + validationOk, errorFields = validatorResetPassword.validate() + + profil = bdd.getObject(Profil, "code", _uuid) + if profil: + + if datetime.strptime(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'), '%d-%m-%Y %H:%M') > datetime.strptime(profil.datePeremption, '%d-%m-%Y %H:%M'): + retourErreur = error.returnMessage[error.EXPIRED_URL]%(_uuid) + elif validationOk: + retour = error.returnMessage[error.PASSWORD_CHANGED] + + profil.set_password(newProfilConnection.get('password1')) + profil.code = '' + profil.datePeremption = '' + profil.save() + clear_cache() + retourErreur='' + + return JsonResponse({'message':retour, "errorMessage":retourErreur, "errorFields":errorFields}, safe=False) diff --git a/Back-End/GestionMessagerie/__init__.py b/Back-End/GestionMessagerie/__init__.py new file mode 100644 index 0000000..db2f57d --- /dev/null +++ b/Back-End/GestionMessagerie/__init__.py @@ -0,0 +1 @@ +default_app_config = 'GestionMessagerie.apps.GestionMessagerieConfig' diff --git a/Back-End/GestionMessagerie/admin.py b/Back-End/GestionMessagerie/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Back-End/GestionMessagerie/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Back-End/GestionMessagerie/apps.py b/Back-End/GestionMessagerie/apps.py new file mode 100644 index 0000000..b9adf5a --- /dev/null +++ b/Back-End/GestionMessagerie/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class GestionMessagerieConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'GestionMessagerie' diff --git a/Back-End/GestionMessagerie/models.py b/Back-End/GestionMessagerie/models.py new file mode 100644 index 0000000..654f6ef --- /dev/null +++ b/Back-End/GestionMessagerie/models.py @@ -0,0 +1,15 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django.conf import settings +from GestionLogin.models import Profil + +class Messagerie(models.Model): + id = models.AutoField(primary_key=True) + objet = models.CharField(max_length=200, default="", blank=True) + emetteur = models.ForeignKey(Profil, on_delete=models.PROTECT, related_name='messages_envoyes') + destinataire = models.ForeignKey(Profil, on_delete=models.PROTECT, related_name='messages_recus') + corpus = models.CharField(max_length=200, default="", blank=True) + + def __str__(self): + return 'Messagerie_'+self.id \ No newline at end of file diff --git a/Back-End/GestionMessagerie/serializers.py b/Back-End/GestionMessagerie/serializers.py new file mode 100644 index 0000000..9dbebe3 --- /dev/null +++ b/Back-End/GestionMessagerie/serializers.py @@ -0,0 +1,16 @@ +from rest_framework import serializers +from GestionLogin.models import Profil +from GestionMessagerie.models import Messagerie + +class MessageSerializer(serializers.ModelSerializer): + destinataire_profil = serializers.SerializerMethodField() + emetteur_profil = serializers.SerializerMethodField() + class Meta: + model = Messagerie + fields = '__all__' + + def get_destinataire_profil(self, obj): + return obj.destinataire.email + + def get_emetteur_profil(self, obj): + return obj.emetteur.email \ No newline at end of file diff --git a/Back-End/GestionMessagerie/urls.py b/Back-End/GestionMessagerie/urls.py new file mode 100644 index 0000000..9636936 --- /dev/null +++ b/Back-End/GestionMessagerie/urls.py @@ -0,0 +1,9 @@ +from django.urls import path, re_path + +from GestionMessagerie.views import MessagerieView, MessageView + +urlpatterns = [ + re_path(r'^messagerie/([0-9]+)$', MessagerieView.as_view(), name="messagerie"), + re_path(r'^message$', MessageView.as_view(), name="message"), + re_path(r'^message/([0-9]+)$', MessageView.as_view(), name="message"), +] \ No newline at end of file diff --git a/Back-End/GestionMessagerie/views.py b/Back-End/GestionMessagerie/views.py new file mode 100644 index 0000000..40c7297 --- /dev/null +++ b/Back-End/GestionMessagerie/views.py @@ -0,0 +1,32 @@ +from django.http.response import JsonResponse +from rest_framework.views import APIView +from rest_framework.parsers import JSONParser + +from .models import * + +from GestionMessagerie.serializers import MessageSerializer + +from N3wtSchool import bdd + +class MessagerieView(APIView): + def get(self, request, _idProfile): + messagesList = bdd.getObjects(_objectName=Messagerie, _columnName='destinataire__id', _value=_idProfile) + messages_serializer = MessageSerializer(messagesList, many=True) + return JsonResponse(messages_serializer.data, safe=False) + +class MessageView(APIView): + def get(self, request, _id): + message=bdd.getObject(Messagerie, "id", _id) + message_serializer=MessageSerializer(message) + return JsonResponse(message_serializer.data, safe=False) + + def post(self, request): + message_data=JSONParser().parse(request) + message_serializer = MessageSerializer(data=message_data) + + if message_serializer.is_valid(): + message_serializer.save() + + return JsonResponse('Nouveau Message ajouté', safe=False) + + return JsonResponse(message_serializer.errors, safe=False) \ No newline at end of file diff --git a/Back-End/GestionNotification/__init__.py b/Back-End/GestionNotification/__init__.py new file mode 100644 index 0000000..607789b --- /dev/null +++ b/Back-End/GestionNotification/__init__.py @@ -0,0 +1 @@ +default_app_config = 'GestionNotification.apps.GestionNotificationConfig' diff --git a/Back-End/GestionNotification/admin.py b/Back-End/GestionNotification/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Back-End/GestionNotification/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Back-End/GestionNotification/apps.py b/Back-End/GestionNotification/apps.py new file mode 100644 index 0000000..097e7f5 --- /dev/null +++ b/Back-End/GestionNotification/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + +class GestionNotificationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'GestionNotification' + + def ready(self): + import GestionNotification.signals + diff --git a/Back-End/GestionNotification/models.py b/Back-End/GestionNotification/models.py new file mode 100644 index 0000000..361c034 --- /dev/null +++ b/Back-End/GestionNotification/models.py @@ -0,0 +1,28 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django.conf import settings +from GestionLogin.models import Profil + +class TypeNotif(models.IntegerChoices): + NOTIF_NONE = 0, _('Aucune notification') + NOTIF_MESSAGE = 1, _('Un message a été reçu') + NOTIF_DI = 2, _('Le dossier d\'inscription a été mis à jour') + +class Notification(models.Model): + user = models.ForeignKey(Profil, on_delete=models.PROTECT) + message = models.CharField(max_length=255) + is_read = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + typeNotification = models.IntegerField(choices=TypeNotif, default=0) + + @property + def typeNotification_label(self): + return self.get_typeNotification_display() + + def get_typeNotification_display(self): + return TypeNotif(self.typeNotification).label + + def __str__(self): + return f'Notification for {self.user.username}' + \ No newline at end of file diff --git a/Back-End/GestionNotification/signals.py b/Back-End/GestionNotification/signals.py new file mode 100644 index 0000000..1f1fc37 --- /dev/null +++ b/Back-End/GestionNotification/signals.py @@ -0,0 +1,23 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver +from .models import Notification, TypeNotif +from GestionMessagerie.models import Messagerie +from GestionInscriptions.models import FicheInscription + +@receiver(post_save, sender=Messagerie) +def notification_MESSAGE(sender, instance, created, **kwargs): + if created: + Notification.objects.create( + user=instance.destinataire, + message=(TypeNotif.NOTIF_MESSAGE).label, + typeNotification=TypeNotif.NOTIF_MESSAGE + ) + +@receiver(post_save, sender=FicheInscription) +def notification_DI(sender, instance, created, **kwargs): + for responsable in instance.eleve.responsables.all(): + Notification.objects.create( + user=responsable.profilAssocie, + message=(TypeNotif.NOTIF_DI).label, + typeNotification=TypeNotif.NOTIF_DI + ) diff --git a/Back-End/GestionNotification/urls.py b/Back-End/GestionNotification/urls.py new file mode 100644 index 0000000..7619357 --- /dev/null +++ b/Back-End/GestionNotification/urls.py @@ -0,0 +1,7 @@ +from django.urls import path, re_path + +from GestionNotification.views import NotificationView + +urlpatterns = [ + re_path(r'^notification$', NotificationView.as_view(), name="notification"), +] \ No newline at end of file diff --git a/Back-End/GestionNotification/views.py b/Back-End/GestionNotification/views.py new file mode 100644 index 0000000..8ffbeb6 --- /dev/null +++ b/Back-End/GestionNotification/views.py @@ -0,0 +1,15 @@ +from django.http.response import JsonResponse +from rest_framework.views import APIView + +from .models import * + +from GestionInscriptions.serializers import NotificationSerializer + +from N3wtSchool import bdd + +class NotificationView(APIView): + def get(self, request): + notifsList=bdd.getAllObjects(Notification) + notifs_serializer=NotificationSerializer(notifsList, many=True) + + return JsonResponse(notifs_serializer.data, safe=False) \ No newline at end of file diff --git a/Back-End/N3wtSchool/__init__.py b/Back-End/N3wtSchool/__init__.py new file mode 100644 index 0000000..75e642f --- /dev/null +++ b/Back-End/N3wtSchool/__init__.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import, unicode_literals +from .celery import app as celery_app + +__all__ = ('celery_app',) +default_app_config = 'N3wtSchool.apps.N3wtSchoolConfig' # Assurer l'utilisation de la configuration d'application \ No newline at end of file diff --git a/Back-End/N3wtSchool/apps.py b/Back-End/N3wtSchool/apps.py new file mode 100644 index 0000000..e38abfd --- /dev/null +++ b/Back-End/N3wtSchool/apps.py @@ -0,0 +1,8 @@ +# n3wtschool/apps.py +from django.apps import AppConfig + +class N3wtSchoolConfig(AppConfig): + name = 'N3wtSchool' + + def ready(self): + import N3wtSchool.signals diff --git a/Back-End/N3wtSchool/asgi.py b/Back-End/N3wtSchool/asgi.py new file mode 100644 index 0000000..6a4912d --- /dev/null +++ b/Back-End/N3wtSchool/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for N3wtSchool project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'N3wtSchool.settings') + +application = get_asgi_application() diff --git a/Back-End/N3wtSchool/bdd.py b/Back-End/N3wtSchool/bdd.py new file mode 100644 index 0000000..ef24b59 --- /dev/null +++ b/Back-End/N3wtSchool/bdd.py @@ -0,0 +1,86 @@ +import logging +from django.db.models import Q +from GestionInscriptions.models import FicheInscription, Profil, Eleve + +def getAllObjects(_objectName): + result = _objectName.objects.all() + if not result: + logging.warning("Aucun résultat n'a été trouvé - " + _objectName.__name__) + return result + +def getObject(_objectName, _columnName, _value): + result=None + try : + result = _objectName.objects.get(**{_columnName: _value}) + except _objectName.DoesNotExist: + logging.error("Aucun résultat n'a été trouvé - " + _objectName.__name__ + " (" + _columnName + "=" + str(_value) + ")") + + return result + +def getObjects(_objectName, _columnName, _value, _reverseCondition=False): + results=None + try : + results = _objectName.objects.filter(**{_columnName: _value}) if _reverseCondition == False else _objectName.objects.filter(~Q(**{_columnName: _value})) + except _objectName.DoesNotExist: + logging.error("Aucun résultat n'a été trouvé - " + _objectName.__name__ + " (" + _columnName + "=" + str(_value) + ")") + + return results + +def existsProfilInList(objectList, valueToCheck): + result = False + for objectInstance in objectList: + if objectInstance.email == valueToCheck: + result = True + return result + +def getProfile(objectList, valueToCheck): + result = None + for objectInstance in objectList: + if objectInstance.email == valueToCheck: + result = objectInstance + return result + +def getEleveByCodeFI(_codeFI): + eleve = None + ficheInscriptions_List=getAllObjects(FicheInscription) + for fi in ficheInscriptions_List: + if fi.codeLienInscription == _codeFI: + eleve = fi.eleve + return eleve + +def getLastId(_object): + result = 1 + try: + result = _object.objects.latest('id').id + except: + logging.warning("Aucun résultat n'a été trouvé - ") + return result + +def searchObjects(_objectName, _searchTerm, _excludeState=None): + """ + Recherche générique sur les objets avec possibilité d'exclure certains états + _objectName: Classe du modèle + _searchTerm: Terme de recherche + _excludeState: État à exclure de la recherche (optionnel) + """ + try: + query = _objectName.objects.all() + + # Si on a un état à exclure + if _excludeState is not None: + query = query.filter(etat__lt=_excludeState) + + # Si on a un terme de recherche + if _searchTerm and _searchTerm.strip(): + terms = _searchTerm.lower().strip().split() + for term in terms: + query = query.filter( + Q(eleve__nom__icontains=term) | + Q(eleve__prenom__icontains=term) + ) + + return query.order_by('eleve__nom', 'eleve__prenom') + + except _objectName.DoesNotExist: + logging.error(f"Aucun résultat n'a été trouvé - {_objectName.__name__} (recherche: {_searchTerm})") + return None \ No newline at end of file diff --git a/Back-End/N3wtSchool/celery.py b/Back-End/N3wtSchool/celery.py new file mode 100644 index 0000000..9c1e699 --- /dev/null +++ b/Back-End/N3wtSchool/celery.py @@ -0,0 +1,20 @@ +from __future__ import absolute_import, unicode_literals +import os +from celery import Celery +from django.apps import apps +import logging + +# Définir le module de réglages de Django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'N3wtSchool.settings') + +app = Celery('N3wtSchool') + +# Lire les configurations de Celery depuis les réglages de Django +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Découverte automatique des tâches des apps Django +app.autodiscover_tasks(lambda: [n.name for n in apps.get_app_configs()]) + +# Configurer le logger global pour Celery +logger = logging.getLogger('celery') +logger.setLevel(logging.WARNING) diff --git a/Back-End/N3wtSchool/error.py b/Back-End/N3wtSchool/error.py new file mode 100644 index 0000000..300a992 --- /dev/null +++ b/Back-End/N3wtSchool/error.py @@ -0,0 +1,31 @@ +from typing import Final + +WRONG_ID: Final = 1 +INCOMPLETE: Final = 2 +BAD_URL: Final = 3 +ALREADY_EXISTS: Final = 4 +DIFFERENT_PASWWORD: Final = 5 +PROFIL_NOT_EXISTS: Final = 6 +MESSAGE_REINIT_PASSWORD: Final = 7 +EXPIRED_URL: Final = 8 +PASSWORD_CHANGED: Final = 8 +WRONG_MAIL_FORMAT: Final = 9 +PROFIL_INACTIVE: Final = 10 +MESSAGE_ACTIVATION_PROFILE: Final = 11 +PROFIL_ACTIVE: Final = 12 + +returnMessage = { + WRONG_ID:'Identifiants invalides', + INCOMPLETE:'Renseignez les champs obligatoires', + BAD_URL:'Lien invalide : veuillez contacter l\'administrateur', + ALREADY_EXISTS: 'Profil déjà existant', + DIFFERENT_PASWWORD: 'Les mots de passe ne correspondent pas', + PROFIL_NOT_EXISTS: 'Aucun profil associé à cet utilisateur', + MESSAGE_REINIT_PASSWORD: 'Un mail a été envoyé à l\'adresse \'%s\'', + EXPIRED_URL:'L\'URL a expiré. Effectuer à nouveau la demande de réinitialisation de mot de passe : http://localhost:3000/password/reset?uuid=%s', + PASSWORD_CHANGED: 'Le mot de passe a été réinitialisé', + WRONG_MAIL_FORMAT: 'L\'adresse mail est mal formatée', + PROFIL_INACTIVE: 'Le profil n\'est pas actif', + MESSAGE_ACTIVATION_PROFILE: 'Votre profil a été activé avec succès', + PROFIL_ACTIVE: 'Le profil est déjà actif', +} \ No newline at end of file diff --git a/Back-End/N3wtSchool/redis_client.py b/Back-End/N3wtSchool/redis_client.py new file mode 100644 index 0000000..0289323 --- /dev/null +++ b/Back-End/N3wtSchool/redis_client.py @@ -0,0 +1,10 @@ +# redis_client.py +import redis +from django.conf import settings + +# Configurer le client Redis +redis_client = redis.StrictRedis( + host=settings.REDIS_HOST, + port=settings.REDIS_PORT, + db=settings.REDIS_DB, +) diff --git a/Back-End/N3wtSchool/renderers.py b/Back-End/N3wtSchool/renderers.py new file mode 100644 index 0000000..96b2b67 --- /dev/null +++ b/Back-End/N3wtSchool/renderers.py @@ -0,0 +1,14 @@ +from io import BytesIO +from django.http import HttpResponse +from django.template.loader import get_template + +from xhtml2pdf import pisa + +def render_to_pdf(template_src, context_dict={}): + template = get_template(template_src) + html = template.render(context_dict) + result = BytesIO() + pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), result) + if pdf.err: + return HttpResponse("Invalid PDF", status_code=400, content_type='text/plain') + return HttpResponse(result.getvalue(), content_type='application/pdf') \ No newline at end of file diff --git a/Back-End/N3wtSchool/settings.py b/Back-End/N3wtSchool/settings.py new file mode 100644 index 0000000..24e93e4 --- /dev/null +++ b/Back-End/N3wtSchool/settings.py @@ -0,0 +1,247 @@ +""" +Django settings for N3wtSchool project. + +Generated by 'django-admin startproject' using Django 5.0.4. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +from pathlib import Path +import json +import os + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +LOGIN_REDIRECT_URL = '/GestionInscriptions/fichesInscriptions' + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-afjm6kvigncxzx6jjjf(qb0n(*qvi#je79r=gqflcn007d_ve9' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + +# Application definition + +INSTALLED_APPS = [ + 'GestionInscriptions.apps.GestioninscriptionsConfig', + 'GestionLogin.apps.GestionloginConfig', + 'GestionMessagerie.apps.GestionMessagerieConfig', + 'GestionNotification.apps.GestionNotificationConfig', + 'GestionEnseignants.apps.GestionenseignantsConfig', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'corsheaders', + 'django_celery_beat', + 'N3wtSchool', + 'drf_yasg', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', # Déplacez ici, avant CorsMiddleware + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + + +ROOT_URLCONF = 'N3wtSchool.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / "templates", BASE_DIR / "static/templates"], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.redis.RedisCache', + 'LOCATION': 'redis://redis:6379', + } +} +SESSION_ENGINE = 'django.contrib.sessions.backends.cache' +SESSION_CACHE_ALIAS = 'default' + +WSGI_APPLICATION = 'N3wtSchool.wsgi.application' + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + 'OPTIONS': { + 'min_length': 6, + } + }, + #{ + # 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + #}, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +DEBUG = True + +STATIC_URL = 'static/' + +STATICFILES_DIRS = [ + BASE_DIR / 'static', +] + +STATIC_ROOT = BASE_DIR / 'staticfiles' + + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +######################################################################## +#################### Application Settings ############################## +######################################################################## + +with open('GestionInscriptions/Configuration/application.json', 'r') as f: + jsonObject = json.load(f) + +DJANGO_SUPERUSER_PASSWORD='admin' +DJANGO_SUPERUSER_USERNAME='admin' +DJANGO_SUPERUSER_EMAIL='admin@n3wtschool.com' + +EMAIL_HOST='smtp.gmail.com' +EMAIL_PORT=587 +EMAIL_HOST_USER=jsonObject['mailFrom'] +EMAIL_HOST_PASSWORD=jsonObject['password'] +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_USE_TLS = True +EMAIL_USE_SSL = False +EMAIL_INSCRIPTION_SUBJECT = '[N3WT-SCHOOL] Dossier Inscription' +EMAIL_INSCRIPTION_CORPUS = """Bonjour, + +Afin de procéder à l'inscription de votre petit bout, vous trouverez ci-joint le lien vers la page d'authentification : http://localhost:3000/users/login + +S'il s'agit de votre première connexion, veuillez procéder à l'activation de votre compte : http://localhost:3000/users/subscribe +identifiant = %s + +Cordialement, +""" + +EMAIL_RELANCE_SUBJECT = '[N3WT-SCHOOL] Relance - Dossier Inscription' +EMAIL_RELANCE_CORPUS = 'Bonjour,\nN\'ayant pas eu de retour de votre part, nous vous renvoyons le lien vers le formulaire d\'inscription : http://localhost:3000/users/login\nCordialement' +EMAIL_REINIT_SUBJECT = 'Réinitialisation du mot de passe' +EMAIL_REINIT_CORPUS = 'Bonjour,\nVous trouverez ci-joint le lien pour réinitialiser votre mot de passe : http://localhost:3000/users/password/reset?uuid=%s\nCordialement' + +DOCUMENT_DIR = 'documents' + +CORS_ORIGIN_ALLOW_ALL = True +CORS_ALLOW_ALL_HEADERS = True +CORS_ALLOW_CREDENTIALS = True + +CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000" +] + +CSRF_TRUSTED_ORIGINS = [ + "http://localhost:3000", # Front Next.js + "http://localhost:8080" # Insomnia +] + +CSRF_COOKIE_HTTPONLY = False +CSRF_COOKIE_SECURE = False +CSRF_COOKIE_NAME = 'csrftoken' + + +USE_TZ = True +TZ_APPLI = 'Europe/Paris' + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + "NAME": "school", + "USER": "postgres", + "PASSWORD": "postgres", + "HOST": "database", + "PORT": "5432", + } +} + +AUTH_USER_MODEL = 'GestionLogin.Profil' +AUTHENTICATION_BACKENDS = ('GestionLogin.backends.EmailBackend', ) +SILENCED_SYSTEM_CHECKS = ["auth.W004"] + +EXPIRATION_URL_NB_DAYS = 7 +EXPIRATION_DI_NB_DAYS = 20 +DATE_FORMAT = '%d-%m-%Y %H:%M' + +EXPIRATION_SESSION_NB_SEC = 10 + +NB_RESULT_PER_PAGE = 8 +NB_MAX_PAGE = 100 + +REST_FRAMEWORK = { + 'DEFAULT_PAGINATION_CLASS': 'GestionInscriptions.pagination.CustomPagination', + 'PAGE_SIZE': NB_RESULT_PER_PAGE +} + +CELERY_BROKER_URL = 'redis://redis:6379/0' +CELERY_RESULT_BACKEND = 'redis://redis:6379/0' +CELERY_ACCEPT_CONTENT = ['json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_TIMEZONE = 'Europe/Paris' +CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True + +URL_DJANGO = 'http://localhost:8080/' + +REDIS_HOST = 'redis' +REDIS_PORT = 6379 +REDIS_DB = 0 +REDIS_PASSWORD = None + +SECRET_KEY = 'QWQ8bYlCz1NpQ9G0vR5kxMnvWszfH2y3' diff --git a/Back-End/N3wtSchool/signals.py b/Back-End/N3wtSchool/signals.py new file mode 100644 index 0000000..c44aabc --- /dev/null +++ b/Back-End/N3wtSchool/signals.py @@ -0,0 +1,21 @@ +from django.db.models.signals import post_migrate +from django.dispatch import receiver +from django_celery_beat.models import IntervalSchedule, PeriodicTask +import json + + +@receiver(post_migrate) +def setup_periodic_tasks(sender, **kwargs): + + schedule, created = IntervalSchedule.objects.get_or_create( + every=5, + period=IntervalSchedule.SECONDS, + ) + + # Déclarer la tâche périodique + PeriodicTask.objects.get_or_create( + interval=schedule, # Utiliser l'intervalle défini ci-dessus + name='Tâche périodique toutes les 5 secondes', + task='GestionInscriptions.tasks.check_for_signature_deadlines', # Remplacer par le nom de ta tâche + kwargs=json.dumps({}) # Si nécessaire, ajoute + ) \ No newline at end of file diff --git a/Back-End/N3wtSchool/urls.py b/Back-End/N3wtSchool/urls.py new file mode 100644 index 0000000..3754cd8 --- /dev/null +++ b/Back-End/N3wtSchool/urls.py @@ -0,0 +1,49 @@ +""" +URL configuration for N3wtSchool project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import include, path, re_path +from rest_framework import permissions +from drf_yasg.views import get_schema_view +from drf_yasg import openapi + + +schema_view = get_schema_view( + openapi.Info( + title="N3wtSchool API", + default_version='v1', + description="Documentation de l'API de N3wtSchool", + terms_of_service="https://www.google.com/policies/terms/", + contact=openapi.Contact(email="contact@example.com"), + license=openapi.License(name="BSD License"), + ), + public=True, + permission_classes=(permissions.AllowAny,), +) + + +urlpatterns = [ + path('admin/', admin.site.urls), + path("GestionInscriptions/", include(("GestionInscriptions.urls", 'GestionInscriptions'), namespace='GestionInscriptions')), + path("GestionLogin/", include(("GestionLogin.urls", 'GestionLogin'), namespace='GestionLogin')), + path("GestionMessagerie/", include(("GestionMessagerie.urls", 'GestionMessagerie'), namespace='GestionMessagerie')), + path("GestionNotification/", include(("GestionNotification.urls", 'GestionNotification'), namespace='GestionNotification')), + path("GestionEnseignants/", include(("GestionEnseignants.urls", 'GestionEnseignants'), namespace='GestionEnseignants')), + # Documentation Api + re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), + path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), +] diff --git a/Back-End/N3wtSchool/wsgi.py b/Back-End/N3wtSchool/wsgi.py new file mode 100644 index 0000000..ede89e0 --- /dev/null +++ b/Back-End/N3wtSchool/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for N3wtSchool project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'N3wtSchool.settings') + +application = get_wsgi_application() diff --git a/Back-End/__version__.py b/Back-End/__version__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/Back-End/__version__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/Back-End/db.sqlite3 b/Back-End/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..bb7dd983ea96df37eec8f868f47a7b8b6001a94d GIT binary patch literal 131072 zcmeI5Yit`=cE>p)MT(Nh(Zlk%n>ZTLQDUyFX!sVLZa0d}*lKJ!mgOb_8cc`eNT$Pw zOj3RzKwBzKw#c>xy2UQg0&R){1zNNp`ovP@)C9Fyz02jOTIgng99SJ9lQzy(3+_da2aZ<*i!1qBZ4&XTl>0o|k0V z|{XN&I&EfjG%pVN3T~(jYg?fZK&ot3t~B0NENbioh$1x zo-(4a%I`7i?tCn+6!KeHcioc%>F#lMkmODQ zQhQSzky_rYuy<-8xwc?crBF=7Q+dT~IX#3X`nR7RTeC^-bDDu;qb6l@L%}{9rm9)6 zRgJ}@P^&ieYEy0A+tFE*if<(|X+4?eRb$$TE_GraRr`6d=r*lc`*9Q?nb$%A>E42{ zCz@4a7U?-bp;pwZT18itn3hjuQkq#CW~p%hYA}rkbUkn+R0ADxk?7tV;{j=9MY#WB zQ_Gjz*D1gH`N9;@F#8tgk&qF@#>&f=a@PLDX~?irMgAR7P5IT_ro3`}d8qvUffi3`n!H!h5Ie$hwVhv=5|1(b>@&jXSkEwaD9Ay>LUTpN_?squpjnjijtK znrgYWU8+XpqSn-#rHXDnUTUa~U1}Jtkw)ZvtyZ=kWqVl{VERuerhDVwO=sCE)#)_- zEjvQ?wa_bCscfDplQPdl<(bf?wzbtOl8$JFX6ZJmbn0gjt2b+Lwvz2yMPne)g3{lFaxmc0!w_qOL~BH))U%hbiSA< zCi46-#HGNn0n`3Q;bOSI=k`;GOEDIER}*`&;IinkYTFx-5(!~%m)E#UNtazdd(FF8 zJcQbhS&@@qQ@wkwbYpTq9DQxb&zklokJIQc8~UwXy;{&6n_gRdj)SM!HF$cYL>KeT!|+amGojPEi37>spQ#M`h{3J zn@TTVoE-MdyuQNTyU9MTYUL)4Sn-ssnY2xL60YYm9! z`UY_nOJ-u(S3>0ITF23{)lnzycaR`ONo2CGj+37^JAN{TpW9lwM0{#>O3oFFs$Q?x z>S~3mPTSU95h=^*L?XR1M$T3{&eCd~-Y!dZy_17rBPb1(YCi5pS;?f7#PZ9dc8pmgCCm@UTZBO~p#7N-h#CxlDsw^?SUYSt7PpRkzC8wpzGRD^Xsc*H!9;>~ba* z%dGgw6;U=^#Z6a+EV^oI%&n`7Ogf#4D_(Lj-*vH1hd?JyCo{{jr6FQUC3uIhUk^eB zlG!ZD6bRkC6W#u2N+|J!5)yyY;}>sx#QzY#A%0oBEq+$~W$~`9OTz{TfB*=900@8p z2!H?xfB*=900@AbaO#9yNg{D1%mfB*=9 z00@8p2!H?xfB*=900=xP0>^wOpAtF~hm3#yPy6Oi3Dy}Ami6I~Z)S$45m<)67xv9a ze2b?Kywkp!IlkFV7*vNQebUJwPYHA;M6mh)C&qumBmT4a`{K`tdGV|`9{!i`ABMjW zu7}scFNCK;{}uY1(C>!c4Q+%@jel?a8{@w-exC~92LwO>1V8`;KmY_l00ck)1RMk+ zCx$)3{G$JMNx!2SFE!`1s?4@!WBWOYY@#tgeLt_$FSoE*wfA)Gj<@aTcZgKCZFcQF z9Xol3>`pEC*_?ihGurG}EF2??#mq2QWqG6ODl_)UY!dp?G#STU95&=R%ngaQ0`v_C zD`4n!6fm?}3qPF9Jv~)z0-InHF7S;Cw;;x!J37c zIqnk{g`TX-u@gQaCb;BRrrQS$x0cMv)QnG95S;mvNzo_F3TA3#f(x)FL&WwT!y(IP zgu_1Jq|i-vgeH8#oM6dsjB^6EgvMBVm*IqGBu4p}&2&YO>vJY3MkJrGB=jaD0`2{V zLo;*W=TsdDhT$n+@OicGo)8a_L(|3eeL;57QZciB>tQDPvVE-*TlaN|5W^n_(k#e#NQTwL;OJeCGi)(f&K=`E7f*k2M?sFJp9GLcdWNlIujA!N@mh}vVF~(cA`t2m`BxqUM#vzYu0`o1xV(#PyiRP z(HKlt4P398as36tv7`M9CILpd5(@fC;Y24J1e|*%8snd zYP1g|!+)=w{pjpyjmDi?ouJ;<>V+Fx{d6q89PN^&ILlh2sg`TorD{YjYE8Xas_53^ zrH0zrrE7t4j>!31t!zEY_ExouZu(CsrhB=4@20bCmFjew{+1mf`&#G~tyDJ8lu4Oq zqVh~=Q`_3=6-h_5LbG(6R66yuh}E05QdK8JJ>f@VqkbtjD|q-Fs&T7aB5SHBvZqHb zwq`tY#k#9wdut;BDVr7UXUzKTx!-W}p1nbrn(r|>R5NIMalN*Bt`crLSYsyYW-m!M zE|*KU$Gur#uyaJ|x%Y3SxjCWrmIJ5>G0+d(pB8K;&7w`b-7fh0 z2*nX(&+vPX$**)Jkc)t~J@qSkI0jo8L&!@k7;!wzFg2cj77gjmO?!IFC}%a>@Pfg7JUW zAL0iDKmY_l00ck)1V8`;KmY_l00fRUfsprtXWH{W9`R;)C-g_-|33QB=nn^fYvh|F zQ~qz!Jb=p3A9y#te=_vb!uvz#$m}utc_+IVkfKrHy+xjtsp@xBGaGK-vgc-7$6^8L zT2yFdc?#Xg&NOsBvaafPcS?0#4e=xHnKa$tOff)*YfSrPrD(@0a?yP z1E1+79?#zM)-01D%U;$oJYzq|9Cw0wOTTC4jFQ$YvSq-AvT_}Jidu`vP5o}uI>2I; z=`uj^qT-jXMEhl;?c&s!eS0kS!h?=(jI<^f0z4UC;h@YEyt&pHrM3?8S;rI}Yyiui zT#Kcw9NVKyQSZ-trmik92L2^Kzeo~i9y~ukOC{rjjP<;eG}`{DcRopL`wivU+rBx+ z=1@7v!w%)q0ID%M_8`M}ZluW#A1&OcPRPVDN*7NhAN4RDoz!i7(G~(t!H7JBDHvgU z|HE6oIWF)}reMqkqT2W`!p(-&gpU9*%b3%++%5R8vnMq|D^=Nl@ zJ@8}_s+5k0NOW&r4zPiN`-0hRn5TA795&=yI}p?OP}h!grb}-hWF4n8x=CvxuO8cQL9}&oHXzj!LhEho?a?YkrSSx{tmXBxTA{ixtC@7Zm?$Rl zyy9Jy`&=vE#c+Smw%e#x5F^vo6ruRIEV`wxJ`<1<31Kg5y`@@9x@b6QuX-1YLsh@2 zuWzliRl6UKwkA$m=UN)Fv|^=HWm6}*b0rI@bW+c2X{W){Mp*Wzmj2~!u)Ag~^v=*= zv~~LFfTYq~%d6Iw(zWfqnv?m0zO3h+(w;g%nzh2%)U<-(r@ur;iAeOFpL~i>68g-N zwIy_=Iu$+R+6KDjhiU+|{7t>kXM#-JalNvsBekl!Ro1p6 za-*V^%QQbp-mNxD+cayW`>ZCPyO=xoD!Ul0_CN9|S&6c)@CJ=r>S~1^bhT}ohvIz9 zb3!vaC^rA;VRcTO)?<3_%&@Yafp^CbG_Y)h%%%>x&7x`js6@TmiqN`c-qW}gzA(FX z=givJRz9EFiWjZv6=c<`-!%ej|Zdz1+#fjFx(?u zGFmpJYsHvl2DUTq9~w9er_VtGFv^hhJ2N!pHn<|}Ee)#O7Q7luYUzAB5jP()xNm1T zCya1ho;O_WyZz@rXM2!9>3XwyqR6L@1*EIgTc#X=^B0qbIsG@ANu{!>WYTO&ZHwdm zYN($Br{1<5YfpTQHzkb!KSn7HNC5#5009sH0T2KI5C8!X009sHfk#9D@BfboEZl+s z2!H?xfB*=900@8p2!H?xfWTu!0Pp{gQDs012!H?xfB*=900@8p2!H?xfB*51V8`;KmY_l00ck)1V8`;9wP#H|9^}s15!W$1V8`;KmY_l00ck)1V8`; zK;RJ(2$5gE_`XN{w)hX?Uy6S$en1=e0Ra#I0T2KI5C8!X009sH0T2KI5O_ocru{z8 z$)}9J#MUtWeQMa}nLlOxWsc^(9lc>$ANTn@Gc)YZ1h7A85c1NN#J2cv_n#V~%{jJd z{vCoz!RL`q@;_rje=&n(J#rWI5Q= z)!o(A5A)~uby}wTv`+8!x=1If()%>MPOs8++UR|gnpCHeK2KAZZuMlXPn*D>1-9V3 zG#47=Y;S)n%y@{`rBOBD#Xob7S5Jpu;f^d|mNc^!3b(nPWPeU3v0=&+F<*hi{rrEg0;gZFbV zKu`2JWQMg)cM;LLoG7a^X4^c~a3k+fV0i}tNJk=Yf! zgrz&+LFI^9xs{$%2{UoMM^M4#z;YvdtBfeqMJJn~lb#6jfh$u>LYw23;8PFMX{Fr`)9&_=?6H89#n;ck2ic#idq&Y@Ocau(*nE!gAK&KjS z!-GosPBtnl{XF(VF(&s$%%1k>acJzxKE~4rI_=2EGklC>mYIX+(0(drOg6Q7J1)fv z4Xn_iuXnZ=-aVbBtzvA331m9Q!_L!zi7Yck->DKdInPlQ(ceZ#Z>8W;nJ@HiRBOh# zLgu!WH%}eioS4g*JlxkN>HtIc%jVzd`BE_-!k_2F@O~_$DS7eA7RO$R>q{2}UnACH z#GYIa8{*1*5vr(aty;J_E9v8ktY?Y9+%IF&ucW|AuZ1Eu!#p_`vthPtaoQ>8N*;5~ zG1G?z+zd}Y6ZkIPD8CLgJbhEVOJFL!GUAi$t+IOZ(XKjBj~HAfBRu`w!ACu#R}=jU zbH}zfljF_#ChyPbNBSQ1XLeaXc6g!=X7$Uok+oM|Uj?dUrgLvg{9_#b!lM@_&kzcd zwPY+&&3vl4U=ut8h2e>~F$4XP@AFVczWi2^Du1RA>1+BDcKBHdG`<%rdd9|60T7<3)52%)R(?Gkd*>j`eK#OZHB+`EH=@SQj^E@8r8sm3ca{ zy6l1Zb?Wjwx?DE0>=mlDs=QW?P8snhN4aFZtn5ImC|6?nB$iWsgA;u!*KZt*t!A%_ zGFAt>QsVjd^Dnby BVg~>K literal 0 HcmV?d00001 diff --git a/Back-End/saveDB.py b/Back-End/saveDB.py new file mode 100644 index 0000000..b5d462a --- /dev/null +++ b/Back-End/saveDB.py @@ -0,0 +1,15 @@ +import subprocess +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument("action", type=str, help="dump | restore") +parser.add_argument("fileName", type=str, help="nom du fichier dump") +args = parser.parse_args() + +if args.action == "dump": + process=subprocess.Popen(["pg_dump", "-h", "database", "-d", "school", "-U", "postgres", "-p", "5432", "-Fc", "-f", args.fileName + ".dmp"]) + process.wait() +elif args.action == "restore": + process=subprocess.Popen(["pg_restore", "--clean", "-h", "database", "-d", "school", "-U", "postgres", args.fileName + ".dmp"]) + process.wait() diff --git a/Back-End/start.py b/Back-End/start.py new file mode 100644 index 0000000..224d3f7 --- /dev/null +++ b/Back-End/start.py @@ -0,0 +1,37 @@ +import subprocess +import os + +def run_command(command): + process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate(input=b"y\n") + if process.returncode != 0: + print(f"Error running command: {' '.join(command)}") + print(f"stdout: {stdout.decode()}") + print(f"stderr: {stderr.decode()}") + return process.returncode + +commands = [ + ["python", "manage.py", "collectstatic", "--noinput"], + ["python", "manage.py", "flush", "--noinput"], + ["python", "manage.py", "makemigrations", "GestionInscriptions"], + ["python", "manage.py", "makemigrations", "GestionNotification"], + ["python", "manage.py", "makemigrations", "GestionMessagerie"], + ["python", "manage.py", "makemigrations", "GestionLogin"], + ["python", "manage.py", "makemigrations", "GestionEnseignants"], + ["python", "manage.py", "migrate"] +] + +for command in commands: + if run_command(command) != 0: + exit(1) + +# Lancer les processus en parallèle +processes = [ + subprocess.Popen(["python", "manage.py", "runserver", "0.0.0.0:8080"]), + subprocess.Popen(["celery", "-A", "N3wtSchool", "worker", "--loglevel=info"]), + subprocess.Popen(["celery", "-A", "N3wtSchool", "beat", "--loglevel=info", "--scheduler", "django_celery_beat.schedulers:DatabaseScheduler"]) +] + +# Attendre la fin des processus +for process in processes: + process.wait() \ No newline at end of file diff --git a/Back-End/static/css/headings.css b/Back-End/static/css/headings.css new file mode 100644 index 0000000..41e5045 --- /dev/null +++ b/Back-End/static/css/headings.css @@ -0,0 +1,22 @@ +h1,h2,h3,h4,h5,h6{ + font-family: var(--font-text); + text-transform: uppercase; +} + + + + +h1,h2,h3,h4,h5,h6{ + color: var(--dark-teal); +} + +h1.negative,h2.negative,h3.negative,h4.negative,h5.negative,h6.negative{ + color: #FFF; +} + +.heading-title{ + font-size: 2em; + font-weight: bold; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/Back-End/static/css/icons.css b/Back-End/static/css/icons.css new file mode 100644 index 0000000..a5e347e --- /dev/null +++ b/Back-End/static/css/icons.css @@ -0,0 +1,80 @@ + +/** +* ICONS +**/ +.icon{ + display: block; + height: 1.5rem; + width: 1.5rem; + background-color: #8F8F8F; + background: no-repeat; + -webkit-mask-size: cover; + mask-size: cover; +} + +.icon.user-add{ + mask: url(../img/icons/user-add.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/user-add.svg) no-repeat 50% 50%; +} +.icon.directbox-send{ + mask: url(../img/icons/directbox-send.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/directbox-send.svg) no-repeat 50% 50%; +} +.icon.arrow-square-up{ + mask: url(../img/icons/arrow-square-up.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/arrow-square-up.svg) no-repeat 50% 50% ; +} +.icon.arrow-square-down{ + transform: rotate(180deg); + mask: url(../img/icons/arrow-square-up.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/arrow-square-up.svg) no-repeat 50% 50% ; +} +.icon.book{ + mask: url(../img/icons/book.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/book.svg) no-repeat 50% 50%; +} +.icon.briefcase{ + mask: url(../img/icons/briefcase.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/briefcase.svg) no-repeat 50% 50%; +} +.icon.calculator{ + mask: url(../img/icons/calculator.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/calculator.svg) no-repeat 50% 50%; +} +.icon.key{ + mask: url(../img/icons/key.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/key.svg) no-repeat 50% 50%; +} +.icon.receipt-edit{ + mask: url(../img/icons/receipt-edit.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/receipt-edit.svg) no-repeat 50% 50%; +} +.icon.teacher{ + mask: url(../img/icons/teacher.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/teacher.svg) no-repeat 50% 50%; +} +.icon.user-line{ + mask: url(../img/icons/user-line.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/user-line.svg) no-repeat 50% 50%; +} +.icon.user{ + mask: url(../img/icons/user.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/user.svg) no-repeat 50% 50%; +} +.icon.user-minus{ + mask: url(../img/icons/user-minus.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/user-minus.svg) no-repeat 50% 50%; +} +.icon.profile-add{ + mask: url(../img/icons/profile-add.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/profile-add.svg) no-repeat 50% 50%; +} +.icon.edit{ + mask: url(../img/icons/edit.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/edit.svg) no-repeat 50% 50%; +} + +.icon.user-search { + mask: url(../img/icons/user-search.svg) no-repeat 50% 50%; + -webkit-mask: url(../img/icons/user-search.svg) no-repeat 50% 50%; +} \ No newline at end of file diff --git a/Back-End/static/css/main.css b/Back-End/static/css/main.css new file mode 100644 index 0000000..7a5aaf5 --- /dev/null +++ b/Back-End/static/css/main.css @@ -0,0 +1,506 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); +@import url('./icons.css'); +@import url('./headings.css'); +:root{ + /*Colors*/ + --deep-blue:#3C87F4; + --artic-blue:#DFE9F5; + --light_grey:#8F8F8F; + --darker-blue:#3572CA; + --dark-teal:#011922; + --darker-teal:#000E14; + --clear-blue:#63A5BF; + --lighter-grey:#F5F5F5; + + /*component settings*/ + --primary-color:var(--dark-teal); + --background-color:var(#FFF); + --font-color:var(--light_grey); + --font-text:"Roboto", sans-serif; + --topbar-bg-color:#FFF; + --sidebar-item-bg:var(--darker-teal); + --sidebar-item-font-color:#FFF; + --topbar-item-font-color:#000; + --topbar-item-bg-color:#FFF; + +} + + +body{ + color:var(--font-color); + background:var(--background-color); + font-family: var(--font-text); + font-size: 16px; + margin:0; + padding:0; +} + + +a{ + text-decoration: none; + color:var(--dark-teal) +} +a.negative{ + color: var(--clear-blue); +} +.right{ + text-align: right; + float: right; +} + +.sidebar{ + display: block; + width: 300px; + background-color: var(--primary-color); + border-radius: 0px 1em 1em 0px ; + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; +} +.sidebar>.itemlogo{ + height: 300px; + width: 300px; +} +.sidebar>.item{ + display: block; + height: 75px; + line-height: 75px; + background-color: var(--sidebar-item-bg); + color: var(--sidebar-item-font-color); + text-transform: uppercase; + text-align: center; +} + +.sidebar>.item:hover{ + background-color: var(--clear-blue); + color: var(--dark-teal); +} +.sidebar>.item.active{ + background-color: #FFF; + color: var(--dark-teal); +} +.sidebar>.item>.icon{ + background-color: var(--sidebar-item-font-color); + line-height: 75px; + display: block; + float: left; + height: 32px; + margin-top: 20px; + margin-left: 40px; + +} +.sidebar>.item:hover>.icon{ + background-color: var(--dark-teal); +} +.sidebar>.item.active>.icon{ + background-color: var(--dark-teal); +} + + +.circle{ + width: 200px; + height: 200px; + background-color: #FFF; + border-radius: 100px; + margin: auto; + margin-top: 75px; +} + +.container{ + display: block; + padding: 20px; + position: absolute; + width: calc(100% - 340px); + top: 0px; + bottom:0px; + right: 0px; + overflow-y: scroll; +} + +.negative{ + background-color: var(--dark-teal); + color:#FFF +} + +.container.full-size{ + left: 0px; +} + + +.input-wrapper{ + width: 300px; + height: 35px; + border-radius: 5px; + background-color: #FFF; + border-color: #C3C3C3; + border-style: solid; + border-width: 1px; +} +.input-group>label{ + font-size: 0.6em; + display: block; + font-weight: bold; + margin-bottom: 10px; + color: #011922; + text-transform: uppercase; +} +.negative>.input-group>label{ + color: #FFF; +} +.input-wrapper>span.icon-ctn{ + height: 35px; + width: 35px; + float: left; + background-color: #f3f3f3; + vertical-align: middle; + border-radius: 8px 35px 35px 8px; +} +.input-wrapper>span.icon-ctn>.icon{ + height: 35px; + width: 35px; + background-size: 20px; + background-position: center; + background-repeat: no-repeat; + display: block; + margin: auto; + background-color: #8F8F8F; +} +.input-wrapper>input{ + line-height: 35px; + width: calc(100% - 50px); + height: 35px; + border: none; + font-size: 14px; + margin-left: 5px; + outline: none; + background-color: transparent; +} +.max{ + width: 100%; +} +.max-90{ + width: 90%; +} +.max-80{ + width: 80%; +} +/* +* Bouton +*/ +.btn{ + display: flex; + font-family: var(--font-text); + background-color: #8F8F8F; + color: #FFF; + height: 35px; + min-width: 75px; + padding-left: 5px; + padding-right: 5px; + font-size: 1em; + outline: none; + border: none; + border-radius: 5px; + text-align: center; + text-decoration: none; + text-transform: uppercase; + line-height: 35px; + font-size: 0.6em; + font-weight: bold; + letter-spacing: 15%; + vertical-align: middle; + justify-content: space-between; +} +.btn>*{ + display: flexbox; +} +.negative>.btn{ + background-color: #FFF; + color: var(--dark-teal); +} +.btn>.icon{ + display: inline-block; + background-color: #FFF; + margin-top: auto; + margin-bottom: auto; + vertical-align: middle; +} +.btn:hover{ + background-color:#606060; +} +.btn.primary{ + background-color: var(--dark-teal); +} +.negative>.btn.primary{ + background-color: var(--clear-blue); +} +.btn.primary:hover{ + background-color: var(--darker-teal); +} + +.negative>.btn.primary:hover{ + background-color: #3b687a; +} + +.logo-circular{ + margin-top: 50px; + text-align: center; + width: 125px; + height: 125px; + border-radius: 125px; + background-color: #FFF; +} +.login-heading{ + margin-top: 50px; + text-align: center; +} +.login-heading.negative{ + color: #FFF;; +} +.login-form{ + margin-top: 50px; + width: 300px; +} +.form-group-submit{ + margin-top: 50px; +} +.centered{ + display: block; + margin-left: auto; + margin-right: auto; +} + +.centered>*{ + margin-left: auto; + margin-right: auto; +} + +.table-action{ + width: 100%; +} + +.splited-row-table{ + margin-left: auto; + margin-right: auto; + width: 90%; + border-collapse: collapse; +} +.splited-row-table>thead>tr>th{ + + padding: 10px; + text-align: left; + color: #000; +} +.splited-row-table>tbody>tr{ + background-color: #f4f4f4; + color:#8F8F8F; + line-height:50px; +} +.table-actions .icon{ + background-color: #8F8F8F; +} + + +.table{ + margin-left: auto; + margin-right: auto; + width: 90%; + border-collapse: separate; + border-spacing: 0; +} +.table>thead>tr>th{ + padding: 15px; + text-align: left; + color: #48586B; + font-weight: bold; + text-transform: Capitalise; + background-color: #F5F6F8; + border-bottom: 1px solid #EBECF0; + border-top: 1px solid #EBECF0; +} +.table>thead>tr>th:first-child{ + border-radius: 15px 0 0 0; + border-left: 1px solid #EBECF0; +} +.table>thead>tr>th:last-child{ + border-radius: 0 15px 0 0 ; + border-right: 1px solid #EBECF0; +} +.table>tbody>tr>td>.avatar{ + height: 42px; + width: 42px; + border-radius: 42px; + background-color: #8F8F8F; + display: block; + margin: auto; + float: left; + margin-right: 20px; +} +.table>tbody>tr>td{ + padding: 15px; + color: #48586B; + border-bottom: 1px solid #EBECF0; + vertical-align: middle; +} +.table>tbody>tr:nth-child(even)>td{ + padding: 15px; + background-color: #F5F6F8; + border-bottom: 1px solid #EBECF0; + vertical-align: middle; +} +.table>tbody>tr>td:first-child{ + border-left: 1px solid #EBECF0; +} +.table>tbody>tr>td:last-child{ + border-right: 1px solid #EBECF0; +} +.table>tfoot>tr>td{ + padding: 35px; + color: #48586B; + border-bottom: 1px solid #EBECF0; +} +.table>tfoot>tr>td:first-child{ + border-radius: 0 0 0 15px; + border-left: 1px solid #EBECF0; +} +.table>tfoot>tr>td:last-child{ + border-radius: 0 0 15px 0 ; + border-right: 1px solid #EBECF0; +} + +.pagination{ + text-align: center; + +} +.pagination>a.item{ + display: inline-block; + width: 35px; + height: 35px; + line-height: 35px; + background-color: #F5F6F8; + color: #48586B; + margin: 5px; + border-radius: 5px; +} + +.pagination>a.item.active{ + background-color: var(--dark-teal); + color: #FFF; +} +.alphabet-filter{ + text-align: center; +} +.alphabet-filter>a.item{ + display: inline-block; + width: 35px; + height: 35px; + line-height: 35px; + background-color: #F5F6F8; + color: #48586B; + margin: 5px; + border-radius: 5px; +} +.alphabet-filter>a.item.active{ + background-color: var(--dark-teal); + color: #FFF; +} +.tag{ + padding: 5px; + display: block; + background-color: #F5F6F8; + text-align: center; + border-radius: 2px; + color: #48586B; + border: 1px solid #48586B; + font-weight: bold +} + +.tag.green{ + background-color: #E0F5EE; + border: 1px solid #4BC097; + color: #4BC097; +} +.tag.red{ + background-color: #ffe4e4; + border: 1px solid #ff6a6a; + color: #ff6a6a; +} +.tag.orange{ + background-color: #FFF0E4; + border: 1px solid #FFB06A; + color: #FFB06A; +} +.tag.purple{ + background-color: #fae4ff; + border: 1px solid #e66aff; + color: #e66aff; +} +.tag.blue{ + background-color: #E7E9FD; + border: 1px solid #7E89F1; + color: #7E89F1; +} +.tag.teal{ + background-color: #b3f5f9; + border: 1px solid #359ab3; + color: #359ab3; +} + +.icon-btn { + display: block; + width: 35px; + height: 35px; + text-align: center; + line-height: 35px; + background-color: #F5F6F8; + color: #48586B; + border: none; + border-radius: 0.5rem; + outline: none; +} +.icon-btn:hover{ + background-color: #dfe0e1; + color: #FFF; +} +.icon-btn>.icon{ + display: block; + height: 100%; + width: 100%; + background-size: 20px; + background-position: center; + background-repeat: no-repeat; + margin: auto; + background-color: #8F8F8F; +} +.icon-btn.primary{ + background-color: var(--dark-teal); +} +.icon-btn.primary>.icon{ + background-color: #FFF; +} +.icon-btn.primary:hover{ + background-color: var(--darker-teal); +} +.icon-btn.red{ + background-color: rgb(202, 34, 34); +} +.icon-btn.red>.icon{ + background-color: #FFF; +} +.icon-btn.red:hover{ + background-color: rgb(148, 25, 25); +} + +.actions > .icon-btn{ + margin-top: 10px; +} + +.heading-section{ + display: flex; + justify-content: space-between; + width:90%; + margin-left: auto; + margin-right: auto; + text-align: center; + margin-bottom: 15px; +} \ No newline at end of file diff --git a/Back-End/static/img/icons/arrow-square-up.svg b/Back-End/static/img/icons/arrow-square-up.svg new file mode 100644 index 0000000..0e9d79a --- /dev/null +++ b/Back-End/static/img/icons/arrow-square-up.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Back-End/static/img/icons/book.svg b/Back-End/static/img/icons/book.svg new file mode 100644 index 0000000..fe2a8f6 --- /dev/null +++ b/Back-End/static/img/icons/book.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Back-End/static/img/icons/briefcase.svg b/Back-End/static/img/icons/briefcase.svg new file mode 100644 index 0000000..0848124 --- /dev/null +++ b/Back-End/static/img/icons/briefcase.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Back-End/static/img/icons/calculator.svg b/Back-End/static/img/icons/calculator.svg new file mode 100644 index 0000000..db4f5ae --- /dev/null +++ b/Back-End/static/img/icons/calculator.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Back-End/static/img/icons/directbox-send.svg b/Back-End/static/img/icons/directbox-send.svg new file mode 100644 index 0000000..0f1fcf4 --- /dev/null +++ b/Back-End/static/img/icons/directbox-send.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Back-End/static/img/icons/edit.svg b/Back-End/static/img/icons/edit.svg new file mode 100644 index 0000000..e677bc1 --- /dev/null +++ b/Back-End/static/img/icons/edit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Back-End/static/img/icons/key.svg b/Back-End/static/img/icons/key.svg new file mode 100644 index 0000000..de8b369 --- /dev/null +++ b/Back-End/static/img/icons/key.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Back-End/static/img/icons/profile-add.svg b/Back-End/static/img/icons/profile-add.svg new file mode 100644 index 0000000..720b66b --- /dev/null +++ b/Back-End/static/img/icons/profile-add.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Back-End/static/img/icons/receipt-edit.svg b/Back-End/static/img/icons/receipt-edit.svg new file mode 100644 index 0000000..2fba4e0 --- /dev/null +++ b/Back-End/static/img/icons/receipt-edit.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Back-End/static/img/icons/teacher.svg b/Back-End/static/img/icons/teacher.svg new file mode 100644 index 0000000..e931d22 --- /dev/null +++ b/Back-End/static/img/icons/teacher.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Back-End/static/img/icons/user-add.svg b/Back-End/static/img/icons/user-add.svg new file mode 100644 index 0000000..197ab44 --- /dev/null +++ b/Back-End/static/img/icons/user-add.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Back-End/static/img/icons/user-line.svg b/Back-End/static/img/icons/user-line.svg new file mode 100644 index 0000000..1060e9f --- /dev/null +++ b/Back-End/static/img/icons/user-line.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Back-End/static/img/icons/user-minus.svg b/Back-End/static/img/icons/user-minus.svg new file mode 100644 index 0000000..ad4bf1a --- /dev/null +++ b/Back-End/static/img/icons/user-minus.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Back-End/static/img/icons/user-search.svg b/Back-End/static/img/icons/user-search.svg new file mode 100644 index 0000000..fb5a1b2 --- /dev/null +++ b/Back-End/static/img/icons/user-search.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Back-End/static/img/icons/user.svg b/Back-End/static/img/icons/user.svg new file mode 100644 index 0000000..bba1b0b --- /dev/null +++ b/Back-End/static/img/icons/user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Back-End/static/img/logo_min.svg b/Back-End/static/img/logo_min.svg new file mode 100644 index 0000000..ddcbde6 --- /dev/null +++ b/Back-End/static/img/logo_min.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Back-End/static/index.html b/Back-End/static/index.html new file mode 100644 index 0000000..5d7bef5 --- /dev/null +++ b/Back-End/static/index.html @@ -0,0 +1,48 @@ + + + + + + + Monteschool + + + +
+
+ +
+ + + + +
+
+
+ +
+ + + + +
+
+ Connexion +  
+ +
+ + diff --git a/Back-End/updateApp.py b/Back-End/updateApp.py new file mode 100644 index 0000000..6276872 --- /dev/null +++ b/Back-End/updateApp.py @@ -0,0 +1,5 @@ +import subprocess +process=subprocess.Popen(["docker-compose", "down", "app"]) +process.wait() +process=subprocess.Popen(["docker-compose", "up", "-d", "app"]) +process.wait() \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/Front-End/.env b/Front-End/.env new file mode 100644 index 0000000..f95783e --- /dev/null +++ b/Front-End/.env @@ -0,0 +1,2 @@ +NEXT_PUBLIC_API_URL=http://localhost:8080 +NEXT_PUBLIC_USE_FAKE_DATA='false' \ No newline at end of file diff --git a/Front-End/.eslintrc.json b/Front-End/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/Front-End/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/Front-End/.gitignore b/Front-End/.gitignore new file mode 100644 index 0000000..fd3dbb5 --- /dev/null +++ b/Front-End/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/Front-End/.vscode/settings.json b/Front-End/.vscode/settings.json new file mode 100644 index 0000000..c99754e --- /dev/null +++ b/Front-End/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "i18n-ally.localesPaths": [ + "messages" + ], + "i18n-ally.keystyle": "nested" +} \ No newline at end of file diff --git a/Front-End/README.md b/Front-End/README.md new file mode 100644 index 0000000..09a8a4d --- /dev/null +++ b/Front-End/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/Front-End/jsconfig.json b/Front-End/jsconfig.json new file mode 100644 index 0000000..b8d6842 --- /dev/null +++ b/Front-End/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/Front-End/messages/en/ResponsableInputFields.json b/Front-End/messages/en/ResponsableInputFields.json new file mode 100644 index 0000000..36105a6 --- /dev/null +++ b/Front-End/messages/en/ResponsableInputFields.json @@ -0,0 +1,13 @@ + +{ + "responsable": "Guardian", + "delete": "Delete", + "lastname": "Last name", + "firstname": "First name", + "email": "Email", + "phone": "Phone", + "birthdate": "Birth date", + "profession": "Profession", + "address": "Address", + "add_responsible": "Add guardian" +} \ No newline at end of file diff --git a/Front-End/messages/en/dashboard.json b/Front-End/messages/en/dashboard.json new file mode 100644 index 0000000..5c10d64 --- /dev/null +++ b/Front-End/messages/en/dashboard.json @@ -0,0 +1,9 @@ +{ + "dashboard": "Dashboard", + "totalStudents": "Total Students", + "averageInscriptionTime": "Average Registration Time", + "reInscriptionRate": "Re-enrollment Rate", + "structureCapacity": "Structure Capacity", + "inscriptionTrends": "Enrollment Trends", + "upcomingEvents": "Upcoming Events" +} \ No newline at end of file diff --git a/Front-End/messages/en/homePage.json b/Front-End/messages/en/homePage.json new file mode 100644 index 0000000..c3d3d78 --- /dev/null +++ b/Front-End/messages/en/homePage.json @@ -0,0 +1,5 @@ +{ + "welcomeParents": "Welcome Parents", + "pleaseLogin": "Please login to access your account", + "loginButton": "Go to login page" +} \ No newline at end of file diff --git a/Front-End/messages/en/pagination.json b/Front-End/messages/en/pagination.json new file mode 100644 index 0000000..7b8cb98 --- /dev/null +++ b/Front-End/messages/en/pagination.json @@ -0,0 +1,6 @@ +{ + "page": "Page", + "of": "of", + "previous": "Previous", + "next": "Next" +} \ No newline at end of file diff --git a/Front-End/messages/en/sidebar.json b/Front-End/messages/en/sidebar.json new file mode 100644 index 0000000..7c93cf5 --- /dev/null +++ b/Front-End/messages/en/sidebar.json @@ -0,0 +1,9 @@ +{ + "dashboard": "Dashboard", + "students": "Students", + "structure": "Structure", + "planning": "Schedule", + "grades": "Grades", + "settings": "Settings", + "schoolAdmin": "School Administration" +} \ No newline at end of file diff --git a/Front-End/messages/en/students.json b/Front-End/messages/en/students.json new file mode 100644 index 0000000..a44b858 --- /dev/null +++ b/Front-End/messages/en/students.json @@ -0,0 +1,30 @@ +{ + "headerBarTitle": "Administration", + "addStudent": "New", + "allStudents": "All Students", + "pending": "Pending Registrations", + "archived": "Archived", + "name": "Name", + "class": "Class", + "status": "Status", + "attendance": "Attendance", + "lastEvaluation": "Last Evaluation", + "active": "Active", + "pendingStatus": "Pending", + "goodAttendance": "Good", + "averageAttendance": "Average", + "lowAttendance": "Poor", + "searchStudent": "Search for a student...", + "title": "Registration", + "information": "Information", + "no_records": "There are currently no registration records.", + "add_button": "Add", + "create_first_record": "Please click the ADD button to create your first registration record.", + "studentName":"Student name", + "studentFistName":"Student first name", + "mainContactMail":"Main contact email", + "phone":"Phone", + "lastUpdateDate":"Last update", + "registrationFileStatus":"Registration file status", + "files":"Files" +} \ No newline at end of file diff --git a/Front-End/messages/fr/ResponsableInputFields.json b/Front-End/messages/fr/ResponsableInputFields.json new file mode 100644 index 0000000..86ab314 --- /dev/null +++ b/Front-End/messages/fr/ResponsableInputFields.json @@ -0,0 +1,13 @@ + +{ + "responsable": "Responsable", + "delete": "Supprimer", + "lastname": "Nom", + "firstname": "Prénom", + "email": "Email", + "phone": "Téléphone", + "birthdate": "Date de naissance", + "profession": "Profession", + "address": "Adresse", + "add_responsible": "Ajouter un responsable" +} diff --git a/Front-End/messages/fr/dashboard.json b/Front-End/messages/fr/dashboard.json new file mode 100644 index 0000000..4ac34ef --- /dev/null +++ b/Front-End/messages/fr/dashboard.json @@ -0,0 +1,9 @@ +{ + "dashboard": "Tableau de bord", + "totalStudents": "Total des étudiants", + "averageInscriptionTime": "Temps moyen d'inscription", + "reInscriptionRate": "Taux de réinscription", + "structureCapacity": "Remplissage de la structure", + "inscriptionTrends": "Tendances d'inscription", + "upcomingEvents": "Événements à venir" +} \ No newline at end of file diff --git a/Front-End/messages/fr/homePage.json b/Front-End/messages/fr/homePage.json new file mode 100644 index 0000000..21561d1 --- /dev/null +++ b/Front-End/messages/fr/homePage.json @@ -0,0 +1,5 @@ +{ + "welcomeParents": "Bienvenue aux parents", + "pleaseLogin": "Veuillez vous connecter pour accéder à votre compte", + "loginButton": "Accéder à la page de login" +} \ No newline at end of file diff --git a/Front-End/messages/fr/pagination.json b/Front-End/messages/fr/pagination.json new file mode 100644 index 0000000..60f6f77 --- /dev/null +++ b/Front-End/messages/fr/pagination.json @@ -0,0 +1,6 @@ +{ + "page": "Page", + "of": "sur", + "previous": "Précédent", + "next": "Suivant" +} \ No newline at end of file diff --git a/Front-End/messages/fr/sidebar.json b/Front-End/messages/fr/sidebar.json new file mode 100644 index 0000000..79610e7 --- /dev/null +++ b/Front-End/messages/fr/sidebar.json @@ -0,0 +1,9 @@ + { + "dashboard": "Tableau de bord", + "students": "Élèves", + "structure": "Structure", + "planning": "Emploi du temps", + "grades": "Notes", + "settings": "Paramètres", + "schoolAdmin": "Administration Scolaire" + } \ No newline at end of file diff --git a/Front-End/messages/fr/students.json b/Front-End/messages/fr/students.json new file mode 100644 index 0000000..302e57d --- /dev/null +++ b/Front-End/messages/fr/students.json @@ -0,0 +1,30 @@ +{ + "headerBarTitle":"Administration", + "addStudent": "Nouveau", + "allStudents": "Tous les élèves", + "pending": "Inscriptions en attente", + "archived": "Archivés", + "name": "Nom", + "class": "Classe", + "status": "Statut", + "attendance": "Assiduité", + "lastEvaluation": "Dernière évaluation", + "active": "Actif", + "pendingStatus": "En attente", + "goodAttendance": "Bonne", + "averageAttendance": "Moyenne", + "lowAttendance": "Faible", + "searchStudent": "Rechercher un élève...", + "title": "Inscription", + "information": "Information", + "no_records": "Il n'y a actuellement aucun dossier d'inscription.", + "add_button": "Ajouter", + "create_first_record": "Veuillez cliquer sur le bouton AJOUTER pour créer votre premier dossier d'inscription.", + "studentName":"Nom de l'élève", + "studentFistName":"Prénom de l'élève", + "mainContactMail":"Email de contact principal", + "phone":"Téléphone", + "lastUpdateDate":"Dernière mise à jour", + "registrationFileStatus":"État du dossier d'inscription", + "files":"Fichiers" +} \ No newline at end of file diff --git a/Front-End/next.config.mjs b/Front-End/next.config.mjs new file mode 100644 index 0000000..ff2db5f --- /dev/null +++ b/Front-End/next.config.mjs @@ -0,0 +1,8 @@ +import createNextIntlPlugin from 'next-intl/plugin'; + +const withNextIntl = createNextIntlPlugin(); + +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default withNextIntl(nextConfig); \ No newline at end of file diff --git a/Front-End/package-lock.json b/Front-End/package-lock.json new file mode 100644 index 0000000..6cf2c1b --- /dev/null +++ b/Front-End/package-lock.json @@ -0,0 +1,5642 @@ +{ + "name": "n3wt-school-front-end", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "n3wt-school-front-end", + "version": "0.0.1", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.2", + "@tailwindcss/forms": "^0.5.9", + "date-fns": "^4.1.0", + "framer-motion": "^11.11.11", + "ics": "^3.8.1", + "lucide-react": "^0.453.0", + "next": "14.2.11", + "next-intl": "^3.24.0", + "react": "^18", + "react-cookie": "^7.2.0", + "react-dom": "^18", + "react-phone-number-input": "^3.4.8", + "react-tooltip": "^5.28.0" + }, + "devDependencies": { + "@babel/parser": "^7.26.2", + "@babel/traverse": "^7.25.9", + "autoprefixer": "^10.4.20", + "eslint": "^8", + "eslint-config-next": "14.2.11", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14" + } + }, + "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==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.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-string-parser": { + "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==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/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/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "dependencies": { + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", + "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.1.tgz", + "integrity": "sha512-O4ywpkdJybrjFc9zyL8qK5aklleIAi5O4nYhBVJaOFtCkNrnU+lKFeJOFC48zpsZQmR8Aok2V79hGpHnzbmFpg==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.2", + "@formatjs/intl-localematcher": "0.5.6", + "tslib": "2" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.2.tgz", + "integrity": "sha512-mzxZcS0g1pOzwZTslJOBTmLzDXseMLLvnh25ymRilCm8QLMObsQ7x/rj9GNrH0iUhZMlFisVOD6J1n6WQqpKPQ==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.1.tgz", + "integrity": "sha512-7AYk4tjnLi5wBkxst2w7qFj38JLMJoqzj7BhdEl7oTlsWMlqwgx4p9oMmmvpXWTSDGNwOKBRc1SfwMh5MOHeNg==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.1", + "@formatjs/icu-skeleton-parser": "1.8.5", + "tslib": "2" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.5.tgz", + "integrity": "sha512-zRZ/e3B5qY2+JCLs7puTzWS1Jb+t/K+8Jur/gEZpA2EjWeLDE17nsx8thyo9P48Mno7UmafnPupV2NCJXX17Dg==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.1", + "tslib": "2" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.6.tgz", + "integrity": "sha512-roz1+Ba5e23AHX6KUAWmLEyTRZegM5YDuxuvkHCyK3RJddf/UXB2f+s7pOMm9ktfPGla0g+mQXOn5vsuYirnaA==", + "dependencies": { + "tslib": "2" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "14.2.11", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "14.2.11", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "10.3.10" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.11.tgz", + "integrity": "sha512-eiY9u7wEJZWp/Pga07Qy3ZmNEfALmmSS1HtsJF3y1QEyaExu7boENz11fWqDmZ3uvcyAxCMhTrA1jfVxITQW8g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.11.tgz", + "integrity": "sha512-lnB0zYCld4yE0IX3ANrVMmtAbziBb7MYekcmR6iE9bujmgERl6+FK+b0MBq0pl304lYe7zO4yxJus9H/Af8jbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.11.tgz", + "integrity": "sha512-Ulo9TZVocYmUAtzvZ7FfldtwUoQY0+9z3BiXZCLSUwU2bp7GqHA7/bqrfsArDlUb2xeGwn3ZuBbKtNK8TR0A8w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.11.tgz", + "integrity": "sha512-fH377DnKGyUnkWlmUpFF1T90m0dADBfK11dF8sOQkiELF9M+YwDRCGe8ZyDzvQcUd20Rr5U7vpZRrAxKwd3Rzg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.11.tgz", + "integrity": "sha512-a0TH4ZZp4NS0LgXP/488kgvWelNpwfgGTUCDXVhPGH6pInb7yIYNgM4kmNWOxBFt+TIuOH6Pi9NnGG4XWFUyXQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.11.tgz", + "integrity": "sha512-DYYZcO4Uir2gZxA4D2JcOAKVs8ZxbOFYPpXSVIgeoQbREbeEHxysVsg3nY4FrQy51e5opxt5mOHl/LzIyZBoKA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.11.tgz", + "integrity": "sha512-PwqHeKG3/kKfPpM6of1B9UJ+Er6ySUy59PeFu0Un0LBzJTRKKAg2V6J60Yqzp99m55mLa+YTbU6xj61ImTv9mg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.11.tgz", + "integrity": "sha512-0U7PWMnOYIvM74GY6rbH6w7v+vNPDVH1gUhlwHpfInJnNe5LkmUZqhp7FNWeNa5wbVgRcRi1F1cyxp4dmeLLvA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.11", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.9.tgz", + "integrity": "sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==", + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/react": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz", + "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.2.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.2.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "dev": true, + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.12.1", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "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/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==" + }, + "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==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.1.3", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.0", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001672", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001672.tgz", + "integrity": "sha512-XhW1vRo1ob6aeK2w3rTohwTPBLse/rvjq+s3RTSBwnlZqoFFjx9cHsShJjAIbLsLjyoacaTxpLZy9v3gg6zypw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "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==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/client-only": { + "version": "0.0.1", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/country-flag-icons": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.13.tgz", + "integrity": "sha512-4JwHNqaKZ19doQoNcBjsoYA+I7NqCH/mC/6f5cBWvdKzcK5TMmzLpq3Z/syVHMHJuDGFwJ+rPpGizvrqJybJow==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-fns": { + "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": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "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.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.47", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.47.tgz", + "integrity": "sha512-zS5Yer0MOYw4rtK2iq43cJagHZ8sXN0jDHDKzB+86gSBSAI4v07S97mcq+Gs2vclAxSh1j7vOAHxSVgduiiuVQ==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "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-iterator-helpers": { + "version": "1.0.19", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "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.3", + "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-config-next": { + "version": "14.2.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "14.2.11", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.3", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.5", + "enhanced-resolve": "^5.15.0", + "eslint-module-utils": "^2.8.1", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.11.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.30.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "~5.1.3", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.19", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.36.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.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/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "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-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "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/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "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" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "11.11.11", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.11.11.tgz", + "integrity": "sha512-tuDH23ptJAKUHGydJQII9PhABNJBpB+z0P1bmgKK9QFIssHGlfPd6kxMq00LSKwE27WFsb2z0ovY0bpUyMvfRw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "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==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/ics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/ics/-/ics-3.8.1.tgz", + "integrity": "sha512-UqQlfkajfhrS4pUGQfGIJMYz/Jsl/ob3LqcfEhUmLbwumg+ZNkU0/6S734Vsjq3/FYNpEcZVKodLBoe+zBM69g==", + "dependencies": { + "nanoid": "^3.1.23", + "runes2": "^1.1.2", + "yup": "^1.2.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/input-format": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/input-format/-/input-format-0.3.10.tgz", + "integrity": "sha512-5cFv/kOZD7Ch0viprVkuYPDkAU7HBZYBx8QrIpQ6yXUWbAQ0+RQ8IIojDJOf/RO6FDJLL099HDSK2KoVZ2zevg==", + "dependencies": { + "prop-types": "^15.8.1" + } + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/intl-messageformat": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.3.tgz", + "integrity": "sha512-AAo/3oyh7ROfPhDuh7DxTTydh97OC+lv7h1Eq5LuHWuLsUMKOhtzTYuyXlUReuwZ9vANDHo4CS1bGRrn7TZRtg==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.1", + "@formatjs/fast-memoize": "2.2.2", + "@formatjs/icu-messageformat-parser": "2.9.1", + "tslib": "2" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "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.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "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-bun-module": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "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.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "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.13", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.8.tgz", + "integrity": "sha512-0fv/YKpJBAgXKy0kaS3fnqoUVN8901vUYAKIGD/MWZaDfhJt1nZjPL3ZzdZBt/G8G8Hw2J1xOIrXWdNHFHPAvg==" + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "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==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "license": "ISC" + }, + "node_modules/lucide-react": { + "version": "0.453.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.453.0.tgz", + "integrity": "sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "14.2.11", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.11", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.11", + "@next/swc-darwin-x64": "14.2.11", + "@next/swc-linux-arm64-gnu": "14.2.11", + "@next/swc-linux-arm64-musl": "14.2.11", + "@next/swc-linux-x64-gnu": "14.2.11", + "@next/swc-linux-x64-musl": "14.2.11", + "@next/swc-win32-arm64-msvc": "14.2.11", + "@next/swc-win32-ia32-msvc": "14.2.11", + "@next/swc-win32-x64-msvc": "14.2.11" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-intl": { + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.24.0.tgz", + "integrity": "sha512-48X68QsI92grir2dH1W15yhyVnEjW4c9qmwNt+du+k6mI1QtlE6GyANWHoL4/leTixHv8knZ1y9B/Ys06gmKLg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "negotiator": "^1.0.0", + "use-intl": "^3.24.0" + }, + "peerDependencies": { + "next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0" + } + }, + "node_modules/next-intl/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "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==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "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==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "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==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "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==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-cookie": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.2.0.tgz", + "integrity": "sha512-mqhPERUyfOljq5yJ4woDFI33bjEtigsl8JDJdPPeNhr0eSVZmBc/2Vdf8mFxOUktQxhxTR1T+uF0/FRTZyBEgw==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.5", + "hoist-non-react-statics": "^3.3.2", + "universal-cookie": "^7.0.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "node_modules/react-phone-number-input": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/react-phone-number-input/-/react-phone-number-input-3.4.8.tgz", + "integrity": "sha512-CoJ0OrG42NtAoAl78xCbw4nuhNc4llCxfcS8zB8wSzY1IJ5ib0nSHkOeKqIfm1rzZhTnwjxgecA8wEtzlargUg==", + "dependencies": { + "classnames": "^2.5.1", + "country-flag-icons": "^1.5.11", + "input-format": "^0.3.10", + "libphonenumber-js": "^1.11.8", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-tooltip": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz", + "integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==", + "dependencies": { + "@floating-ui/dom": "^1.6.1", + "classnames": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "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==", + "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==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.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": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "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", + "dev": true, + "license": "ISC", + "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/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/runes2": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz", + "integrity": "sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==" + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "license": "MIT", + "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/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "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==", + "dependencies": { + "@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": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", + "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "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==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "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==" + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.7.0", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.6.2", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/universal-cookie": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.0.tgz", + "integrity": "sha512-PvcyflJAYACJKr28HABxkGemML5vafHmiL4ICe3e+BEKXRMt0GaFLZhAwgv637kFFnnfiSJ8e6jknrKkMrU+PQ==", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-intl": { + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.24.0.tgz", + "integrity": "sha512-lmrARod7yjMYehbyY9xBLjjgnlNcJsl1UAltAPlgspRG7RH6H0JYaGo4C3PZW/BTy0Dgmcvcl8rH/VemzGIhgQ==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^2.2.0", + "intl-messageformat": "^10.5.14" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0" + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/which": { + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "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-builtin-type": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "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", + "license": "MIT", + "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/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yup": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", + "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/Front-End/package.json b/Front-End/package.json new file mode 100644 index 0000000..29d968c --- /dev/null +++ b/Front-End/package.json @@ -0,0 +1,36 @@ +{ + "name": "n3wt-school-front-end", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "check-strings": "node scripts/check-hardcoded-strings.js" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.1.2", + "@tailwindcss/forms": "^0.5.9", + "date-fns": "^4.1.0", + "framer-motion": "^11.11.11", + "ics": "^3.8.1", + "lucide-react": "^0.453.0", + "next": "14.2.11", + "next-intl": "^3.24.0", + "react": "^18", + "react-cookie": "^7.2.0", + "react-dom": "^18", + "react-phone-number-input": "^3.4.8", + "react-tooltip": "^5.28.0" + }, + "devDependencies": { + "@babel/parser": "^7.26.2", + "@babel/traverse": "^7.25.9", + "autoprefixer": "^10.4.20", + "eslint": "^8", + "eslint-config-next": "14.2.11", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14" + } +} \ No newline at end of file diff --git a/Front-End/postcss.config.js b/Front-End/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/Front-End/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/Front-End/project.inlang/.gitignore b/Front-End/project.inlang/.gitignore new file mode 100644 index 0000000..5e46596 --- /dev/null +++ b/Front-End/project.inlang/.gitignore @@ -0,0 +1 @@ +cache \ No newline at end of file diff --git a/Front-End/project.inlang/project_id b/Front-End/project.inlang/project_id new file mode 100644 index 0000000..f461c3d --- /dev/null +++ b/Front-End/project.inlang/project_id @@ -0,0 +1 @@ +2ff5cbbb4bc1c6d178400871dfa342ac4f0b18e9b86cb64a1110be1ec54238c1 \ No newline at end of file diff --git a/Front-End/project.inlang/settings.json b/Front-End/project.inlang/settings.json new file mode 100644 index 0000000..fb6c9ff --- /dev/null +++ b/Front-End/project.inlang/settings.json @@ -0,0 +1,12 @@ +{ + // official schema ensures that your project file is valid + "$schema": "https://inlang.com/schema/project-settings", + // the "source" language tag that is used in your project + "sourceLanguageTag": "fr", + // all the language tags you want to support in your project + "languageTags": ["fr", "en"], + "modules": [ + "https://cdn.jsdelivr.net/npm/@inlang/plugin-json@4/dist/index.js" + ], // or use another storage module: https://inlang.com/c/plugins (i18next, json, inlang message format) + "settings": {} +} \ No newline at end of file diff --git a/Front-End/scripts/check-hardcoded-strings.js b/Front-End/scripts/check-hardcoded-strings.js new file mode 100644 index 0000000..db1a771 --- /dev/null +++ b/Front-End/scripts/check-hardcoded-strings.js @@ -0,0 +1,192 @@ +// scripts/check-hardcoded-strings.js +const fs = require('fs'); +const path = require('path'); +const babel = require('@babel/parser'); +const traverse = require('@babel/traverse').default; + +// Patterns pour les classes Tailwind +const TAILWIND_PATTERNS = [ + // Layout + /^(container|flex|grid|block|inline|hidden)/, + // Spacing + /^(m|p|mt|mb|ml|mr|mx|my|pt|pb|pl|pr|px|py)-[0-9]+/, + // Sizing + /^(w|h|min-w|min-h|max-w|max-h)-[0-9]+/, + // Typography + /^(text|font|leading)-/, + // Backgrounds + /^bg-/, + // Borders + /^(border|rounded|divide)-/, + // Effects + /^(shadow|opacity|transition)-/, + // Interactivity + /^(cursor|pointer-events|select|resize)-/, + // Layout + /^(z|top|right|bottom|left|float|clear)-/, + // Flexbox & Grid + /^(justify|items|content|self|place|gap)-/, + // États + /^(hover|focus|active|disabled|group|dark):/, + // Couleurs + /-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]+$/ +]; + +// Nouveaux patterns pour ignorer les logs et imports +const CODE_PATTERNS = [ + // Console et debug avec contenu + /console\.(log|error|warn|info|debug)\(['"].*['"]/, + /^console\.(log|error|warn|info|debug)\(/, + /^debugger/, + + // Imports et requires avec leurs chaînes + /^import\s+.*\s+from\s+['"].*['"]/, + /^import\s+['"].*['"]/, + /^require\(['"].*['"]\)/, + /^from\s+['"].*['"]/, + /^@/, + + // Autres cas courants + /^process\.env\./, + /^module\.exports/, + /^export\s+/, +]; + +function isHardcodedString(str) { + // Ignorer les chaînes vides ou trop courtes + if (!str || str.length <= 1) return false; + + // Vérifier si la chaîne fait partie d'un console.log ou d'un import + const context = str.trim(); + if (CODE_PATTERNS.some(pattern => pattern.test(context))) { + return false; + } + + // Vérifier si c'est une chaîne dans un console.log + if (context.includes('console.log(')) { + return false; + } + + // Vérifier si c'est une chaîne dans un import + if (context.includes('from \'') || context.includes('from "')) { + return false; + } + + // Vérifier si c'est une classe Tailwind + const classes = str.split(' '); + if (classes.some(cls => + TAILWIND_PATTERNS.some(pattern => pattern.test(cls)) + )) { + return false; + } + + + // Autres patterns à ignorer + const IGNORE_PATTERNS = [ + /^[A-Z][A-Za-z]+$/, // Noms de composants + /^@/, // Imports + /^[./]/, // Chemins de fichiers + /^[0-9]+$/, // Nombres + /^style=/, // Props style + /^id=/, // Props id + /^data-/, // Data attributes + /^aria-/, // Aria attributes + /^role=/, // Role attributes + /^className=/, // className attributes + ]; + + return !IGNORE_PATTERNS.some(pattern => pattern.test(str)); +} + +async function scanFile(filePath) { + const content = fs.readFileSync(filePath, 'utf-8'); + const hardcodedStrings = new Map(); // Utiliser Map pour stocker string -> ligne + + try { + const ast = babel.parse(content, { + sourceType: 'module', + plugins: ['jsx', 'typescript'], + locations: true // Active le tracking des positions + }); + + traverse(ast, { + StringLiteral(path) { + const value = path.node.value; + const line = path.node.loc.start.line; + if (isHardcodedString(value)) { + hardcodedStrings.set(value, line); + } + }, + JSXText(path) { + const value = path.node.value.trim(); + const line = path.node.loc.start.line; + if (isHardcodedString(value)) { + hardcodedStrings.set(value, line); + } + }, + }); + } catch (error) { + console.error(`Erreur dans le fichier ${filePath}:`, error); + } + + return Array.from(hardcodedStrings.entries()); +} + +async function scanDirectory(dir) { + const results = {}; + const files = fs.readdirSync(dir); + + for (const file of files) { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') { + Object.assign(results, await scanDirectory(filePath)); + } else if ( + stat.isFile() && + (file.endsWith('.js') || file.endsWith('.jsx') || file.endsWith('.tsx')) + ) { + const strings = await scanFile(filePath); + if (strings.length > 0) { + results[filePath] = strings; + } + } + } + + return results; +} + +async function logStringsToFile(results) { + const outputPath = path.join(process.cwd(), 'hardcoded-strings-report.md'); + let content = '# Rapport des chaînes en dur\n\n'; + let totalStrings = 0; + + for (const [file, strings] of Object.entries(results)) { + if (strings.length > 0) { + const relativePath = path.relative(process.cwd(), file); + content += `\n## ${relativePath}\n\n`; + + strings.forEach(([str, line]) => { + totalStrings++; + content += `- Ligne ${line}: \`${str}\`\n`; + content += ` → [Voir dans le code](${relativePath}:${line}:1)\n\n`; + }); + } + } + + content += `\n## Résumé\n`; + content += `- Total des chaînes trouvées: ${totalStrings}\n`; + content += `- Date du scan: ${new Date().toLocaleString('fr-FR')}\n`; + + fs.writeFileSync(outputPath, content, 'utf8'); + console.log(`\nRapport généré: ${outputPath}`); +} + +// Modifier la fonction main existante +async function main() { + const rootDir = process.cwd(); + const results = await scanDirectory(path.join(rootDir, 'src')); + await logStringsToFile(results); +} + +main().catch(console.error); \ No newline at end of file diff --git a/Front-End/src/app/[locale]/admin/classes/page.js b/Front-End/src/app/[locale]/admin/classes/page.js new file mode 100644 index 0000000..30b6a9d --- /dev/null +++ b/Front-End/src/app/[locale]/admin/classes/page.js @@ -0,0 +1,49 @@ +'use client' +import React, { useState, useEffect } from 'react'; +import Table from '@/components/Table'; +import Button from '@/components/Button'; + +const columns = [ + { name: 'Nom', transform: (row) => row.Nom }, + { name: 'Niveau', transform: (row) => row.Niveau }, + { name: 'Effectif', transform: (row) => row.Effectif }, +]; + +export default function Page() { + const [classes, setClasses] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + + useEffect(() => { + fetchClasses(); + }, [currentPage]); + + const fetchClasses = async () => { + const fakeData = { + classes: [ + { Nom: 'Classe A', Niveau: '1ère année', Effectif: 30 }, + { Nom: 'Classe B', Niveau: '2ème année', Effectif: 25 }, + { Nom: 'Classe C', Niveau: '3ème année', Effectif: 28 }, + ], + totalPages: 3 + }; + setClasses(fakeData.classes); + setTotalPages(fakeData.totalPages); + }; + + const handlePageChange = (page) => { + setCurrentPage(page); + }; + + const handleCreateClass = () => { + console.log('Créer une nouvelle classe'); + }; + + return ( +
+

Gestion des Classes

+
+ ); +}; + +export default PlanningView; \ No newline at end of file diff --git a/Front-End/src/components/Calendar/WeekView.js b/Front-End/src/components/Calendar/WeekView.js new file mode 100644 index 0000000..2804368 --- /dev/null +++ b/Front-End/src/components/Calendar/WeekView.js @@ -0,0 +1,208 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { usePlanning } from '@/context/PlanningContext'; +import { format, startOfWeek, addDays, differenceInMinutes, isSameDay } from 'date-fns'; +import { fr } from 'date-fns/locale'; +import { getWeekEvents } from '@/utils/events'; +import { isToday } from 'date-fns'; + +const WeekView = ({ onDateClick, onEventClick, events }) => { + const { currentDate } = usePlanning(); + const [currentTime, setCurrentTime] = useState(new Date()); + const scrollContainerRef = useRef(null); // Ajouter cette référence + + // Déplacer ces déclarations avant leur utilisation + const timeSlots = Array.from({ length: 24 }, (_, i) => i); + const weekStart = startOfWeek(currentDate, { weekStartsOn: 1 }); + const weekDays = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i)); + + // Maintenant on peut utiliser weekDays + const isCurrentWeek = weekDays.some(day => isSameDay(day, new Date())); + + // Mettre à jour la position de la ligne toutes les minutes + useEffect(() => { + const interval = setInterval(() => { + setCurrentTime(new Date()); + }, 60000); + + return () => clearInterval(interval); + }, []); + + // Modifier l'useEffect pour l'auto-scroll + useEffect(() => { + if (scrollContainerRef.current && isCurrentWeek) { + const currentHour = new Date().getHours(); + const scrollPosition = currentHour * 80; + // Ajout d'un délai pour laisser le temps au DOM de se mettre à jour + setTimeout(() => { + scrollContainerRef.current.scrollTop = scrollPosition - 200; + }, 0); + } + }, [currentDate, isCurrentWeek]); // Ajout de currentDate dans les dépendances + + // Calculer la position de la ligne de temps + const getCurrentTimePosition = () => { + const hours = currentTime.getHours(); + const minutes = currentTime.getMinutes(); + return `${(hours + minutes / 60) * 5}rem`; + }; + + // Utiliser les événements déjà filtrés passés en props + const weekEventsMap = weekDays.reduce((acc, day) => { + acc[format(day, 'yyyy-MM-dd')] = getWeekEvents(day, events); + return acc; + }, {}); + + const isWeekend = (date) => { + const day = date.getDay(); + return day === 0 || day === 6; + }; + + const findOverlappingEvents = (event, dayEvents) => { + const eventStart = new Date(event.start); + const eventEnd = new Date(event.end); + + return dayEvents.filter(otherEvent => { + if (otherEvent.id === event.id) return false; + const otherStart = new Date(otherEvent.start); + const otherEnd = new Date(otherEvent.end); + return !(otherEnd <= eventStart || otherStart >= eventEnd); + }); + }; + + const calculateEventStyle = (event, dayEvents) => { + const start = new Date(event.start); + const end = new Date(event.end); + const startMinutes = (start.getMinutes() / 60) * 5; + const duration = ((end - start) / (1000 * 60 * 60)) * 5; + + // Trouver les événements qui se chevauchent + const overlappingEvents = findOverlappingEvents(event, dayEvents); + const eventIndex = overlappingEvents.findIndex(e => e.id > event.id) + 1; + const totalOverlapping = overlappingEvents.length + 1; + + // Calculer la largeur et la position horizontale + const width = `calc((100% / ${totalOverlapping}) - 4px)`; + const left = `calc((100% / ${totalOverlapping}) * ${eventIndex})`; + + return { + height: `${duration}rem`, + position: 'absolute', + width, + left, + backgroundColor: `${event.color}15`, + borderLeft: `3px solid ${event.color}`, + borderRadius: '0.25rem', + zIndex: 1, + transform: `translateY(${startMinutes}rem)` + }; + }; + + const renderEventInCell = (event, dayEvents) => { + const eventStyle = calculateEventStyle(event, dayEvents); + + return ( +
{ + e.stopPropagation(); + onEventClick(event); + }} + > +
+
+ {event.title} +
+
+ {format(new Date(event.start), 'HH:mm')} - {format(new Date(event.end), 'HH:mm')} +
+ {event.location && ( +
+ {event.location} +
+ )} +
+
+ ); + }; + + return ( +
+ {/* En-tête des jours */} +
+
+ {weekDays.map((day) => ( +
+
+ {format(day, 'EEEE', { locale: fr })} +
+
+ {format(day, 'd', { locale: fr })} +
+
+ ))} +
+ + {/* Grille horaire */} +
+ {/* Ligne de temps actuelle */} + {isCurrentWeek && ( +
+
+
+ )} + +
+ {timeSlots.map(hour => ( + +
+ {`${hour.toString().padStart(2, '0')}:00`} +
+ {weekDays.map((day) => { + const dayKey = format(day, 'yyyy-MM-dd'); + const dayEvents = weekEventsMap[dayKey] || []; + return ( +
{ + const date = new Date(day); + date.setHours(hour); + onDateClick(date); + }} + > +
{/* Ajout de gap-1 */} + {dayEvents.filter(event => { + const eventStart = new Date(event.start); + return eventStart.getHours() === hour; + }).map(event => renderEventInCell(event, dayEvents))} +
+
+ ); + })} +
+ ))} +
+
+
+ ); +}; + +export default WeekView; \ No newline at end of file diff --git a/Front-End/src/components/Calendar/YearView.js b/Front-End/src/components/Calendar/YearView.js new file mode 100644 index 0000000..0f31967 --- /dev/null +++ b/Front-End/src/components/Calendar/YearView.js @@ -0,0 +1,52 @@ +import React from 'react'; +import { usePlanning } from '@/context/PlanningContext'; +import { format } from 'date-fns'; +import { fr } from 'date-fns/locale'; +import { getMonthEventCount } from '@/utils/events'; +import { isSameMonth } from 'date-fns'; + +const MonthCard = ({ month, eventCount, onClick }) => ( +
+

+ {format(month, 'MMMM', { locale: fr })} +

+
+ + {eventCount} événements + +
+
+); + +const YearView = ({ onDateClick }) => { + const { currentDate, events, setViewType, setCurrentDate } = usePlanning(); + + const months = Array.from( + { length: 12 }, + (_, i) => new Date(currentDate.getFullYear(), i, 1) + ); + + const handleMonthClick = (month) => { + setCurrentDate(month); + setViewType('month'); + }; + + return ( +
+ {months.map(month => ( + handleMonthClick(month)} + /> + ))} +
+ ); +}; + +export default YearView; \ No newline at end of file diff --git a/Front-End/src/components/ClassForm.js b/Front-End/src/components/ClassForm.js new file mode 100644 index 0000000..de0bed6 --- /dev/null +++ b/Front-End/src/components/ClassForm.js @@ -0,0 +1,188 @@ +import React, { useState } from 'react'; +import Slider from '@/components/Slider' + +const ClassForm = ({ classe, onSubmit, isNew, specialities, teachers }) => { + const [formData, setFormData] = useState({ + nom_ambiance: classe.nom_ambiance || '', + tranche_age: classe.tranche_age || [3, 6], + nombre_eleves: classe.nombre_eleves || '', + langue_enseignement: classe.langue_enseignement || 'Français', + annee_scolaire: classe.annee_scolaire || '2024-2025', + specialites_ids: classe.specialites_ids || [], + enseignant_principal_id: classe.enseignant_principal_id || null, + }); + + const handleChange = (e) => { + const { name, value, type } = e.target; + const newValue = type === 'radio' ? parseInt(value) : value; + setFormData((prevState) => ({ + ...prevState, + [name]: newValue, + })); + }; + + const handleSliderChange = (value) => { + console.log('update value : ', value) + setFormData(prevFormData => ({ + ...prevFormData, + tranche_age: value + })); + }; + + const handleNumberChange = (e) => { + const { name, value } = e.target; + setFormData(prevFormData => ({ + ...prevFormData, + [name]: Number(value) + })); + }; + + const handleSubmit = () => { + onSubmit(formData, isNew); + }; + + const handleSpecialityChange = (id) => { + setFormData(prevFormData => { + const specialites_ids = prevFormData.specialites_ids.includes(id) + ? prevFormData.specialites_ids.filter(specialityId => specialityId !== id) + : [...prevFormData.specialites_ids, id]; + return { ...prevFormData, specialites_ids }; + }); + }; + + return ( +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ {specialities.map(speciality => ( +
+ handleSpecialityChange(speciality.id)} + className="h-4 w-4 text-emerald-600 border-gray-300 rounded focus:ring-emerald-500" + /> + +
+ ))} +
+
+
+ +
+ {teachers.map(teacher => ( +
+ + +
+ ))} +
+
+
+ +
+
+ ); +}; + +export default ClassForm; diff --git a/Front-End/src/components/ClassesSection.js b/Front-End/src/components/ClassesSection.js new file mode 100644 index 0000000..5d3175b --- /dev/null +++ b/Front-End/src/components/ClassesSection.js @@ -0,0 +1,111 @@ +import { School, Trash2, MoreVertical, Edit3, Plus, ZoomIn } from 'lucide-react'; +import { useState } from 'react'; +import Table from '@/components/Table'; +import DropdownMenu from '@/components/DropdownMenu'; +import Modal from '@/components/Modal'; +import ClassForm from '@/components/ClassForm'; + +const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleEdit, handleDelete }) => { + + const [isOpen, setIsOpen] = useState(false); + const [editingClass, setEditingClass] = useState(null); + + const openEditModal = (classe) => { + setIsOpen(true); + setEditingClass(classe); + } + + const closeEditModal = () => { + setIsOpen(false); + setEditingClass(null); + }; + + const handleModalSubmit = (updatedData) => { + if (editingClass) { + handleEdit(editingClass.id, updatedData); + } else { + handleCreate(updatedData); + } + closeEditModal(); + }; + + const handleInspect = (data) => { + console.log('inspect classe : ', data) + } + + return ( +
+
+

+ + Classes +

+ +
+
+ row.nom_ambiance }, + { name: 'AGE', transform: (row) => `${row.tranche_age[0]} - ${row.tranche_age[1]} ans` }, + { name: 'NOMBRE D\'ELEVES', transform: (row) => row.nombre_eleves }, + { name: 'LANGUE D\'ENSEIGNEMENT', transform: (row) => row.langue_enseignement }, + { name: 'ANNEE SCOLAIRE', transform: (row) => row.annee_scolaire }, + { + name: 'SPECIALITES', + transform: (row) => ( +
+ {row.specialites.map(specialite => ( + + ))} +
+ ) + }, + { + name: 'ENSEIGNANT PRINCIPAL', + transform: (row) => { + return row.enseignant_principal + ? `${row.enseignant_principal.nom || ''} ${row.enseignant_principal.prenom || ''}` + : Non assigné; + } + }, + { name: 'ACTIONS', transform: (row) => ( + } + items={[ + { label: 'Inspecter', icon: ZoomIn, onClick: () => handleInspect(row) }, + { label: 'Modifier', icon:Edit3, onClick: () => openEditModal(row) }, + { label: 'Supprimer', icon: Trash2, onClick: () => handleDelete(row.id) } + ] + } + buttonClassName="text-gray-400 hover:text-gray-600" + menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-10 flex flex-col items-center" + /> + )} + ]} + data={classes} + /> + + {isOpen && ( + ( + + )} + /> + )} + + ); +}; + +export default ClassesSection; diff --git a/Front-End/src/components/DjangoCSRFToken.js b/Front-End/src/components/DjangoCSRFToken.js new file mode 100644 index 0000000..56270db --- /dev/null +++ b/Front-End/src/components/DjangoCSRFToken.js @@ -0,0 +1,16 @@ +import React, { useEffect } from 'react'; +import { useCookies } from 'react-cookie'; + +export default function DjangoCSRFToken({ csrfToken }) { + const [cookies, setCookie] = useCookies(['csrftoken']); + + useEffect(() => { + if (csrfToken && csrfToken !== cookies.csrftoken) { + setCookie('csrftoken', csrfToken, { path: '/' }); + } + }, [csrfToken, cookies.csrftoken, setCookie]); + + return ( + + ); +} diff --git a/Front-End/src/components/DropdownMenu.js b/Front-End/src/components/DropdownMenu.js new file mode 100644 index 0000000..4c13fe2 --- /dev/null +++ b/Front-End/src/components/DropdownMenu.js @@ -0,0 +1,52 @@ +// Composant générique pour les menus dropdown +import { useRouter } from 'next/navigation'; +import React, { useState, useEffect, useRef } from 'react'; + +const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dropdownOpen: propDropdownOpen, setDropdownOpen: propSetDropdownOpen }) => { + const [dropdownOpen, setDropdownOpen] = useState(false); + const menuRef = useRef(null); + const router = useRouter(); + const isControlled = propDropdownOpen !== undefined && propSetDropdownOpen !== undefined; + const actualDropdownOpen = isControlled ? propDropdownOpen : dropdownOpen; + const actualSetDropdownOpen = isControlled ? propSetDropdownOpen : setDropdownOpen; + + + const handleClickOutside = (event) => { + if (menuRef.current && !menuRef.current.contains(event.target)) { + actualSetDropdownOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + return ( +
+ + {actualDropdownOpen && ( +
+ {items.map((item, index) => ( + + ))} +
+ )} +
+ ); +}; + +export default DropdownMenu; \ No newline at end of file diff --git a/Front-End/src/components/EventModal.js b/Front-End/src/components/EventModal.js new file mode 100644 index 0000000..bcfc80a --- /dev/null +++ b/Front-End/src/components/EventModal.js @@ -0,0 +1,320 @@ +import { usePlanning } from '@/context/PlanningContext'; +import { format } from 'date-fns'; +import React from 'react'; + +export default function EventModal({ isOpen, onClose, eventData, setEventData }) { + const { addEvent, updateEvent, deleteEvent, schedules } = usePlanning(); + + if (!isOpen) return null; + + const recurrenceOptions = [ + { value: 'none', label: 'Aucune' }, + { value: 'daily', label: 'Quotidienne' }, + { value: 'weekly', label: 'Hebdomadaire' }, + { value: 'monthly', label: 'Mensuelle' }, + { value: 'custom', label: 'Personnalisée' } // Nouvelle option + ]; + + const daysOfWeek = [ + { value: 1, label: 'Lun' }, + { value: 2, label: 'Mar' }, + { value: 3, label: 'Mer' }, + { value: 4, label: 'Jeu' }, + { value: 5, label: 'Ven' }, + { value: 6, label: 'Sam' }, + { value: 0, label: 'Dim' } + ]; + + // S'assurer que scheduleId est défini lors du premier rendu + React.useEffect(() => { + if (!eventData.scheduleId && schedules.length > 0) { + setEventData(prev => ({ + ...prev, + scheduleId: schedules[0].id, + color: schedules[0].color + })); + } + }, [schedules, eventData.scheduleId]); + + const handleSubmit = (e) => { + e.preventDefault(); + + if (!eventData.scheduleId) { + alert('Veuillez sélectionner un planning'); + return; + } + + const selectedSchedule = schedules.find(s => s.id === eventData.scheduleId); + + if (eventData.id) { + updateEvent(eventData.id, { + ...eventData, + scheduleId: eventData.scheduleId, // S'assurer que scheduleId est bien défini + color: eventData.color || selectedSchedule?.color + }); + } else { + addEvent({ + ...eventData, + id: `event-${Date.now()}`, + scheduleId: eventData.scheduleId, // S'assurer que scheduleId est bien défini + color: eventData.color || selectedSchedule?.color + }); + } + onClose(); + }; + + const handleDelete = () => { + if (eventData.id && confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')) { + deleteEvent(eventData.id); + onClose(); + } + }; + + return ( +
+
+

+ {eventData.id ? 'Modifier l\'événement' : 'Nouvel événement'} +

+ +
+ {/* Titre */} +
+ + setEventData({ ...eventData, title: e.target.value })} + className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500" + required + /> +
+ + {/* Description */} +
+ +