From b5754ba3ff3469d895081c6bfe89865408e24c4e Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Thu, 14 Dec 2023 15:30:29 +0200 Subject: [PATCH 01/18] Added "highlight_as_new" to MainMenuItem and added Public Entities menu item --- .../header-and-footer/NavBar/index.html | 8 +- .../header-and-footer/NavBar/styles.scss | 15 +- budgetportal/fixtures/menu.json | 460 ++++++++++-------- .../0072_mainmenuitem_highlight_as_new.py | 18 + budgetportal/models/__init__.py | 3 + 5 files changed, 291 insertions(+), 213 deletions(-) create mode 100644 budgetportal/migrations/0072_mainmenuitem_highlight_as_new.py diff --git a/assets/js/components/header-and-footer/NavBar/index.html b/assets/js/components/header-and-footer/NavBar/index.html index d30dad0ec..ea875b892 100644 --- a/assets/js/components/header-and-footer/NavBar/index.html +++ b/assets/js/components/header-and-footer/NavBar/index.html @@ -46,16 +46,20 @@

Navigate to a section on this site:

{% endif %} - {% if item.name == 'homepage' %} {% include 'components/Icon/index.html' with type="home" %} Home {% else %} - {{ item.label }} + {{ item.label }} + {% if item.highlight_as_new %} + New + {% endif %} + {% endif %} + {% if item.url %} diff --git a/assets/scss/components/header-and-footer/NavBar/styles.scss b/assets/scss/components/header-and-footer/NavBar/styles.scss index 53193591c..2843c6fcc 100644 --- a/assets/scss/components/header-and-footer/NavBar/styles.scss +++ b/assets/scss/components/header-and-footer/NavBar/styles.scss @@ -131,6 +131,16 @@ $mobile-break: 900px; } } +.Navbar-Tag-new { + border-radius: 6px; + background-color: #76B643; + color: #fff; + font-size: 14px; + font-weight: 700; + line-height: 16.8px; + padding: 2px 4px; +} + .NavBar-link { display: block; border-bottom: solid 1px #b9b9b9; @@ -141,7 +151,7 @@ $mobile-break: 900px; @media screen and (min-width: $mobile-break) { border-radius: 18px 18px 0 0; - padding: 6px 10px; + padding: 6px 6px; fill: #4a4a4a; user-select: none; font-weight: bold; @@ -191,7 +201,6 @@ $mobile-break: 900px; text-decoration: none; color: #4a4a4a; - @media screen and (min-width: $mobile-break) { display: inline-block; font-weight: normal; @@ -294,7 +303,7 @@ $mobile-break: 900px; position: relative; &::after { - content: ''; + content: ""; display: block; width: 0; height: 0; diff --git a/budgetportal/fixtures/menu.json b/budgetportal/fixtures/menu.json index b7d2a94a0..a1c6fe524 100644 --- a/budgetportal/fixtures/menu.json +++ b/budgetportal/fixtures/menu.json @@ -1,211 +1,255 @@ [ -{ - "model": "budgetportal.mainmenuitem", - "pk": 1, - "fields": { - "name": "revenue-spending", - "label": "Revenue and Spending", - "url": null, - "align_right": false, - "main_menu_item_order": 2 + { + "model": "budgetportal.mainmenuitem", + "pk": 1, + "fields": { + "name": "revenue-spending", + "label": "Revenue and Spending", + "url": null, + "align_right": false, + "main_menu_item_order": 2 + } + }, + { + "model": "budgetportal.mainmenuitem", + "pk": 2, + "fields": { + "name": "about", + "label": "About", + "url": "/about", + "align_right": true, + "main_menu_item_order": 6 + } + }, + { + "model": "budgetportal.mainmenuitem", + "pk": 4, + "fields": { + "name": "homepage", + "label": "Homepage", + "url": "/", + "align_right": false, + "main_menu_item_order": 1 + } + }, + { + "model": "budgetportal.mainmenuitem", + "pk": 5, + "fields": { + "name": "learning", + "label": "Learning", + "url": null, + "align_right": true, + "main_menu_item_order": 7 + } + }, + { + "model": "budgetportal.mainmenuitem", + "pk": 6, + "fields": { + "name": "infrastructure", + "label": "Infrastructure", + "url": null, + "align_right": false, + "main_menu_item_order": 3 + } + }, + { + "model": "budgetportal.mainmenuitem", + "pk": 7, + "fields": { + "name": "data", + "label": "Data and Analysis", + "url": "/datasets", + "align_right": false, + "main_menu_item_order": 5 + } + }, + { + "model": "budgetportal.mainmenuitem", + "pk": 9, + "fields": { + "name": "public-entities", + "label": "Public Entities", + "url": "/public-entities", + "align_right": false, + "main_menu_item_order": 4 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 1, + "fields": { + "parent": 1, + "name": "departments", + "label": "Department budgets", + "url": "/latest/departments", + "sub_menu_item_order": 1 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 4, + "fields": { + "parent": 2, + "name": "background", + "label": "Background", + "url": "/about#background", + "sub_menu_item_order": 4 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 5, + "fields": { + "parent": 2, + "name": "development", + "label": "Development", + "url": "/about#development", + "sub_menu_item_order": 5 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 6, + "fields": { + "parent": 2, + "name": "project-status", + "label": "Project Status", + "url": "/about#project-status", + "sub_menu_item_order": 6 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 7, + "fields": { + "parent": 2, + "name": "your-contribution", + "label": "Your Contribution", + "url": "/about#your-contribution", + "sub_menu_item_order": 7 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 8, + "fields": { + "parent": 2, + "name": "contacts", + "label": "Contacts", + "url": "/about#contacts", + "sub_menu_item_order": 8 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 9, + "fields": { + "parent": 2, + "name": "media-and-other-information", + "label": "Media & Other Information", + "url": "/about#media-and-other-information", + "sub_menu_item_order": 9 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 10, + "fields": { + "parent": 2, + "name": "data-scientists", + "label": "Information for Developers and Data Scientists", + "url": "/about#data-scientists", + "sub_menu_item_order": 10 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 11, + "fields": { + "parent": 5, + "name": "glossary", + "label": "Glossary", + "url": "/learning-resources/glossary", + "sub_menu_item_order": 14 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 12, + "fields": { + "parent": 5, + "name": "resources", + "label": "Resources", + "url": "/learning-resources/resources/", + "sub_menu_item_order": 13 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 13, + "fields": { + "parent": 5, + "name": "guides", + "label": "Dataset Guides", + "url": "/learning-resources/guides/", + "sub_menu_item_order": 12 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 14, + "fields": { + "parent": 6, + "name": "ppp", + "label": "PPP & major national department projects", + "url": "/infrastructure-projects/", + "sub_menu_item_order": 14 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 15, + "fields": { + "parent": 6, + "name": "full", + "label": "Government department projects", + "url": "/infrastructure-projects/full/", + "sub_menu_item_order": 15 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 16, + "fields": { + "parent": 5, + "name": "faq", + "label": "FAQ", + "url": "/faq", + "sub_menu_item_order": 15 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 17, + "fields": { + "parent": 1, + "name": "revenue", + "label": "Division of revenue", + "url": "/learning-resources/guides/division-revenue-raised-nationally/", + "sub_menu_item_order": 17 + } + }, + { + "model": "budgetportal.submenuitem", + "pk": 18, + "fields": { + "parent": 5, + "name": "videos", + "label": "Videos", + "url": "/learning-resources/videos/", + "sub_menu_item_order": 11 + } } -}, -{ - "model": "budgetportal.mainmenuitem", - "pk": 2, - "fields": { - "name": "about", - "label": "About", - "url": "/about", - "align_right": true, - "main_menu_item_order": 6 - } -}, -{ - "model": "budgetportal.mainmenuitem", - "pk": 3, - "fields": { - "name": "faq", - "label": "FAQ", - "url": "/faq", - "align_right": true, - "main_menu_item_order": 7 - } -}, -{ - "model": "budgetportal.mainmenuitem", - "pk": 4, - "fields": { - "name": "homepage", - "label": "Homepage", - "url": "/", - "align_right": false, - "main_menu_item_order": 1 - } -}, -{ - "model": "budgetportal.mainmenuitem", - "pk": 5, - "fields": { - "name": "learning", - "label": "Learning", - "url": "/learning-resources", - "align_right": false, - "main_menu_item_order": 3 - } -}, -{ - "model": "budgetportal.mainmenuitem", - "pk": 6, - "fields": { - "name": "infrastructure", - "label": "Infrastructure", - "url": null, - "align_right": false, - "main_menu_item_order": 4 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 1, - "fields": { - "parent": 1, - "name": "departments", - "label": "Department budgets", - "url": "/latest/departments", - "sub_menu_item_order": 1 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 4, - "fields": { - "parent": 2, - "name": "background", - "label": "Background", - "url": "/about#background", - "sub_menu_item_order": 4 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 5, - "fields": { - "parent": 2, - "name": "development", - "label": "Development", - "url": "/about#development", - "sub_menu_item_order": 5 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 6, - "fields": { - "parent": 2, - "name": "project-status", - "label": "Project Status", - "url": "/about#project-status", - "sub_menu_item_order": 6 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 7, - "fields": { - "parent": 2, - "name": "your-contribution", - "label": "Your Contribution", - "url": "/about#your-contribution", - "sub_menu_item_order": 7 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 8, - "fields": { - "parent": 2, - "name": "contacts", - "label": "Contacts", - "url": "/about#contacts", - "sub_menu_item_order": 8 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 9, - "fields": { - "parent": 2, - "name": "media-and-other-information", - "label": "Media & Other Information", - "url": "/about#media-and-other-information", - "sub_menu_item_order": 9 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 10, - "fields": { - "parent": 2, - "name": "data-scientists", - "label": "Information for Developers and Data Scientists", - "url": "/about#data-scientists", - "sub_menu_item_order": 10 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 11, - "fields": { - "parent": 5, - "name": "glossary", - "label": "Glossary", - "url": "/learning-resources/glossary", - "sub_menu_item_order": 11 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 12, - "fields": { - "parent": 5, - "name": "resources", - "label": "Resources", - "url": "/learning-resources/resources/", - "sub_menu_item_order": 12 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 13, - "fields": { - "parent": 5, - "name": "guides", - "label": "Dataset Guides", - "url": "/learning-resources/guides/", - "sub_menu_item_order": 13 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 14, - "fields": { - "parent": 6, - "name": "ppp", - "label": "PPP & major national department projects", - "url": "/infrastructure-projects/", - "sub_menu_item_order": 14 - } -}, -{ - "model": "budgetportal.submenuitem", - "pk": 15, - "fields": { - "parent": 6, - "name": "full", - "label": "Government department projects", - "url": "/infrastructure-projects/full/", - "sub_menu_item_order": 15 - } -} ] diff --git a/budgetportal/migrations/0072_mainmenuitem_highlight_as_new.py b/budgetportal/migrations/0072_mainmenuitem_highlight_as_new.py new file mode 100644 index 000000000..7a89034ba --- /dev/null +++ b/budgetportal/migrations/0072_mainmenuitem_highlight_as_new.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.20 on 2023-12-14 13:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('budgetportal', '0071_auto_20230605_1521'), + ] + + operations = [ + migrations.AddField( + model_name='mainmenuitem', + name='highlight_as_new', + field=models.BooleanField(default=False), + ), + ] diff --git a/budgetportal/models/__init__.py b/budgetportal/models/__init__.py index f0e1b4008..0032c16d8 100644 --- a/budgetportal/models/__init__.py +++ b/budgetportal/models/__init__.py @@ -707,6 +707,9 @@ class MainMenuItem(SortableMixin): main_menu_item_order = models.PositiveIntegerField( default=0, editable=False, db_index=True ) + highlight_as_new = models.BooleanField( + default=False, + ) class Meta: ordering = ["main_menu_item_order"] From 1ce47a1773beb1a84c21e7d38e933639d55e446f Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Thu, 14 Dec 2023 16:46:09 +0200 Subject: [PATCH 02/18] Stub of /public-entities page --- .../components/universal/Section/styles.scss | 10 ++++++- budgetportal/templates/page-shell.html | 3 --- .../templates/public-entities_list.html | 26 +++++++++++++++++++ budgetportal/urls.py | 6 +++++ budgetportal/views.py | 10 +++++++ 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 budgetportal/templates/public-entities_list.html diff --git a/assets/scss/components/universal/Section/styles.scss b/assets/scss/components/universal/Section/styles.scss index b2e6bb481..320c23fbb 100644 --- a/assets/scss/components/universal/Section/styles.scss +++ b/assets/scss/components/universal/Section/styles.scss @@ -11,13 +11,21 @@ box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.25); border: 1px solid $colour-shading-border; + @media ($mobile-breakpoint) { padding: 25px 30px 30px; } - + @media ($tablet-breakpoint) { padding: 25px 40px 30px; } + + &.NoBg { + background-color: transparent; + box-shadow: none; + border: none; + padding-top: 0; + } &.is-link { cursor: pointer; diff --git a/budgetportal/templates/page-shell.html b/budgetportal/templates/page-shell.html index c7e5c66c5..37a1f718e 100644 --- a/budgetportal/templates/page-shell.html +++ b/budgetportal/templates/page-shell.html @@ -9,9 +9,6 @@ {% include 'components/header-and-footer/HeaderBar/index.html' %} {% endwith %} - {% if financial_years and selected_financial_year %} - {% include 'components/header-and-footer/YearSelect/index.html' with years=financial_years %} - {% endif %}
{% block page_content %} diff --git a/budgetportal/templates/public-entities_list.html b/budgetportal/templates/public-entities_list.html new file mode 100644 index 000000000..eaea3e403 --- /dev/null +++ b/budgetportal/templates/public-entities_list.html @@ -0,0 +1,26 @@ +{% extends 'page-shell.html' %} {% load define_action %} {% block page_content%} + +
+
+ + +

Public Entities

+ +
+

+ Public entities in South Africa are state-owned companies or + institutions that provide goods or services to the public. They are + accountable to the government departments that falls under the same + ministerial portfolio. The relationship between public entities and + their partner departments is governed by + legislation, policy, and memoranda of understanding. +

+
+ +
+
+
+{% endblock %} diff --git a/budgetportal/urls.py b/budgetportal/urls.py index 80abd464d..284024cef 100644 --- a/budgetportal/urls.py +++ b/budgetportal/urls.py @@ -276,6 +276,12 @@ def trigger_error(request): {"sitemaps": sitemaps}, name="django.contrib.sitemaps.views.sitemap", ), + # Public Entities List + url( + r"^public-entities$", + views.public_entities_list, + name="public-entities-list", + ), url("^", include(webflow_urls.urlpatterns)), re_path(r"^cms/", include(wagtailadmin_urls)), re_path(r"^documents/", include(wagtaildocs_urls)), diff --git a/budgetportal/views.py b/budgetportal/views.py index fc40e49b9..3e43a2f01 100644 --- a/budgetportal/views.py +++ b/budgetportal/views.py @@ -1188,3 +1188,13 @@ def budget_summary_view(request): and latest_provincial_year.slug, } return render(request, "budget-summary.html", context) + + +def public_entities_list(request): + context = { + "title": "Public Entities - vulekamali", + "description": COMMON_DESCRIPTION + COMMON_DESCRIPTION_ENDING, + "selected_tab": "public-entities", + "navbar": MainMenuItem.objects.prefetch_related("children").all(), + } + return render(request, "public-entities_list.html", context) From d7f6b46f22d07a99a07cf80f66346e84ef030a94 Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Mon, 22 Jan 2024 12:06:32 +0200 Subject: [PATCH 03/18] In dev send email to console --- budgetportal/settings.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/budgetportal/settings.py b/budgetportal/settings.py index a3fc660eb..59b4c93a8 100644 --- a/budgetportal/settings.py +++ b/budgetportal/settings.py @@ -251,11 +251,17 @@ "allauth.account.auth_backends.AuthenticationBackend", ) -EMAIL_HOST = os.environ.get("EMAIL_HOST", "smtp.sendgrid.net") -EMAIL_PORT = os.environ.get("EMAIL_PORT", 587) -EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "apikey") -EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", None) -EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", True) +# Send email in production or fake it in development +if os.environ.get("EMAIL_HOST_PASSWORD", None): + EMAIL_HOST = os.environ.get("EMAIL_HOST", "smtp.sendgrid.net") + EMAIL_PORT = os.environ.get("EMAIL_PORT", 587) + EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "apikey") + EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", None) + EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", True) + EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +else: + EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "info@vulekamali.gov.za") From 02ad31c519b3e44a8c423b87b297946e267c6a6d Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Mon, 22 Jan 2024 12:07:02 +0200 Subject: [PATCH 04/18] Ignore local SQL dumps --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ec64d07e1..056c22837 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ env staticfiles/ docs/_build *.sqlite3 +*.sql .tox .coverage /.vscode/ From f013ebb3e4cead1d9166dd2209eb8c6d41c99e49 Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Mon, 22 Jan 2024 12:07:21 +0200 Subject: [PATCH 05/18] Port map db service to allow psql loading --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 207d399ac..21a718dcf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,8 @@ services: - POSTGRES_DB=budgetportal volumes: - db-data:/var/lib/postgresql/data + ports: + - "5439:5432" app: build: From a2b2bf1140122c423eb20d499ffac84598b5859d Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Mon, 18 Mar 2024 14:28:16 +0200 Subject: [PATCH 06/18] Public Entities list page --- .../partials/filterAccordingToProvince.js | 7 - .../ArrowButtons/index.html | 53 ++ .../ArrowButtons/scripts.js | 72 +++ .../ContributedData/index.html | 38 ++ .../IntroSection/index.html | 19 + .../IntroSection/index.jsx | 36 ++ .../IntroSection/scripts.jsx | 69 +++ .../PerformanceIndicators/indicator-card.js | 521 ++++++++++++++++ .../PerformanceIndicators/programme.js | 104 ++++ .../PerformanceIndicators/scripts.jsx | 306 +++++++++ .../PublicEntityControl/index.jsx | 58 ++ .../partials/departments.json | 33 + .../partials/functiongroup1s.json | 10 + .../PublicEntityGroup/index.jsx | 113 ++++ .../PublicEntitySearch/README.md | 7 + .../PublicEntitySearch/examples/basic.html | 1 + .../PublicEntitySearch/index.html | 7 + .../PublicEntitySearch/index.jsx | 56 ++ .../partials/filterDepartments.js | 8 + .../partials/filterFunctiongroup1s.js | 9 + .../partials/filterKeywords.js | 30 + .../partials/filterResults.js | 25 + .../PublicEntitySearch/scripts.jsx | 98 +++ assets/js/scripts.js | 5 + .../ArrowButtons/styles.scss | 168 +++++ .../ContributedData/styles.scss | 25 + .../InfraProjectList/styles.scss | 21 + .../IntroSection/styles.scss | 42 ++ .../PerformanceIndicators/styles.scss | 262 ++++++++ .../PublicEntityControl/styles.scss | 34 + .../PublicEntityGroup/styles.scss | 106 ++++ .../PublicEntitySearch/styles.scss | 166 +++++ .../PublicEntitySectionHead/styles.scss | 21 + .../SectionIndicator/styles.scss | 58 ++ .../universal/PseudoSelect/styles.scss | 5 + assets/scss/styles.scss | 10 + .../utilities/scss/variables/z-index.scss | 4 + budgetportal/admin.py | 56 ++ budgetportal/import_export_admin.py | 65 ++ budgetportal/migrations/0073_publicentity.py | 33 + budgetportal/models/__init__.py | 1 + budgetportal/models/government.py | 105 +++- .../templates/public-entities_list.html | 26 - budgetportal/templates/public_entity.html | 354 +++++++++++ .../templates/public_entity_list.html | 19 + budgetportal/urls.py | 29 +- budgetportal/views.py | 146 ++++- yarn.lock | 581 ++++++++++++++---- 48 files changed, 3859 insertions(+), 163 deletions(-) delete mode 100644 assets/js/components/department-budgets/DeptSearch/partials/filterAccordingToProvince.js create mode 100644 assets/js/components/public-entity-budgets/ArrowButtons/index.html create mode 100644 assets/js/components/public-entity-budgets/ArrowButtons/scripts.js create mode 100644 assets/js/components/public-entity-budgets/ContributedData/index.html create mode 100644 assets/js/components/public-entity-budgets/IntroSection/index.html create mode 100644 assets/js/components/public-entity-budgets/IntroSection/index.jsx create mode 100644 assets/js/components/public-entity-budgets/IntroSection/scripts.jsx create mode 100644 assets/js/components/public-entity-budgets/PerformanceIndicators/indicator-card.js create mode 100644 assets/js/components/public-entity-budgets/PerformanceIndicators/programme.js create mode 100644 assets/js/components/public-entity-budgets/PerformanceIndicators/scripts.jsx create mode 100644 assets/js/components/public-entity-budgets/PublicEntityControl/index.jsx create mode 100644 assets/js/components/public-entity-budgets/PublicEntityControl/partials/departments.json create mode 100644 assets/js/components/public-entity-budgets/PublicEntityControl/partials/functiongroup1s.json create mode 100644 assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx create mode 100644 assets/js/components/public-entity-budgets/PublicEntitySearch/README.md create mode 100644 assets/js/components/public-entity-budgets/PublicEntitySearch/examples/basic.html create mode 100644 assets/js/components/public-entity-budgets/PublicEntitySearch/index.html create mode 100644 assets/js/components/public-entity-budgets/PublicEntitySearch/index.jsx create mode 100644 assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterDepartments.js create mode 100644 assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterFunctiongroup1s.js create mode 100644 assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterKeywords.js create mode 100644 assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterResults.js create mode 100644 assets/js/components/public-entity-budgets/PublicEntitySearch/scripts.jsx create mode 100644 assets/scss/components/public-entity-budgets/ArrowButtons/styles.scss create mode 100644 assets/scss/components/public-entity-budgets/ContributedData/styles.scss create mode 100644 assets/scss/components/public-entity-budgets/InfraProjectList/styles.scss create mode 100644 assets/scss/components/public-entity-budgets/IntroSection/styles.scss create mode 100644 assets/scss/components/public-entity-budgets/PerformanceIndicators/styles.scss create mode 100644 assets/scss/components/public-entity-budgets/PublicEntityControl/styles.scss create mode 100644 assets/scss/components/public-entity-budgets/PublicEntityGroup/styles.scss create mode 100644 assets/scss/components/public-entity-budgets/PublicEntitySearch/styles.scss create mode 100644 assets/scss/components/public-entity-budgets/PublicEntitySectionHead/styles.scss create mode 100644 assets/scss/components/public-entity-budgets/SectionIndicator/styles.scss create mode 100644 budgetportal/migrations/0073_publicentity.py delete mode 100644 budgetportal/templates/public-entities_list.html create mode 100644 budgetportal/templates/public_entity.html create mode 100644 budgetportal/templates/public_entity_list.html diff --git a/assets/js/components/department-budgets/DeptSearch/partials/filterAccordingToProvince.js b/assets/js/components/department-budgets/DeptSearch/partials/filterAccordingToProvince.js deleted file mode 100644 index 0d88f85b5..000000000 --- a/assets/js/components/department-budgets/DeptSearch/partials/filterAccordingToProvince.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function filterAccordingToProvince(items, province) { - if (province !== 'all') { - return items.filter(({ slug }) => slug === province); - } - - return items; -} diff --git a/assets/js/components/public-entity-budgets/ArrowButtons/index.html b/assets/js/components/public-entity-budgets/ArrowButtons/index.html new file mode 100644 index 000000000..1d310ab9f --- /dev/null +++ b/assets/js/components/public-entity-budgets/ArrowButtons/index.html @@ -0,0 +1,53 @@ +{% load define_action %} + +{% if fixed == "true" %} + {% assign "" as closingTag %} + {% assign " nav-link" as navlinkModifier %} +{% else %} + {% assign "
" as root %} + {% assign " active" as activeModifiers %} + {% assign " is-responsive" as responsiveModifiers %} + {% assign "
" as closingTag %} + {% assign "" as navlinkModifier %} +{% endif %} + + +{{ root }} + +{{ closingTag }} diff --git a/assets/js/components/public-entity-budgets/ArrowButtons/scripts.js b/assets/js/components/public-entity-budgets/ArrowButtons/scripts.js new file mode 100644 index 000000000..6dec4a2d6 --- /dev/null +++ b/assets/js/components/public-entity-budgets/ArrowButtons/scripts.js @@ -0,0 +1,72 @@ +import DebounceFunction from '../../../utilities/js/helpers/DebounceFunction.js'; + + +const calcAbsolutePositionFromTop = (node) => { + const { top } = node.getBoundingClientRect(); + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + return top + scrollTop; +}; + + +function ArrowButtons() { + const node = document.querySelector('[data-sticky-arrows]'); + const clickNodes = document.querySelectorAll('[data-scroll-smooth]'); + + const clickOverride = (event, target) => { + event.preventDefault(); + const targetNode = document.querySelector(target); + const absoluteTop = calcAbsolutePositionFromTop(targetNode); + const top = absoluteTop - 100; + + window.scrollTo({ + behavior: 'smooth', + top, + }); + }; + + if (Array.from) { + Array.from(clickNodes).forEach((innerNode) => { + const target = innerNode.getAttribute('data-scroll-smooth'); + + if (target) { + innerNode.addEventListener( + 'click', + event => clickOverride(event, target), + ); + } + }); + } + + const updateSticky = () => { + if (node) { + const active = node.classList.contains('is-active'); + const top = window.pageYOffset || document.documentElement.scrollTop; + + if (top < 700 && active) { + return node.classList.remove('is-active'); + } + + if (top > 700 && !active) { + return node.classList.add('is-active'); + } + } + + return null; + }; + + const viewportDebounce = new DebounceFunction(50); + const updateViewport = () => viewportDebounce.update(updateSticky); + + window.addEventListener( + 'resize', + updateViewport, + ); + + window.addEventListener( + 'scroll', + updateViewport, + ); +} + + +export default ArrowButtons(); diff --git a/assets/js/components/public-entity-budgets/ContributedData/index.html b/assets/js/components/public-entity-budgets/ContributedData/index.html new file mode 100644 index 000000000..84bbdd1fc --- /dev/null +++ b/assets/js/components/public-entity-budgets/ContributedData/index.html @@ -0,0 +1,38 @@ +{% load define_action %} + +{% if datasets|length > 0 %} + {% assign '' as padding %} +{% else %} + {% assign ' u-paddingBottom0' as padding %} +{% endif %} + + +
+ + + {% if datasets|length > 0 %} + {% for item in datasets %} + +
+
+
+
{{ item.name }}
+
Data by {{ item.contributor }}
+
+ +
+
+ + {% endfor %} + {% endif %} + + +
diff --git a/assets/js/components/public-entity-budgets/IntroSection/index.html b/assets/js/components/public-entity-budgets/IntroSection/index.html new file mode 100644 index 000000000..0866898cc --- /dev/null +++ b/assets/js/components/public-entity-budgets/IntroSection/index.html @@ -0,0 +1,19 @@ +{% load define_action %} +{% load markdownify %} +{% if location == 'National' %} + {% assign 'ENE' as short_estimate %} + {% assign 'Estimates of National Expenditure' as long_estimate %} +{% else %} + {% assign 'EPRE' as short_estimate %} + {% assign 'Estimates of Provincial Revenue and Expenditure' as long_estimate %} +{% endif %} + +
+
+
+
+ {{ description | markdownify }} +
+
+
+
\ No newline at end of file diff --git a/assets/js/components/public-entity-budgets/IntroSection/index.jsx b/assets/js/components/public-entity-budgets/IntroSection/index.jsx new file mode 100644 index 000000000..0abc1c900 --- /dev/null +++ b/assets/js/components/public-entity-budgets/IntroSection/index.jsx @@ -0,0 +1,36 @@ +import React from 'react'; + + +export default function IntroSection({ innerHtml, open, setOpen, parentAction, triggered }) { + const prompt = ( +
+
+
+ +
+
+ ); + + if (triggered) { + return ( +
+
parentAction(node)} + /> + {open ? null : prompt} +
+ ); + } + + return ( +
+
parentAction(node)} + /> +
+ ); +} diff --git a/assets/js/components/public-entity-budgets/IntroSection/scripts.jsx b/assets/js/components/public-entity-budgets/IntroSection/scripts.jsx new file mode 100644 index 000000000..5f991094a --- /dev/null +++ b/assets/js/components/public-entity-budgets/IntroSection/scripts.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import IntroSection from './index.jsx'; + + +class IntroSectionContainer extends React.Component { + constructor(props) { + super(props); + + this.state = { + open: false, + triggered: false, + }; + + this.parent = null; + this.setOpen = this.setOpen.bind(this); + this.parentAction = this.parentAction.bind(this); + } + + setOpen() { + this.setState({ open: !this.state.open }); + } + + parentAction(node) { + if (node && this.parent !== node.clientHeight) { + this.parent = node.clientHeight; + this.calcIfTriggered(node.clientHeight); + } + } + + calcIfTriggered(value) { + if (value > 330) { + return this.setState({ triggered: true }); + } + + return this.setState({ triggered: false }); + } + + render() { + return ( + + ); + } +} + + +function scripts() { + const nodes = document.getElementsByClassName('js-initIntroSection'); + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + const innerHtml = node.getElementsByClassName('js-content')[0].innerHTML; + + ReactDOM.render( + , + node, + ); + } + +} + + +export default scripts(); diff --git a/assets/js/components/public-entity-budgets/PerformanceIndicators/indicator-card.js b/assets/js/components/public-entity-budgets/PerformanceIndicators/indicator-card.js new file mode 100644 index 000000000..4513b3909 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PerformanceIndicators/indicator-card.js @@ -0,0 +1,521 @@ +import ReactDOM from "react-dom"; +import React, {Component} from "react"; +import {Button, Card, Grid, Tooltip} from "@material-ui/core"; +import {scaleLinear, scaleBand} from "d3-scale"; +import {create} from "d3-selection"; + +class IndicatorCard extends Component { + constructor(props) { + super(props); + + this.resizeObserver = null; + + this.state = { + indicator: props.data, + selectedQuarter: this.findLatestQuarter(props.data), + selectedPeriodType: props.data.frequency === 'annually' ? 'annual' : 'quarter', + selectedYear: props.financialYear, // current year in default + previousYearsIndicators: props.previousYearsIndicators, + financialYear: props.financialYear + } + } + + componentDidMount() { + this.handleObservers(); + + if (this.state.indicator == null) { + return; + } + + if (this.state.indicator.frequency === 'annually') { + this.handleAnnualCharts(); + } else { + this.handleQuarterlyCharts(); + } + } + + componentDidUpdate(prevProps, prevState, snapshot) { + if (prevState.selectedPeriodType !== this.state.selectedPeriodType && this.state.selectedPeriodType === 'annual') { + this.handleAnnualCharts(); + } + + if (this.props.previousYearsIndicators !== this.state.previousYearsIndicators) { + this.setState({ + ...this.state, + previousYearsIndicators: this.props.previousYearsIndicators + }); + + if (this.state.indicator.frequency === 'annually') { + this.handleAnnualCharts(); + } + } + } + + findLatestQuarter(data) { + if (data['q4_actual_output'].trim() !== '') { + return 4; + } else if (data['q3_actual_output'].trim() !== '') { + return 3; + } else if (data['q2_actual_output'].trim() !== '') { + return 2; + } else { + return 1; + } + } + + handleObservers() { + const ps = document.querySelectorAll('.output-text-container .output-text'); + const padding = 24; + if (this.resizeObserver === null) { + this.resizeObserver = new ResizeObserver(entries => { + for (let entry of entries) { + entry.target.parentElement.classList[entry.target.scrollHeight * 0.95 > entry.contentRect.height + padding ? 'add' : 'remove']('read-more-visible'); + } + }) + } + + ps.forEach(p => { + this.resizeObserver.observe(p); + }) + } + + expandOutput(event) { + const ele = event.target.parentElement.querySelectorAll('.output-text')[0] + if (ele != null) { + ele.style['-webkit-box-orient'] = 'unset'; + } + } + + handleQuarterSelection(newVal) { + this.setState({ + ...this.state, + selectedQuarter: newVal, + selectedPeriodType: 'quarter' + }); + } + + handlePeriodTypeSelection(newVal) { + this.setState({ + ...this.state, + selectedPeriodType: newVal + }); + } + + renderQuarterSelection() { + return ( + + + + + + + + ) + } + + isNumeric(str) { + if (typeof str != 'string') { + return false + } + + return !isNaN(str) && !isNaN(parseFloat(str)); + } + + getIndicatorQuarterMax() { + let valuesArr = []; + for (let i = 1; i <= 4; i++) { + const {target, actual} = this.getQuarterTargetAndActual(i); + valuesArr.push(this.isNumeric(target) ? parseFloat(target) : 0); + valuesArr.push(this.isNumeric(actual) ? parseFloat(actual) : 0); + } + + return Math.max(...valuesArr); + } + + getIndicatorAnnualMax(financialYear) { + let valuesArr = []; + for (let i = 3; i >= 0; i--) { + const {target, actual} = this.getAnnualTargetAndActual(financialYear); + valuesArr.push(this.isNumeric(target) ? parseFloat(target) : 0); + valuesArr.push(this.isNumeric(actual) ? parseFloat(actual) : 0); + } + + return Math.max(...valuesArr); + } + + getQuarterKeyValue(appix, annualAppix, quarter) { + const prefix = this.state.selectedPeriodType === 'annual' ? '' : 'q'; + const finalAppix = this.state.selectedPeriodType === 'annual' ? annualAppix : appix; + const key = `${prefix}${quarter}_${finalAppix}`; + if (this.state.selectedPeriodType === 'annual' && this.state.selectedYear !== this.state.financialYear) { + const indicator = this.state.previousYearsIndicators.filter(x => x.financialYear === this.state.selectedYear)[0].indicator; + return indicator == null ? null : indicator[key]; + } else { + return this.state.indicator[key]; + } + } + + renderQuarterKeyValue(appix, annualAppix, quarter = this.state.selectedQuarter) { + const val = this.getQuarterKeyValue(appix, annualAppix, quarter); + const elem = (val == null || val.trim() === '') ? + Data not yet available : {val} + + return elem; + } + + getQuarterKeyText(appix, annualAppix) { + const prefix = this.state.selectedPeriodType === 'annual' ? `${this.state.selectedYear} ANNUAL` : 'QUARTER'; + const finalAppix = this.state.selectedPeriodType === 'annual' ? annualAppix : appix; + const quarter = this.state.selectedPeriodType === 'annual' ? '' : this.state.selectedQuarter; + return `${prefix} ${quarter} ${finalAppix}`; + } + + createChart(data, indicatorMax) { + const width = 400; + const height = 400; + const margin = {top: 0, right: 0, bottom: 0, left: 0}; + + const x = scaleBand() + .domain(data.map((d) => d.quarter)) + .rangeRound([margin.left, width - margin.right]) + .padding(0); + + const y = scaleLinear() + .domain([0, indicatorMax]) //max value + .range([height - margin.bottom, margin.top]); + + const svg = create("svg").attr("viewBox", [0, 0, width, height]); + + // bar + svg + .append("g") + .attr("fill", "#f59e46") + .selectAll("rect") + .data(data) + .join("rect") + .attr("x", (d) => x(d.quarter)) + .attr("y", (d) => y(d.actual)) + .attr("height", (d) => y(0) - y(d.actual)) + .attr("width", x.bandwidth()); + + // dashed line + svg.append('line') + .data(data) + .attr('x1', margin.left) + .attr('x2', width) + .attr('y1', (d) => y(d.target)) + .attr('y2', (d) => y(d.target)) + .attr('stroke-width', 10) + .style('stroke-dasharray', '8') + .style('stroke', 'rgba(0, 0, 0, 0.7)') + + return svg.node(); + } + + createUnavailableChartIndicator(quarter, nonNumeric) { + const svgElement = + + + + return ( +
+ + {svgElement} + +
+ ) + } + + getQuarterTargetAndActual(quarter) { + const target_init = this.getQuarterKeyValue('target', 'target', quarter); + const actual_init = this.getQuarterKeyValue('actual_output', 'audited_output', quarter); + const target = target_init == null ? 0 : target_init.replace('%', '').trim(); + const actual = actual_init == null ? 0 : actual_init.replace('%', '').trim(); + + return {target, actual}; + } + + handleQuarterChart(quarter) { + const ctx = document.getElementById(`chart-quarter-${this.state.indicator.id}-${quarter}`); + const {target, actual} = this.getQuarterTargetAndActual(quarter); + const bothNumeric = this.isNumeric(target) && this.isNumeric(actual); + + if (bothNumeric) { + // show chart + let values = [{ + quarter: `Q${quarter}`, + actual: parseFloat(actual), + target: parseFloat(target) + }]; + let indicatorMax = this.getIndicatorQuarterMax(); + + const chart = this.createChart(values, indicatorMax); + ctx.appendChild(chart) + } else { + // chart is not available + const nonNumeric = !this.isNumeric(actual) ? 'actual output' : 'target'; + const parentDiv = this.createUnavailableChartIndicator(`Q${quarter}`, nonNumeric); + + ReactDOM.render(parentDiv, ctx); + } + } + + getAnnualTargetAndActual(financialYear) { + const indicator = this.state.previousYearsIndicators.filter(x => x.financialYear === financialYear)[0].indicator; + const target = indicator == null ? 0 : indicator['annual_target'].replace('%', '').trim(); + const actual = indicator == null ? 0 : indicator['annual_audited_output'].replace('%', '').trim(); + + return {target, actual}; + } + + handleAnnualCharts() { + for (let i = 1; i <= 4; i++) { + if (this.state.previousYearsIndicators[i - 1] != null) { + const financialYear = this.state.previousYearsIndicators[i - 1].financialYear; + const ctx = document.getElementById(`chart-annual-${this.state.indicator.id}-${i}`); + if (!ctx.hasChildNodes()) { + const {target, actual} = this.getAnnualTargetAndActual(financialYear); + const bothNumeric = this.isNumeric(target) && this.isNumeric(actual); + + if (bothNumeric) { + // show chart + let values = [{ + quarter: financialYear, + actual: parseFloat(actual), + target: parseFloat(target) + }] + + let indicatorMax = this.getIndicatorAnnualMax(financialYear); + + const chart = this.createChart(values, indicatorMax); + ctx.appendChild(chart) + } else { + // chart is not available + const nonNumeric = !this.isNumeric(actual) ? 'actual output' : 'target'; + const parentDiv = this.createUnavailableChartIndicator(financialYear, nonNumeric); + + ReactDOM.render(parentDiv, ctx); + } + } + } + } + } + + handleQuarterlyCharts() { + for (let i = 1; i <= 4; i++) { + this.handleQuarterChart(i); + } + } + + getQuarterChartContainer(q) { + return ( + { + this.setState({ + ...this.state, + selectedQuarter: q + }) + }} + > +
+ + ) + } + + getAnnualChartContainer(q) { + if (this.state.previousYearsIndicators.length <= q - 1 || this.state.previousYearsIndicators[q - 1] == null) { + return; + } + + const selectedYear = this.state.previousYearsIndicators[q - 1].financialYear; + return ( + { + this.setState({ + ...this.state, + selectedYear: selectedYear + }) + }} + > +
+ + ) + } + + renderChartContainerColumns() { + return ( + [1, 2, 3, 4].map(q => { + return ([ + this.getQuarterChartContainer(q), + this.getAnnualChartContainer(q) + ]) + }) + ) + } + + renderChartContainers() { + return ( + + {this.renderChartContainerColumns()} + + {this.state.selectedPeriodType === 'annual' && this.state.previousYearsIndicators[0] !== undefined ? this.state.previousYearsIndicators[0].financialYear : 'Q1'} + + + {this.state.selectedPeriodType === 'annual' && this.state.previousYearsIndicators[1] !== undefined ? this.state.previousYearsIndicators[1].financialYear : 'Q2'} + + + {this.state.selectedPeriodType === 'annual' && this.state.previousYearsIndicators[2] !== undefined ? this.state.previousYearsIndicators[2].financialYear : 'Q3'} + + + {this.state.selectedPeriodType === 'annual' && this.state.previousYearsIndicators[3] !== undefined ? this.state.previousYearsIndicators[3].financialYear : 'Q4'} + + + ) + } + + renderCard() { + return ( + + +

TYPE: {this.state.indicator.type}

+

{this.state.indicator.indicator_name}

+ {this.renderQuarterSelection()} + +

{this.getQuarterKeyText('TARGET', 'TARGET')}:

+
+
+ {this.renderQuarterKeyValue('target', 'target', this.state.selectedPeriodType === 'annual' ? 'annual' : this.state.selectedQuarter)} +
+
+ + + +
+
+
+ +

{this.getQuarterKeyText('ACTUAL OUTPUT', 'AUDITED PERFORMANCE')}:

+
+
+ {this.renderQuarterKeyValue('actual_output', 'audited_output', this.state.selectedPeriodType === 'annual' ? 'annual' : this.state.selectedQuarter)} +
+ +
+
+ +

+ {this.getQuarterKeyText('PERFORMANCE', 'PERFORMANCE')}: + + + + + + + Actual + + + + + + + Target + +

+
+ {this.renderChartContainers()} +
+
+
+
+ ) + } + + render() { + return this.renderCard() + } +} + +export default IndicatorCard; \ No newline at end of file diff --git a/assets/js/components/public-entity-budgets/PerformanceIndicators/programme.js b/assets/js/components/public-entity-budgets/PerformanceIndicators/programme.js new file mode 100644 index 000000000..b60f08802 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PerformanceIndicators/programme.js @@ -0,0 +1,104 @@ +import React, {Component} from "react"; +import {Button, Grid, Paper} from "@material-ui/core"; +import IndicatorCard from "./indicator-card"; + +class Programme extends Component { + constructor(props) { + super(props); + + this.state = { + open: false, + triggered: false, + programme: props.data, + previousYearsProgrammes: props.previousYearsProgrammes, + financialYear: props.financialYear + } + } + + componentDidUpdate(prevProps, prevState, snapshot) { + if (this.props.previousYearsProgrammes !== this.state.previousYearsProgrammes) { + this.setState({ + ...this.state, + previousYearsProgrammes: this.props.previousYearsProgrammes + }); + } + } + + setOpen() { + this.setState({ + ...this.state, open: !this.state.open + }); + } + + renderIndicatorCards(programme) { + return programme.visibleIndicators.map((indicator) => { + let prevArr = this.state.previousYearsProgrammes.map(item => { + return { + financialYear: item.financialYear, + indicator: item.programme == null ? null : item.programme.allIndicators.filter(p => p.indicator_name === indicator.indicator_name)[0] + }; + }) + + return () + }) + } + + renderReadMoreButton() { + if (this.state.open) { + return; + } + + return ( +
+
+
+ +
+
+ ) + } + + renderProgramme() { + return ( +
+

{this.state.programme.name}

+ + {this.renderIndicatorCards(this.state.programme)} + + + + +
+ {this.renderReadMoreButton()} +
) + } + + render() { + return (
{this.renderProgramme()}
); + } +} + +export default Programme; \ No newline at end of file diff --git a/assets/js/components/public-entity-budgets/PerformanceIndicators/scripts.jsx b/assets/js/components/public-entity-budgets/PerformanceIndicators/scripts.jsx new file mode 100644 index 000000000..159dc44f0 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PerformanceIndicators/scripts.jsx @@ -0,0 +1,306 @@ +import ReactDOM from "react-dom"; +import React, {Component} from "react"; +import Programme from "./programme"; +import fetchWrapper from "../../../utilities/js/helpers/fetchWrapper"; +import decodeHtmlEntities from "../../../utilities/js/helpers/decodeHtmlEntities"; +import {Button, CircularProgress, Dialog, Grid} from "@material-ui/core"; + +class PerformanceIndicators extends Component { + constructor(props) { + super(props); + + this.state = { + department: props.department, + financialYear: props.year, + sphere: props.sphere, + government: props.government, + previousYears: props.previousYears, + programmes: [], + previousYearsProgrammes: [], + pageCount: 3, + isLoading: true + }; + } + + componentDidMount() { + this.fetchAPIData(); + } + + fetchPreviousYearsAPIData() { + this.state.previousYears.forEach((fy, index) => { + this.fetchAPIDataRecursive(1, [], fy) + .then((items) => { + let arr = this.state.previousYearsProgrammes; + arr[index] = { + financialYear: fy, programmes: this.extractProgrammeData(items) + } + + this.setState({ + ...this.state, previousYearsProgrammes: arr + }); + }) + }) + } + + fetchAPIData() { + this.fetchAPIDataRecursive(1, [], this.state.financialYear) + .then((items) => { + this.setState({ + ...this.state, isLoading: false, programmes: this.extractProgrammeData(items) + }); + + this.fetchPreviousYearsAPIData(); + }) + } + + extractProgrammeData(items) { + let programmes = [...new Set(items.map(x => x.programme_name))]; + let data = []; + programmes.forEach(p => { + const allIndicators = items.filter(x => x.programme_name === p); + data.push({ + name: p, visibleIndicators: allIndicators.slice(0, this.state.pageCount), allIndicators: allIndicators + }) + }) + + return data; + } + + fetchAPIDataRecursive(page = 1, allItems = [], financialYear) { + return new Promise((resolve, reject) => { + const pageQuery = `page=${page}`; + const departmentQuery = `department__name=${encodeURI(this.state.department)}`; + const financialYearQuery = `department__government__sphere__financial_year__slug=${financialYear}`; + const baseUrl = `../../../../../performance/api/v1/eqprs/`; + let url = `${baseUrl}?${pageQuery}&${departmentQuery}&${financialYearQuery}`; + + fetchWrapper(url) + .then((response) => { + let newArr = allItems.concat(response.results.items); + if (response.next === null) { + resolve(newArr); + } else { + this.fetchAPIDataRecursive(page + 1, newArr, financialYear) + .then((items) => { + resolve(items); + }); + } + }) + .catch((errorResult) => console.warn(errorResult)); + }) + } + + handleShowMore(currentProgramme) { + let programmes = this.state.programmes.map(programme => { + if (programme.name === currentProgramme.name) { + programme.visibleIndicators = programme.allIndicators.slice(0, programme.visibleIndicators.length + this.state.pageCount); + } + return programme; + }) + + this.setState({ + ...this.state, programmes: programmes + }) + } + + renderProgrammes() { + if (this.state.programmes.length > 0) { + return this.state.programmes.map((programme, index) => { + let prevArr = this.state.previousYearsProgrammes.map(item => { + return { + financialYear: item.financialYear, + programme: item.programmes.filter(p => p.name === programme.name)[0] + }; + }) + + return ( this.handleShowMore(programme)} + previousYearsProgrammes={prevArr} + financialYear={this.state.financialYear} + />) + }) + } else { + return (
+
+ Please note +
+
No performance data currently available for this department.
+
) + } + } + + renderLoadingState() { + if (!this.state.isLoading) { + return + } + const tableContainer = document.getElementsByClassName('js-initYearSelect')[0]; + const gifWidth = 40; + const marginLeftVal = (tableContainer.clientWidth - gifWidth) / 2; + + return (
+ +
) + } + + render() { + return (
+ {this.renderProgrammes()} + {this.renderLoadingState()} +
); + } +} + +class PerformanceIndicatorsContainer extends Component { + constructor(props) { + super(props); + + this.state = { + dataDisclaimerAcknowledged: false, + modalOpen: false, + department: props.department, + year: props.year, + sphere: props.sphere, + government: props.government, + previousYears: props.previousYears + } + } + + componentDidMount() { + this.checkForLocalStorage(); + } + + checkForLocalStorage() { + const ack = localStorage.getItem('data-disclaimer-acknowledged'); + this.setState({ + ...this.state, dataDisclaimerAcknowledged: ack === 'true', modalOpen: ack !== 'true' + }) + } + + handleStorage() { + localStorage.setItem('data-disclaimer-acknowledged', 'true'); + this.setState({ + ...this.state, dataDisclaimerAcknowledged: true, modalOpen: false + }) + } + + renderNavigateButtons() { + const baseUrl = '../../../../../performance'; + const sphereQuery = `department__government__sphere__name=${encodeURI(this.state.sphere)}`; + const governmentQuery = `department__government__name=${encodeURI(this.state.government)}`; + const yearQuery = `department__government__sphere__financial_year__slug=${this.state.year}`; + const departmentQuery = `department__name=${this.state.department}`; + return ( + + + ) + } + + renderModal() { + return ( document.getElementById('js-initPerformanceIndicators')} + style={{position: 'absolute'}} + BackdropProps={{ + style: {position: 'absolute'} + }} + disableAutoFocus={true} + disableEnforceFocus={true} + className={'performance-modal'} + > + + Data disclaimer + + + The Quarterly Performance Reporting (QPR) data (other than the Annual audited output field) + is pre-audited non financial data. This data is approved by the accounting officer of the + relevant organ of state before publication. + + + Learn more about these performance indicators. + + + + + ) + } + + render() { + return (
+

Indicators of performance

+ + {this.renderNavigateButtons()} + {this.renderModal()} +
); + } +} + +function scripts() { + const nodes = document.getElementsByClassName('js-initYearSelect'); + const nodesArray = [...nodes]; + let previousYears = []; + + if (nodesArray.length > 0) { + const jsonData = JSON.parse(decodeHtmlEntities(nodes[0].getAttribute('data-json'))).data; + jsonData.forEach((d) => { + previousYears.push(d.id) + }) + } + + const parent = document.getElementById('js-initPerformanceIndicators'); + if (parent) { + const departmentName = parent.getAttribute('data-department'); + const financialYear = parent.getAttribute('data-year'); + const sphere = parent.getAttribute('data-sphere'); + const government = parent.getAttribute('data-government'); + ReactDOM.render(, parent) + } +} + + +export default scripts(); \ No newline at end of file diff --git a/assets/js/components/public-entity-budgets/PublicEntityControl/index.jsx b/assets/js/components/public-entity-budgets/PublicEntityControl/index.jsx new file mode 100644 index 000000000..90295aa13 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntityControl/index.jsx @@ -0,0 +1,58 @@ +import React from "react"; +import PseudoSelect from "./../../universal/PseudoSelect/index.jsx"; +import functiongroup1s from "./partials/functiongroup1s.json"; +import departments from "./partials/departments.json"; + +export default function PublicEntityGroup({ + changeKeywords, + updateFilter, + keywords, + open, + functiongroup1, + department +}) { + const triggerKeyword = (event) => changeKeywords(event.target.value); + const updateFunctionGroup1 = (value) => updateFilter("functiongroup1", value); + const updateDepartment = (value) => updateFilter("department", value); + + return ( +
+
+
+ +
+
+ +
+
in
+
+ +
+
+ +
+
+
+ +
+
+
+ ); +} diff --git a/assets/js/components/public-entity-budgets/PublicEntityControl/partials/departments.json b/assets/js/components/public-entity-budgets/PublicEntityControl/partials/departments.json new file mode 100644 index 000000000..f071daa19 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntityControl/partials/departments.json @@ -0,0 +1,33 @@ +{ + "All departments": "all", + "Science and Innovation": "Science and Innovation", + "National Treasury": "National Treasury", + "International Relations and Cooperation": "International Relations and Cooperation", + "Public Works and Infrastructure": "Public Works and Infrastructure", + "Agriculture, Land Reform and Rural Development": "Agriculture, Land Reform and Rural Development", + "Higher Education and Training": "Higher Education and Training", + "Transport": "Transport", + "Water and Sanitation": "Water and Sanitation", + "Sport, Arts and Culture": "Sport, Arts and Culture", + "Defence": "Defence", + "Home Affairs": "Home Affairs", + "Government Communication and Information System": "Government Communication and Information System", + "Communications and Digital Technologies": "Communications and Digital Technologies", + "Mineral Resources and Energy": "Mineral Resources and Energy", + "Employment and Labour": "Employment and Labour", + "Human Settlements": "Human Settlements", + "Trade, Industry and Competition": "Trade, Industry and Competition", + "Health": "Health", + "Public Enterprises": "Public Enterprises", + "Forestry, Fisheries and the Environment": "Forestry, Fisheries and the Environment", + "Justice and Constitutional Development": "Justice and Constitutional Development", + "Cooperative Governance": "Cooperative Governance", + "Social Development": "Social Development", + "National School of Government": "National School of Government", + "Women, Youth and Persons with Disabilities": "Women, Youth and Persons with Disabilities", + "Police": "Police", + "Small Business Development": "Small Business Development", + "Basic Education": "Basic Education", + "Tourism": "Tourism", + "Traditional Affairs": "Traditional Affairs" +} \ No newline at end of file diff --git a/assets/js/components/public-entity-budgets/PublicEntityControl/partials/functiongroup1s.json b/assets/js/components/public-entity-budgets/PublicEntityControl/partials/functiongroup1s.json new file mode 100644 index 000000000..fd369d735 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntityControl/partials/functiongroup1s.json @@ -0,0 +1,10 @@ +{ + "All functional groups": "all", + "Economic development": "Economic development", + "General public services": "General public services", + "Learning and culture": "Learning and culture", + "Peace and security": "Peace and security", + "Community development": "Community development", + "Social development": "Social development", + "Health": "Health" +} \ No newline at end of file diff --git a/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx b/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx new file mode 100644 index 000000000..5cea191f5 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx @@ -0,0 +1,113 @@ +import React from "react"; + +function notAvailableMessage() { + return ( +
+

+ This data is not yet available. Provincial budgets are tabled after the + national budget has been announced. This is because the national budget + determines the amount of money each province receives. We expect to be + able make provincial budget data available by April{" "} + {new Date().getFullYear()}. +

+

+ { + "In the meantime you view previous financial years' data by selecting a year at the top of your screen." + } +

+
+ ); +} + +function public_entities(linksArray) { + return ( + + + + + + + + + + + {linksArray.map( + ( + { + name, + url_path: url, + functiongroup1, + department, + department_slug, + department_sphere, + selected_year_slug, + }, + index + ) => { + return ( + + + + + + + ); + } + )} + +
Entity nameRelevant departmentPFMAExpenditure {}
+ + {name} + + + + {department} + + {functiongroup1} +
+
+ +
+
+
+ ); +} + +export default function PublicEntityGroup({ linksArray }) { + return ( +
+
+

+ {linksArray.length} Public entities match your search +

+
+
+ {public_entities(linksArray)} +
+
+
+
+ ); +} diff --git a/assets/js/components/public-entity-budgets/PublicEntitySearch/README.md b/assets/js/components/public-entity-budgets/PublicEntitySearch/README.md new file mode 100644 index 000000000..ac0c42442 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntitySearch/README.md @@ -0,0 +1,7 @@ +# PublicEntitySearch + +## Overview + +### Examples + +- [Basic example](basic.html) \ No newline at end of file diff --git a/assets/js/components/public-entity-budgets/PublicEntitySearch/examples/basic.html b/assets/js/components/public-entity-budgets/PublicEntitySearch/examples/basic.html new file mode 100644 index 000000000..b88da7450 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntitySearch/examples/basic.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/assets/js/components/public-entity-budgets/PublicEntitySearch/index.html b/assets/js/components/public-entity-budgets/PublicEntitySearch/index.html new file mode 100644 index 000000000..7614d571e --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntitySearch/index.html @@ -0,0 +1,7 @@ +{% load define_action %} +
+
diff --git a/assets/js/components/public-entity-budgets/PublicEntitySearch/index.jsx b/assets/js/components/public-entity-budgets/PublicEntitySearch/index.jsx new file mode 100644 index 000000000..3421b3e82 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntitySearch/index.jsx @@ -0,0 +1,56 @@ +import React from "react"; +import PublicEntityControl from "./../PublicEntityControl/index.jsx"; +import PublicEntityGroup from "./../PublicEntityGroup/index.jsx"; + +const makeGroup = (public_entities) => { + return ( +
+ +
+ ); +}; + +const emptyNotification = ( +
+
+
No results found
+
Please try changing or broadening your search terms
+
+
+); + +const makeGroups = (public_entities) => { + if (public_entities.length < 1) { + return emptyNotification; + } + + return makeGroup(public_entities); +}; + +function PublicEntitySearch({ state, eventHandlers }) { + return ( +
+
+

Filters

+
    +
  • + +
  • +
+

Results

+
+ {makeGroups(state.results)} +
+
+
+ ); +} + +export { PublicEntitySearch, makeGroups }; diff --git a/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterDepartments.js b/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterDepartments.js new file mode 100644 index 000000000..b5405bc6e --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterDepartments.js @@ -0,0 +1,8 @@ +export default function filterDepartments(items, selected) { + console.log({ items, selected }); + if (selected !== "all") { + return items.filter(({ department }) => selected === department); + } + + return items; +} diff --git a/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterFunctiongroup1s.js b/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterFunctiongroup1s.js new file mode 100644 index 000000000..b8098d22a --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterFunctiongroup1s.js @@ -0,0 +1,9 @@ +export default function filterFunctiongroup1s(items, functiongroup1) { + if (functiongroup1 !== "all") { + return items.filter( + ({ functiongroup1 }) => functiongroup1 === functiongroup1 + ); + } + + return items; +} diff --git a/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterKeywords.js b/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterKeywords.js new file mode 100644 index 000000000..c64a4fc96 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterKeywords.js @@ -0,0 +1,30 @@ +import lunrSearchWrapper from './../../../../utilities/js/helpers/lunrSearchWrapper.js'; +import wrapStringPhrases from './../../../../utilities/js/helpers/wrapStringPhrases.js'; + + +export default function filterKeywords(keywords, results) { + console.log({results}); + return results.map((group) => { + const filteredItems = lunrSearchWrapper( + results, + 'slug', + 'name', + keywords, + ); + + const phraseArray = [keywords, ...keywords.split(' ')]; + const wrapFn = string => `${string}`; + + const currentItems = filteredItems.map((obj) => { + return { + ...obj, + name: wrapStringPhrases(obj.name, phraseArray, wrapFn), + }; + }); + + return { + ...group, + public_entities: currentItems, + }; + }); +} diff --git a/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterResults.js b/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterResults.js new file mode 100644 index 000000000..bd1d6edad --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntitySearch/partials/filterResults.js @@ -0,0 +1,25 @@ +import filterKeywords from "./filterKeywords.js"; +import filterFunctiongroup1s from "./filterFunctiongroup1s.js"; +import filterDepartments from "./filterDepartments.js"; + +export default function filterResults(filtersObject, rawItems) { + const filtersKeys = Object.keys(filtersObject); + + return filtersKeys.reduce((results, key) => { + const value = filtersObject[key]; + + if (key === "keywords" && value.length > 2) { + return filterKeywords(value, results); + } + + if (key === "functiongroup1" && value !== "all") { + return filterFunctiongroup1s(results, value); + } + + if (key === "department" && value !== "all") { + return filterDepartments(results, value); + } + + return results; + }, rawItems); +} diff --git a/assets/js/components/public-entity-budgets/PublicEntitySearch/scripts.jsx b/assets/js/components/public-entity-budgets/PublicEntitySearch/scripts.jsx new file mode 100644 index 000000000..0d777bfb0 --- /dev/null +++ b/assets/js/components/public-entity-budgets/PublicEntitySearch/scripts.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import decodeHtmlEntities from './../../../utilities/js/helpers/decodeHtmlEntities.js'; +import updateQs from './../../../utilities/js/helpers/updateQs.js'; +import { PublicEntitySearch } from './index.jsx'; +import filterResults from './partials/filterResults.js'; + +class PublicEntitySearchContainer extends React.Component { + constructor(props) { + super(props); + const filters = { + keywords: this.props.phrase || '', + functiongroup1: this.props.functiongroup1 || 'all', + department: this.props.department || 'all', + }; + + this.state = { + loading: false, + open: null, + results: filterResults(filters, Object.keys(this.props.publicEntities[0]).map(key => this.props.publicEntities[0][key])), + filters, + }; + + this.eventHandlers = { + updateDropdown: this.updateDropdown.bind(this), + updateKeywords: this.updateKeywords.bind(this), + }; + } + + componentWillUpdate(nextProps, nextState) { + updateQs({ + ...window.vulekamali.qs, + phrase: nextState.filters.keywords, + functiongroup1: nextState.filters.functiongroup1, + department: nextState.filters.department, + }); + } + + updateKeywords(keywords) { + const filters = { + ...this.state.filters, + keywords, + }; + + this.setState({ filters }); + this.setState({ results: filterResults(filters, Object.keys(this.props.publicEntities[0]).map(key => this.props.publicEntities[0][key])) }); + } + + updateDropdown(filter, value) { + if (this.state.open === filter) { + this.setState({ open: null }); + } else { + return this.setState({ open: filter }); + } + + const filters = { + ...this.state.filters, + [filter]: value, + }; + + this.setState({ filters }); + return this.setState({ results: filterResults(filters, Object.keys(this.props.publicEntities[0]).map(key => this.props.publicEntities[0][key])) }); + } + + render() { + return ; + } +} + + +function scripts() { + const componentsList = document.getElementsByClassName('js-initPublicEntitySearch'); + const ckanUrl = document.getElementsByTagName('body')[0].getAttribute('data-ckan-url');; + + + for (let i = 0; i < componentsList.length; i++) { + const component = componentsList[i]; + const publicEntitiesData = JSON.parse(decodeHtmlEntities(component.getAttribute('data-public-entities-json'))).data; + const financialYear = decodeHtmlEntities(component.getAttribute('data-year')); + + + const publicEntities = [ + { + ...publicEntitiesData, + }, + ]; + + const { department, functiongroup1, phrase } = window.vulekamali.qs; + + ReactDOM.render( + , + component, + ); + } +} + + +export default scripts(); diff --git a/assets/js/scripts.js b/assets/js/scripts.js index 04d3e8b26..286492b67 100644 --- a/assets/js/scripts.js +++ b/assets/js/scripts.js @@ -33,6 +33,11 @@ import './components/department-budgets/IntroSection/scripts.jsx'; import './components/department-budgets/ArrowButtons/scripts.js'; import './components/department-budgets/PerformanceIndicators/scripts.jsx'; +import './components/public-entity-budgets/PublicEntitySearch/scripts.jsx'; +import './components/public-entity-budgets/IntroSection/scripts.jsx'; +import './components/public-entity-budgets/ArrowButtons/scripts.js'; +import './components/public-entity-budgets/PerformanceIndicators/scripts.jsx'; + import './components/performance/Table/scripts.jsx'; import './components/contributed-data/CsoMeta/scripts.js'; diff --git a/assets/scss/components/public-entity-budgets/ArrowButtons/styles.scss b/assets/scss/components/public-entity-budgets/ArrowButtons/styles.scss new file mode 100644 index 000000000..1ea307dea --- /dev/null +++ b/assets/scss/components/public-entity-budgets/ArrowButtons/styles.scss @@ -0,0 +1,168 @@ + +.ArrowButtons { + display: none; + + @supports (display: flex) { + display: block; + + &--fixed { + position: fixed; + padding: 0.25rem; + background-color: #f0f0f0; + top: 0; + left: 0; + width: 100%; + z-index: 999999; + overflow: hidden; + transform: translateY(-100px); + transition: transform 0.4s; + box-shadow: + 0 2px 4px -1px rgba(0, 0, 0, 0.2), + 0 4px 5px 0 rgba(0, 0, 0, 0.14), + 0 1px 10px 0 rgba(0, 0, 0, 0.12); + + &.is-active { + transform: translateY(47px); + overflow: visible; + } + } + } +} + +.ArrowButtons-list { + padding: 0; + list-style-type: none; + display: flex; + width: 100%; + justify-content: space-evenly; + max-width: 810px; + margin: 0 auto; +} + +.ArrowButtons-item { + width: 100%; + margin: 0 0.5rem; +} + +.ArrowButtons-button { + padding-right: 1rem; + position: relative; + color: white; + display: block; + text-decoration: none; + + @media screen and (min-width: 900px) { + &.is-responsive { + padding-right: 3rem; + } + } + + &::after { + content: ''; + position: absolute; + right: 0; + top: 0; + width: 0; + height: 0; + border-top: 1rem solid transparent; + border-left: 1rem solid #aaa; + border-bottom: 1rem solid transparent; + } + + @media screen and (min-width: 900px) { + &.is-responsive::after { + border-top: 3rem solid transparent; + border-left: 3rem solid #aaa; + border-bottom: 3rem solid transparent; + } + } + + &--green.active::after { + border-left-color: #7bb344; + } + + &--orange.active::after { + border-left-color: #ee9f31; + } + + &--purple.active::after { + border-left-color: #ad3c64; + } + + &--link.ArrowButtons-button--green.active:hover::after { + border-left-color: darken(#7bb344, 15%); + } + + &--link.ArrowButtons-button--orange.active:hover::after { + border-left-color: darken(#ee9f31, 15%); + } + + &--link.ArrowButtons-button--purple.active:hover::after { + border-left-color: darken(#ad3c64, 15%); + } +} + +.ArrowButtons-box { + height: 2rem; + background-color: #aaa; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + padding: 0 0.5rem; + + @media screen and (min-width: 900px) { + &.is-responsive { + height: 6rem; + padding: 0 2rem; + } + } + + .ArrowButtons-button.active &--green { + background-color: #7bb344; + } + + .ArrowButtons-button--link.active:hover &--green { + background-color: darken(#7bb344, 15%); + } + + .ArrowButtons-button.active &--orange { + background-color: #ee9f31; + } + + .ArrowButtons-button--link.active:hover &--orange { + background-color: darken(#ee9f31, 15%); + } + + .ArrowButtons-button.active &--purple { + background-color: #ad3c64; + } + + .ArrowButtons-button--link.active:hover &--purple { + background-color: darken(#ad3c64, 15%); + } +} + +.ArrowButtons-description { + display: none; + + @media screen and (min-width: 900px) { + &.is-responsive { + line-height: 1.2; + display: block; + } + } +} + +.ArrowButtons-heading { + font-size: 1rem; + font-weight: bold; + line-height: 2; + + @media screen and (min-width: 900px) { + &.is-responsive { + font-size: 1.25rem; + line-height: 1; + } + } +} diff --git a/assets/scss/components/public-entity-budgets/ContributedData/styles.scss b/assets/scss/components/public-entity-budgets/ContributedData/styles.scss new file mode 100644 index 000000000..908521357 --- /dev/null +++ b/assets/scss/components/public-entity-budgets/ContributedData/styles.scss @@ -0,0 +1,25 @@ +.ContributedItem { + @media screen and ($mobile-breakpoint) { + display: table; + width: 100%; + } +} + +.ContributedItem-info { + @media screen and ($mobile-breakpoint) { + display: table-cell; + vertical-align: middle; + } +} + +.ContributedItem-button { + margin-top: 20px; + + @media screen and ($mobile-breakpoint) { + margin-top: 0; + display: table-cell; + width: 190px; + vertical-align: middle; + text-align: center; + } +} diff --git a/assets/scss/components/public-entity-budgets/InfraProjectList/styles.scss b/assets/scss/components/public-entity-budgets/InfraProjectList/styles.scss new file mode 100644 index 000000000..3e500fd26 --- /dev/null +++ b/assets/scss/components/public-entity-budgets/InfraProjectList/styles.scss @@ -0,0 +1,21 @@ +#InfraSection * { + td, th { + padding-bottom: 10px; + } + + tr:hover { + background-color: #f4f1f1; + } + + @media (max-width: 768px) { + .hidden-sm { + display: none; + } + } + + @media (max-width: 992px) { + .hidden-md { + display: none; + } + } +} diff --git a/assets/scss/components/public-entity-budgets/IntroSection/styles.scss b/assets/scss/components/public-entity-budgets/IntroSection/styles.scss new file mode 100644 index 000000000..8cec3e455 --- /dev/null +++ b/assets/scss/components/public-entity-budgets/IntroSection/styles.scss @@ -0,0 +1,42 @@ +.IntroSection { + max-width: 800px; + margin: 1rem auto; +} + +.IntroSection-text { + max-height: 300px; + overflow: hidden; + + &.is-initialised { + position: relative; + } + + &.is-open { + max-height: 100%; + } +} + +.IntroSection-button { + position: absolute; + bottom: 10px; + width: 100%; + text-align: center; +} + +.IntroSection-fade { + background: + linear-gradient( + to top, + rgba(240, 240, 240, 1) 0%, + rgba(255, 255, 255, 0) 100% + ); + height: 100px; + bottom: 0; + left: 0; + width: 100%; + position: absolute; +} + +.IntroSection-content > h2 { + font-size: 14px; +} diff --git a/assets/scss/components/public-entity-budgets/PerformanceIndicators/styles.scss b/assets/scss/components/public-entity-budgets/PerformanceIndicators/styles.scss new file mode 100644 index 000000000..63581c6ee --- /dev/null +++ b/assets/scss/components/public-entity-budgets/PerformanceIndicators/styles.scss @@ -0,0 +1,262 @@ +#js-initPerformanceIndicators { + position: relative; +} + +.performance-indicators-container { + font-size: 16px; + line-height: 18px; + background-color: #e2e3e5 !important; + border-radius: 20px !important; + font-family: Lato, sans-serif !important; + padding: 30px 30px 0px; + box-shadow: none !important; + margin-bottom: 30px; + max-height: 300px; + overflow: hidden; + position: relative; + @media screen and (max-width: 400px) { + padding: 20px 10px 10px; + } + + .programme-name { + font-size: 18px; + font-weight: 800; + line-height: 120%; + margin-top: 0px; + color: #3f3f3f; + @media screen and (max-width: 400px) { + padding-right: 20px; + padding-left: 20px; + } + } + + .programme-card { + padding: 30px; + border-radius: 20px !important; + @media screen and (max-width: 400px) { + padding: 20px; + } + + .indicator-type { + font-family: Roboto, sans-serif; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #3F3F3F; + opacity: 0.5; + margin: 0 0 5px; + } + + .indicator-name { + font-weight: 700; + font-size: 18px; + line-height: 120%; + color: #3F3F3F; + margin: 0; + } + + .quarter-selection-container { + margin-top: 15px; + border-bottom: 1px solid #f2f2f2; + padding-bottom: 15px; + + .quarter-selection { + background-color: rgba(63, 63, 63, 0.05); + border-radius: 100px; + padding: 9px 12px; + box-shadow: none; + font-weight: 700; + font-size: 16px; + line-height: 100%; + color: #3F3F3F; + margin-right: 5px; + min-width: unset; + text-transform: none; + @media screen and (max-width: 400px) { + padding: 9px 7px; + } + } + + .quarter-selection.selected { + background-color: #3F3F3F; + color: #fff; + } + + .hidden-quarter { + visibility: hidden; + } + } + + .indicator-section { + p.section-head { + font-family: Roboto, sans-serif; + font-weight: 600; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #3f3f3f; + + .chart-legend { + float: right; + text-transform: none; + font-weight: 400; + font-size: 14px; + line-height: 20px; + color: #3f3f3f; + } + } + + p.section-text { + border-radius: 16px; + background-color: rgba(0, 0, 0, 0.05); + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + display: -webkit-box; + overflow: hidden; + font-family: Roboto, sans-serif; + font-weight: 400; + font-size: 14px; + line-height: 145%; + color: #333; + padding: 12px 12px 4px; + } + + .output-text-container { + border-radius: 16px; + background-color: rgba(0, 0, 0, 0.05); + display: flex; + width: 100%; + min-height: 44px; + + .output-text { + width: 90%; + -webkit-line-clamp: 2; + display: -webkit-box; + overflow: hidden; + font-family: Roboto, sans-serif; + font-weight: 400; + font-size: 14px; + line-height: 145%; + color: #333; + padding: 12px 12px; + + .data-not-available { + opacity: 0.5; + } + } + + .read-more-output { + width: 10%; + align-self: center; + text-align: center; + display: none; + cursor: pointer; + border: none; + + svg { + pointer-events: none; + } + } + } + + .output-chart-container { + border-radius: 16px; + background-color: rgba(0, 0, 0, 0.05); + display: flex; + width: 100%; + padding: 12px; + + .active-chart { + border: 1px solid #3f3f3f; + border-radius: 6px; + } + + .bar-text { + text-align: center; + font-size: 12px; + } + + .unavailable-chart-indicator { + height: 100%; + min-height: 100px; + align-items: center; + display: flex; + @media screen and (max-width: 400px) { + min-height: 50px; + } + + svg { + margin: 0 auto; + } + } + } + + .output-text-container.read-more-visible { + .read-more-output { + display: block; + } + + .output-text { + padding: 12px 12px 4px; + } + } + } + } + + .float-right { + float: right; + } + + .IntroSection-fade { + background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.34) 100%); + } + + .IntroSection-button { + bottom: 40px; + + button { + border: none; + } + } +} + +.performance-indicators-container.is-open { + max-height: unset; + padding-bottom: 30px; +} + +.programme-btn { + text-transform: none !important; + font-weight: 700 !important; + font-size: 16px !important; + line-height: 100% !important; + font-family: Lato, sans-serif !important; + border-radius: 100px !important; + border: 1px solid #3f3f3f !important; + background-color: #fff !important; + padding: 9px 12px !important; + letter-spacing: unset !important; + font-stretch: normal !important; +} + +.MuiTooltip-tooltip { + font-family: Roboto, sans-serif !important; + font-weight: 400 !important; + font-size: 14px !important; + line-height: 145% !important; + color: #333 !important; + background-color: #fff !important; + border-radius: 20px !important; + white-space: nowrap; + max-width: unset !important; +} + +.Page-contentInner--public-entity { + @media screen and (max-width: 400px) { + padding-right: 1rem; + padding-left: 1rem; + } +} \ No newline at end of file diff --git a/assets/scss/components/public-entity-budgets/PublicEntityControl/styles.scss b/assets/scss/components/public-entity-budgets/PublicEntityControl/styles.scss new file mode 100644 index 000000000..95ab007d9 --- /dev/null +++ b/assets/scss/components/public-entity-budgets/PublicEntityControl/styles.scss @@ -0,0 +1,34 @@ +.PublicEntityControls { + @include clearfix; +} + +.PublicEntityControls-keywords { + width: 100%; + max-width: 600px; + + @media screen and (min-width: 1000px) { + width: 350px; + float: left; + } +} + +.PublicEntityControls-in { + float: left; + padding: 10px; + font-size: 14px; +} + +.PublicEntityControls-dropdown { + float: left; + width: 230px; +} + +.PublicEntityControls-itemWrap { + padding: 10px 0; + @include clearfix; + + @media screen and (min-width: 1000px) { + float: left; + padding: 0; + } +} diff --git a/assets/scss/components/public-entity-budgets/PublicEntityGroup/styles.scss b/assets/scss/components/public-entity-budgets/PublicEntityGroup/styles.scss new file mode 100644 index 000000000..91a56fefe --- /dev/null +++ b/assets/scss/components/public-entity-budgets/PublicEntityGroup/styles.scss @@ -0,0 +1,106 @@ +.PublicEntityGroup { + border-radius: 20px; + box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.25); + background-color: white; + padding: 23px 21px; + position: relative; + overflow: hidden; +} + +.PublicEntityGroup-title { + font-weight: bold; + font-weight: 900; + font-size: 18px; + padding-bottom: 23px; + margin: 0; +} + +.PublicEntityGroup-item { + font-size: 14px; +} + +.PublicEntityGroup-mapWrap { + position: relative; +} + +.PublicEntityGroup-map { + position: absolute; + z-index: 5; + right: 0; + top: 0; + height: 100%; + width: 100%; + text-align: right; + padding: 5px; + fill: #f5f5f5; +} + +.PublicEntityGroup-svg { + height: 100%; + width: auto; +} + +.PublicEntityGroup-list { + list-style: none; + padding-left: 0; + margin: 0; + + &--doubleRow { + @media screen and (min-width: 750px) { + column-count: 2; + } + } +} + +.PublicEntitySearch-table { + width: 100%; + border-collapse: collapse; + + thead { + th { + text-align: left; + padding: 10px 6px; + border-bottom: 1px solid #f2f2f2; + } + } + + tbody { + tr { + td { + padding: 10px 6px; + } + + &:hover { + td { + background-color: #f5f5f5; + } + } + } + } +} + +.PublicEntityGroup-wrap { + position: relative; + z-index: 10; +} + +.PublicEntityGroup-link { + text-decoration: underline; + color: #3f3f3f; + + &:hover { + color: #1f1f1f; + } +} + +.PublicEntityGroup-expenditure-outer { + background-color: #f5f5f5; + border-radius: 5px; + + .PublicEntityGroup-expenditure-inner { + background-color: #79b43c; + padding: 4px 6px; + font-size: smaller; + border-radius: 5px; + } +} diff --git a/assets/scss/components/public-entity-budgets/PublicEntitySearch/styles.scss b/assets/scss/components/public-entity-budgets/PublicEntitySearch/styles.scss new file mode 100644 index 000000000..a0feb54e7 --- /dev/null +++ b/assets/scss/components/public-entity-budgets/PublicEntitySearch/styles.scss @@ -0,0 +1,166 @@ +.PublicEntitySearch-keywords { + border: solid 1px #9b9b9b; + border-radius: 3px; + padding: 9px 13px; + font-size: 14px; + width: 100%; + max-width: 300px; + display: inline-block; + + &.is-loading { + height: 36px; + } +} + +.PublicEntitySearch-groupWrap { + padding-bottom: 20px; +} + +.PublicEntitySearch-select { + border-radius: 3px; + border: solid 1px #9b9b9b; + padding-left: 0; + display: table; + min-width: 170px; + margin: 0; + background-color: white; +} + +.PublicEntitySearch-filter { + display: inline-block; + vertical-align: top; + max-height: 36px; +} + +.PublicEntitySearch-label { + cursor: pointer; + padding: 9px 13px; + font-size: 14px; + display: block; + user-select: none; + + &:hover { + background-color: darken(white, 15%); + } + + .PublicEntitySearch-select.is-open .PublicEntitySearch-item.is-active & { + border-bottom: 1px solid #4a4a4a; + } + + .PublicEntitySearch-item.is-active &::after { + content: ''; + display: inline-block; + float: right; + width: 0; + height: 0; + position: relative; + border: 5px solid transparent; + border-top: 5px solid #4a4a4a; + margin-left: 10px; + top: 5px; + transform: rotate(360deg); + transition: transform 0.3s, top 0.3s; + + .PublicEntitySearch-select.is-open & { + top: 2px; + transform: rotate(180deg); + } + } +} + +.PublicEntitySearch-item { + background: white; + cursor: pointer; + list-style: none; + display: none; + + .PublicEntitySearch-select.is-open & { + z-index: z-index('PublicEntitySearch-select'); + display: table-row; + } + + &.is-active { + display: table-header-group !important; + } +} + +.PublicEntitySearch-radio { + display: none; +} + +.PublicEntitySearch-divider { + padding: 10px 10px 10px 0; + display: inline-block; + font-size: 14px; + + @media screen and (min-width: 800px) { + padding-left: 20px; + } +} + +.PublicEntitySearch-group { + padding-top: 30px; + line-height: 1.5; + @include clearfix; + + &--blank { + opacity: 0.3; + } +} + +.PublicEntitySearch-title { + padding: 0 7px; + font-size: 14px; + font-weight: bold; +} + +.PublicEntitySearch-link { + font-size: 14px; + color: #4a4a4a; + text-decoration: none; + border-radius: 3px; + padding: 0 7px; + display: block; + + &:hover { + background-color: darken(white, 5%); + } +} + +.PublicEntitySearch-list { + list-style: none; + padding-left: 5px; +} + +.PublicEntitySearch-results { + padding-left: 0; + list-style: none; +} + +.PublicEntitySearch-nestedResults { + padding-left: 0; + list-style: none; + + @media screen and (min-width: 900px) { + column-count: 2; /* stylelint-disable-line plugin/no-unsupported-browser-features */ // Is visual enhancement + } +} + +.PublicEntitySearch-items { + padding-left: 0; + list-style: none; +} + +.PublicEntitySearch-filterGroup { + padding-top: 15px; + vertical-align: top; + + @media screen and (min-width: 800px) { + display: inline-block; + padding: 0; + } +} + +.PublicEntitySearch-filter { + min-width: 150px; +} \ No newline at end of file diff --git a/assets/scss/components/public-entity-budgets/PublicEntitySectionHead/styles.scss b/assets/scss/components/public-entity-budgets/PublicEntitySectionHead/styles.scss new file mode 100644 index 000000000..172f345b6 --- /dev/null +++ b/assets/scss/components/public-entity-budgets/PublicEntitySectionHead/styles.scss @@ -0,0 +1,21 @@ + +.PublicEntitySectionHead { + font-size: 18px; + color: white; + font-weight: bold; + background-color: grey; + border-radius: 15px 15px 0 0; + padding: 10px 20px; + + &--green { + background-color: #7bb344; + } + + &--orange { + background-color: #ee9f31; + } + + &--purple { + background-color: #ad3c64; + } +} diff --git a/assets/scss/components/public-entity-budgets/SectionIndicator/styles.scss b/assets/scss/components/public-entity-budgets/SectionIndicator/styles.scss new file mode 100644 index 000000000..c92d76678 --- /dev/null +++ b/assets/scss/components/public-entity-budgets/SectionIndicator/styles.scss @@ -0,0 +1,58 @@ +.SectionIndicator { + position: fixed; + height: 100%; + width: 1rem; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + left: 0; + top: 0; + + @media screen and (min-width: 750px) { + width: 3rem; + } +} + +.SectionIndicator-list { + list-style: none; + margin: 0; + padding: 0; +} + +.SectionIndicator-item { + margin: 0; + padding: 0; +} + +.SectionIndicator-circle { + display: block; + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + border: 1px solid grey; + margin-bottom: 1rem; + + @media screen and (min-width: 750px) { + width: 1rem; + height: 1rem; + } + + &--green { + &.active { + background: #7bb344; + } + } + + &--orange { + &.active { + background: #ee9f31; + } + } + + &--purple { + &.active { + background: #ad3c64; + } + } +} diff --git a/assets/scss/components/universal/PseudoSelect/styles.scss b/assets/scss/components/universal/PseudoSelect/styles.scss index 5afe66dae..b9284b60b 100644 --- a/assets/scss/components/universal/PseudoSelect/styles.scss +++ b/assets/scss/components/universal/PseudoSelect/styles.scss @@ -49,6 +49,7 @@ font-size: 14px; display: block; user-select: none; + @include clearfix; &:hover { @@ -82,4 +83,8 @@ .PseudoSelect-text { display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 330px; } diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss index 6cf370f07..3753a7200 100644 --- a/assets/scss/styles.scss +++ b/assets/scss/styles.scss @@ -104,6 +104,16 @@ @import 'components/department-budgets/InfraProjectList/styles'; @import 'components/department-budgets/SectionIndicator/styles'; @import 'components/department-budgets/PerformanceIndicators/styles'; +@import 'components/public-entity-budgets/PublicEntityControl/styles'; +@import 'components/public-entity-budgets/PublicEntityGroup/styles'; +@import 'components/public-entity-budgets/PublicEntitySearch/styles'; +@import 'components/public-entity-budgets/IntroSection/styles'; +@import 'components/public-entity-budgets/ContributedData/styles'; +@import 'components/public-entity-budgets/ArrowButtons/styles'; +@import 'components/public-entity-budgets/PublicEntitySectionHead/styles'; +@import 'components/public-entity-budgets/InfraProjectList/styles'; +@import 'components/public-entity-budgets/SectionIndicator/styles'; +@import 'components/public-entity-budgets/PerformanceIndicators/styles'; @import 'components/contributed-data/CsoMeta/styles'; @import 'components/contributed-data/Screenshots/styles'; @import 'components/learning-centre/Glossary/styles'; diff --git a/assets/scss/utilities/scss/variables/z-index.scss b/assets/scss/utilities/scss/variables/z-index.scss index 72adc9865..92cd8074c 100644 --- a/assets/scss/utilities/scss/variables/z-index.scss +++ b/assets/scss/utilities/scss/variables/z-index.scss @@ -3,6 +3,10 @@ @return 10; } + @if $name == 'PublicEntitySearch-select' { + @return 10; + } + @if $name == 'SearchResult-item' { @return 10; } diff --git a/budgetportal/admin.py b/budgetportal/admin.py index 5823e9131..451978fba 100644 --- a/budgetportal/admin.py +++ b/budgetportal/admin.py @@ -14,6 +14,8 @@ from .import_export_admin import ( DepartmentImportForm, DepartmentResource, + PublicEntityImportForm, + PublicEntityResource, InfrastructureProjectResource, ) @@ -96,6 +98,59 @@ def get_financial_year(self, obj): return obj.government.sphere.financial_year.slug +class PublicEntityAdmin(ImportMixin, admin.ModelAdmin): + # Resource class to be used by the django-import-export package + resource_class = PublicEntityResource + # File formats that can be used to import public entities + formats = [CSV] + + def get_import_form(self): + """ + Get the import form to use by the django-import-export package + to import public entities. + """ + return PublicEntityImportForm + + def get_resource_kwargs(self, request, *args, **kwargs): + """ + Get the kwargs to send on to the public entity resource when + we import public entities. + """ + if "sphere" in request.POST: + return {"sphere": request.POST["sphere"]} + return {} + + list_display = ( + "name", + "functiongroup1", + "department", + "get_financial_year", + ) + list_display_links = ("name") + list_filter = ( + "government__sphere__financial_year__slug", + "government__sphere__name", + "government__name", + ) + search_fields = ( + "government__sphere__financial_year__slug", + "government__sphere__name", + "government__name", + "name", + ) + readonly_fields = ("slug",) + list_per_page = 20 + + def get_government(self, obj): + return obj.government.name + + def get_sphere(self, obj): + return obj.government.sphere.name + + def get_financial_year(self, obj): + return obj.government.sphere.financial_year.slug + + class ProgrammeAdmin(admin.ModelAdmin): list_display = ( "programme_number", @@ -229,6 +284,7 @@ class ShowcaseItemAdmin(SortableAdmin): admin.site.register(models.Government, GovernmentAdmin) admin.site.register(models.GovtFunction, GovtFunctionAdmin) admin.site.register(models.Department, DepartmentAdmin) +admin.site.register(models.PublicEntity, PublicEntityAdmin) admin.site.register(models.InfrastructureProjectPart, InfrastructureProjectAdmin) admin.site.register(models.Programme, ProgrammeAdmin) admin.site.register(User, UserAdmin) diff --git a/budgetportal/import_export_admin.py b/budgetportal/import_export_admin.py index eab833a31..6dbf1e060 100644 --- a/budgetportal/import_export_admin.py +++ b/budgetportal/import_export_admin.py @@ -116,6 +116,30 @@ def get_instance(self, row): return None +class PublicEntityInstanceLoader(ModelInstanceLoader): + """ + Class to find a Public Entity model instance from a row. + """ + + def get_instance(self, row): + """ + Gets a Public Entity instance by either a unique government-slug or + government-name combination. + """ + name = self.resource.fields["name"].clean(row) + slug = slugify(name) + government = self.resource.fields["government"].clean(row) + + q = Q(name=name, government=government) + q |= Q(slug=slug, government=government) + + try: + return models.PublicEntity.objects.get(q) + except models.PublicEntity.DoesNotExist: + pass + + return None + class DepartmentResource(resources.ModelResource): """ @@ -155,6 +179,38 @@ def __init__(self, *args, **kwargs): self.fields["government"].widget.set_sphere(self.sphere) +class PublicEntityResource(resources.ModelResource): + """ + Class to help django-import-export know how to map the rows in public entity + import files to django models. + """ + + name = Field(attribute="name", column_name="entity_name") + pfma = Field(attribute="pfma", column_name="pfma") + functiongroup1 = Field(attribute="functiongroup1", column_name="functiongroup1") + government = Field( + attribute="government", + column_name="government", + widget=CustomGovernmentWidget(), + ) + + class Meta: + model = models.PublicEntity + fields = ( + "government", + "name", + "pfma", + "functiongroup1" + ) + instance_loader_class = PublicEntityInstanceLoader + import_id_fields = ["government", "name"] + + def __init__(self, *args, **kwargs): + if "sphere" in kwargs: + self.sphere = kwargs["sphere"] + self.fields["government"].widget.set_sphere(self.sphere) + + class DepartmentImportForm(ImportForm): """ Form class to use to upload a CSV file to import departments. @@ -163,6 +219,15 @@ class DepartmentImportForm(ImportForm): sphere = forms.ModelChoiceField(queryset=models.Sphere.objects.all(), required=True) +class PublicEntityImportForm(ImportForm): + """ + Form class to use to upload a CSV file to import public entities. + """ + + sphere = forms.ModelChoiceField(queryset=models.Sphere.objects.all(), required=True) + + + class InfrastructureProjectProvinceField(Field): """ The only reason to override this class is so that we can force the clean() diff --git a/budgetportal/migrations/0073_publicentity.py b/budgetportal/migrations/0073_publicentity.py new file mode 100644 index 000000000..49df036d9 --- /dev/null +++ b/budgetportal/migrations/0073_publicentity.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.20 on 2024-01-25 09:33 + +import autoslug.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('budgetportal', '0072_mainmenuitem_highlight_as_new'), + ] + + operations = [ + migrations.CreateModel( + name='PublicEntity', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='The public entity name must precisely match the text used . All datasets must be normalised to match this name. Beware that changing this name might cause a mismatch with already-published datasets which might need to be update to match this.', max_length=200)), + ('slug', autoslug.fields.AutoSlugField(always_update=True, editable=True, max_length=200, populate_from='name')), + ('intro', models.TextField(default='A description of this public entity.')), + ('pfma', models.CharField(choices=[('1', '1'), ('2', '2'), ('3A', '3A'), ('3B', '3B'), ('NL', 'Not listed')], max_length=2)), + ('functiongroup1', models.CharField(blank=True, choices=[('GPS', 'General public services'), ('ED', 'Economic development'), ('LAC', 'Learning and culture'), ('SD', 'Social development'), ('PAS', 'Peace and security'), ('CD', 'Community development'), ('H', 'Health')], max_length=3, null=True)), + ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='budgetportal.Department')), + ('government', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='public_entities', to='budgetportal.Government')), + ], + options={ + 'verbose_name_plural': 'public entities', + 'ordering': ['name'], + 'unique_together': {('government', 'slug'), ('government', 'name')}, + }, + ), + ] diff --git a/budgetportal/models/__init__.py b/budgetportal/models/__init__.py index 0032c16d8..75a52ebd6 100644 --- a/budgetportal/models/__init__.py +++ b/budgetportal/models/__init__.py @@ -30,6 +30,7 @@ Government, GovtFunction, Department, + PublicEntity, Programme, SPHERE_SLUG_CHOICES, NATIONAL_SLUG, diff --git a/budgetportal/models/government.py b/budgetportal/models/government.py index 32dd13287..bfe67b66d 100644 --- a/budgetportal/models/government.py +++ b/budgetportal/models/government.py @@ -53,6 +53,24 @@ "actual": "Audit Outcome", } +PFMA_CHOICES = [ + ("1", "1"), + ("2", "2"), + ("3A", "3A"), + ("3B", "3B"), + ("NL", "Not listed"), +] + +FUNCTIONGROUP1_CHOICES = ( + ("GPS", "General public services"), + ("ED", "Economic development"), + ("LAC", "Learning and culture"), + ("SD", "Social development"), + ("PAS", "Peace and security"), + ("CD", "Community development"), + ("H", "Health"), +) + DIRECT_CHARGE_NRF = "Direct charge against the National Revenue Fund" URL_LENGTH_LIMIT = 2000 @@ -326,7 +344,8 @@ def get_financial_year(self): def get_latest_department_instance(self): """Try to find the department in the most recent year with the same slug. - Continue traversing backwards in time until found, or until the original year has been reached.""" + Continue traversing backwards in time until found, or until the original year has been reached. + """ newer_departments = Department.objects.filter( government__slug=self.government.slug, government__sphere__slug=self.government.sphere.slug, @@ -1648,3 +1667,87 @@ def csv_url(aggregate_url): % URL_LENGTH_LIMIT ) return csv_url + + +class PublicEntityManager(models.Manager): + def get_by_natural_key( + self, financial_year, sphere_slug, government_slug, public_entity_slug + ): + return self.get( + slug=public_entity_slug, + government__slug=government_slug, + government__sphere__slug=sphere_slug, + government__sphere__financial_year__slug=financial_year, + ) + + +class PublicEntity(models.Model): + objects = PublicEntityManager() + + organisational_unit = "public_entity" + government = models.ForeignKey( + Government, on_delete=models.CASCADE, related_name="public_entities" + ) + department = models.ForeignKey(Department, on_delete=models.CASCADE) + name = models.CharField( + max_length=200, + help_text="The public entity name must precisely match the text used " + ". All datasets must be normalised to match this name. Beware that changing " + "this name might cause a mismatch with already-published datasets which might " + "need to be update to match this.", + ) + slug = AutoSlugField( + populate_from="name", max_length=200, always_update=True, editable=True + ) + intro = models.TextField(default="A description of this public entity.") + + pfma = models.CharField(max_length=2, blank=False, null=False, choices=PFMA_CHOICES) + functiongroup1 = models.CharField( + max_length=3, blank=True, null=True, choices=FUNCTIONGROUP1_CHOICES + ) + + class Meta: + unique_together = (("government", "slug"), ("government", "name")) + ordering = ["name"] + verbose_name_plural = "public entities" + + @classmethod + def get_in_latest_government(cls, name, government_name): + """ + Get a public entity instance whose slug matches the provided name slugified, + in the government with the provided name in the latest financial year. + Returns None if a matching public entity is not found. + """ + try: + return cls.objects.filter( + slug=slugify(name), government__name=government_name + ).order_by("-government__sphere__financial_year__slug")[0] + except IndexError: + return None + + def get_url_path(self): + """e.g. 2018-19/national/public-entities/military-veterans""" + return "%s/public-entities/%s" % (self.government.get_url_path(), self.slug) + + def get_financial_year(self): + return self.government.sphere.financial_year + + def get_latest_department_instance(self): + """Try to find the department in the most recent year with the same slug. + Continue traversing backwards in time until found, or until the original year has been reached. + """ + newer_public_entities = PublicEntity.objects.filter( + government__slug=self.government.slug, + government__sphere__slug=self.government.sphere.slug, + slug=self.slug, + ).order_by("-government__sphere__financial_year__slug") + return newer_public_entities.first() if newer_public_entities else None + + def _get_financial_year_query(self): + return '+vocab_financial_years:"%s"' % self.get_financial_year().slug + + def _get_government_query(self): + return none_selected_query("vocab_provinces") + + def __str__(self): + return "<%s %s>" % (self.__class__.__name__, self.get_url_path()) diff --git a/budgetportal/templates/public-entities_list.html b/budgetportal/templates/public-entities_list.html deleted file mode 100644 index eaea3e403..000000000 --- a/budgetportal/templates/public-entities_list.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends 'page-shell.html' %} {% load define_action %} {% block page_content%} - -
-
- - -

Public Entities

- -
-

- Public entities in South Africa are state-owned companies or - institutions that provide goods or services to the public. They are - accountable to the government departments that falls under the same - ministerial portfolio. The relationship between public entities and - their partner departments is governed by - legislation, policy, and memoranda of understanding. -

-
- -
-
-
-{% endblock %} diff --git a/budgetportal/templates/public_entity.html b/budgetportal/templates/public_entity.html new file mode 100644 index 000000000..c4b14ec1d --- /dev/null +++ b/budgetportal/templates/public_entity.html @@ -0,0 +1,354 @@ +{% extends 'page-shell.html' %} +{% load define_action %} +{% load humanize %} +{% block page_content %} + + {% if government.slug == 'south-africa' %} + {% assign 'National' as department_location %} + {% else %} + {% assign government.name as department_location %} + {% endif %} + + + {% if government.slug == 'south-africa' %} + {% assign 'Estimates of National Expenditure' as source_type %} + {% assign 'Estimates of National Expenditure' as source_type_revenue %} + {% assign 'ENE' as source_type_revenue_short %} + {% assign "Adjusted Estimates of National Expenditure" as source_type_adjusted %} + {% assign "AENE" as source_type_adjusted_short %} + {% assign "/learning-resources/guides/estimates-of-national-expenditure" as guide %} + {% else %} + {% assign 'Estimates of Provincial Expenditure' as source_type %} + {% assign 'Estimates of Provincial Revenue and Expenditure' as source_type_revenue %} + {% assign 'EPRE' as source_type_revenue_short %} + {% assign "Adjusted Estimates of Provincial Revenue and Expenditure" as source_type_adjusted %} + {% assign "AEPRE" as source_type_adjusted_short %} + {% assign "/learning-resources/guides/estimates-of-provincial-expenditure" as guide %} + {% endif %} + + + {% if department_budget %} + {% assign department_budget.name as chapter_name %} + {% assign department_budget.document.url as pdf_link %} + {% assign department_budget.tables.url as excel_link %} + {% else %} + {% assign "" as chapter_name %} + {% assign "" as pdf_link %} + {% assign "" as excel_link %} + {% endif %} + + + {% if department_adjusted_budget %} + {% assign department_adjusted_budget.document.url as pdf_link_adjusted %} + {% assign department_adjusted_budget.tables.url as excel_link_adjusted %} + {% else %} + {% assign "" as pdf_link_adjusted %} + {% assign "" as excel_link_adjusted %} + {% endif %} + + + {% if treasury_datasets %} + {% for item in treasury_datasets %} + {% assign item.1.formats as modified_formats %} {# | sort: 'format' %} #} + {% assign modified_formats.0.url as pdf %} + {% assign modified_formats.1.url as excel %} + {% endfor %} + {% else %} + {% assign pdf_link as pdf %} + {% assign excel_link as excel %} + {% endif %} + + + {% include 'components/department-budgets/ArrowButtons/index.html' with fixed="true" link_1="#section-plan" link_2="#section-implement" link_3="#section-review" %} + + {% if sphere.slug == "national" and slug == "parliament" %} + {% assign "true" as parliament %} + {% else %} + {% assign "" as parliament %} + {% endif %} + + {% assign department_location|add:" "|add:name|add:" Department Budget "|add:selected_financial_year as subtitle %} + + +
+
+ +

+ {{ name }} +

+ + {{ department_location }} Public Entity Budget for {{ selected_financial_year }} + + {% if website_url %} + + {{ website_url }} + + {% endif %} + {% include 'components/department-budgets/IntroSection/index.html' with description=intro datasets=treasury_datasets location=department_location %} + +
+ The Budget Cycle +
+ +
+ {% include 'components/department-budgets/ArrowButtons/index.html' with link_1="#section-plan" link_2="#section-implement" link_3="#section-review" %} +
+ +
+
+ Plan +
+
+ +
+

{{ selected_financial_year }} Budget

+

+ The {{ source_type_revenue }} ({{ source_type_revenue_short }}) is a book published along with the + tabling of the budget for the new financial year. +

+ + {% assign "View the "|add:source_type_revenue_short|add:" chapter for "|add:chapter_name|add:" (PDF)" as text1 %} + {% assign "View tables in the "|add:source_type_revenue_short|add:" chapter (Excel)" as text2 %} + + {% if pdf_link or excel_link %} + + {% endif %} +
+ +
+ {% include 'scenes/department/ProgrammesSection/index.html' with source_type=source_type year=selected_financial_year pdf=pdf excel=excel guide=guide %} +
+ +
+ {% include 'scenes/department/EconClassPackedCircles/econ-class-packed-circles.html' with source_type=source_type year=selected_financial_year pdf=pdf excel=excel guide=guide %} +
+ +
+ {% include 'scenes/department/ProgramEconSmallMultiples/programme-econ-small-muls.html' with source_type=source_type year=selected_financial_year pdf=pdf excel=excel guide=guide %} +
+ +

{{ selected_financial_year }} Adjusted Budget

+

+ The {{ source_type_adjusted }} ({{ source_type_adjusted_short }}) is a book published along with the + tabling of the adjusted budget. +

+ + {% assign "View the "|add:source_type_adjusted_short|add:" chapter for "|add:chapter_name|add:" (PDF)." as text1 %} + {% assign "View tables in the "|add:source_type_adjusted_short|add:" chapter (Excel)." as text2 %} + + {% if pdf_link_adjusted or excel_link_adjusted %} + + {% else %} + + {% if excel_link_adjusted %} +
+
+ {% include 'components/Icon/index.html' with type="info" %} + + Please note + +
+
The {{ source_type_adjusted_short }} chapter + for {{ chapter_name }} (PDF) is not available yet +
+
+ {% elif pdf_link_adjusted %} +
+
+ {% include 'components/Icon/index.html' with type="info" %} + + Please note + +
+
The tables in {{ source_type_revenue_short }} + chapter (Excel) is not available yet +
+
+ {% else %} + {% assign "The "|add:source_type_revenue_short|add:" chapter for "|add:chapter_name|add:" (PDF) is not available yet" as info2 %} + {% assign "The tables in "|add:source_type_revenue_short|add:" chapter (Excel)" as info3 %} + +
+
+ {% include 'components/Icon/index.html' with type="info" %} + + Please note + +
+
The adjusted budget documents are not + available yet on vulekamali.gov.za +
+
+ {% endif %} + {% endif %} + + + {% if government.slug == 'south-africa' %} +
+ {% include 'scenes/department/AdjustedSection/index.html' with type="adjusted" items=adjusted_budget_summary source_type=source_type source_type_adjusted=source_type_adjusted year=selected_financial_year pdf=pdf excel=excel pdf_adjusted=pdf_link_adjusted excel_adjusted=excel_link_adjusted csv=adjusted_budget_summary.department_data_csv dataset=adjusted_budget_summary.dataset_detail_page parliament=parliament title="Programme budgets" subtitle=subtitle description="Activities of this department" %} +
+ {% endif %} + + {% if procurement_resource_links %} +
+ {% include 'scenes/department/ResourceLinks/resource-links.html' with resource_links=procurement_resource_links section_title="Procurement resources" more_link="/datasets/procurement-portals-and-resources" %} +
+ {% endif %} + +
+
+ Implement +
+
+ + {% if infra_enabled %} +
+

Department infrastructure projects

+ +

Largest infrastructure projects by this department.

+ +
+ + + + + + + {% for project in projects %} + + + + + + {% empty %} + + + + {% endfor %} +
Project nameEstimated completion date
+ + {{ project.name }} + + {{ project.estimated_completion_date|default:"Not available" }}
No projects available for this department.
+
+ + + +
+ {% endif %} + + {% if in_year_monitoring_resource_links %} +
+ {% include 'scenes/department/ResourceLinks/resource-links.html' with resource_links=in_year_monitoring_resource_links section_title="In-year monitoring resources" %} +
+ {% endif %} + + {% if performance_resource_links %} +
+ {% include 'scenes/department/ResourceLinks/resource-links.html' with resource_links=performance_resource_links section_title="Performance monitoring resources" %} +
+ {% endif %} + + {% if eqprs_data_enabled %} +
+ {% endif %} + + {% if not infra_enabled and not in_year_monitoring_resource_links and not performance_resource_links %} +
+
+ {% include 'components/Icon/index.html' with type="info" size="l" %} + + Please note + +
+
Implementation data coming soon.
+
+ {% endif %} + +
+
+ Review +
+
+ + {% if expenditure_over_time %} +
+ {% include 'scenes/department/ExpenditureSection/index.html' with items=expenditure_over_time.expenditure cpi=global_values.cpi_dataset_url source_type=source_type year=selected_financial_year dataset=expenditure_over_time.dataset_detail_page pdf=pdf_link excel=excel csv=expenditure_over_time.department_data_csv guide=guide color="purple" title="Planned compared to historical expenditure" subtitle=subtitle description="Expenditure changes over time" %} +
+ {% endif %} + + {% with ""|add:department_location|add:" "|add:name|add:" Department" as text %} + + {% if budget_actual %} +
+ {% include 'scenes/department/ExpenditurePhaseSection/index.html' with items=budget_actual.expenditure cpi=global_values.cpi_dataset_url source_type="Expenditure Time Series" year=selected_financial_year dataset=budget_actual.dataset_detail_page csv=budget_actual.department_data_csv color="purple" description="Budgeted and Actual Expenditure comparison" subtitle=review_subtitle notices=budget_actual.notices website_url=website_url %} +
+ {% endif %} + + {% if budget_actual_programmes %} +
+ {% include 'scenes/department/ExpenditureMultiplesSection/index.html' with items=budget_actual_programmes.programmes cpi=global_values.cpi_dataset_url source_type="Expenditure Time Series" year=selected_financial_year dataset=budget_actual_programmes.dataset_detail_page csv=budget_actual_programmes.department_data_csv color="purple" subtitle=review_subtitle description="Budgeted and Actual Expenditure comparison by Programme" notices=budget_actual_programmes.notices %} +
+ {% endif %} + {% endwith %} +
+
+
+ {% include 'components/department-budgets/ContributedData/index.html' with datasets=contributed_datasets %} +
+ +
+ {% include 'components/universal/Participate/index.html' with title="Timelines for this department and ways to participate" description="National Treasury, departments and commitees are busy with different things depending on the time of year:" %} + + {% if comments_enabled %} +
+

Discuss this budget with others

+
+
+
+
+ {% endif %} +
+
+
+ + +
+
+{% endblock %} diff --git a/budgetportal/templates/public_entity_list.html b/budgetportal/templates/public_entity_list.html new file mode 100644 index 000000000..81ac11667 --- /dev/null +++ b/budgetportal/templates/public_entity_list.html @@ -0,0 +1,19 @@ +{% extends 'page-shell.html' %} +{% load define_action %} +{% block page_content %} + +
+
+

Public Entities Expenditure for {{ selected_financial_year }}

+

+ Public entities in South Africa are state-owned companies or + institutions that provide goods or services to the public. They are + accountable to the government departments that falls under the same + ministerial portfolio. The relationship between public entities and + their partner departments is governed by + legislation, policy, and memoranda of understanding. +

+ {% include 'components/public-entity-budgets/PublicEntitySearch/index.html' with national=national.0 %} +
+
+{% endblock %} diff --git a/budgetportal/urls.py b/budgetportal/urls.py index 284024cef..c3a2b2a7a 100644 --- a/budgetportal/urls.py +++ b/budgetportal/urls.py @@ -51,6 +51,12 @@ def trigger_error(request): ), ] +public_entity_urlpatterns = [ + url( + r"^$", cache_page(CACHE_MINUTES_SECS)(views.public_entity_page), name="public-entity" + ), +] + urlpatterns = [ url("sentry-debug/", trigger_error), url( @@ -244,6 +250,25 @@ def trigger_error(request): "/(?P[\w-]+)/", include((department_urlpatterns, "provincial"), namespace="provincial"), ), + # Public Entities List + url( + r"^latest/public-entities$", + views.latest_public_entity_list, + name="latest-public-entity-list", + ), + url( + r"^(?P\d{4}-\d{2})/public-entities$", + cache_page(CACHE_MINUTES_SECS)(views.public_entity_list), + name="public-entity-list", + ), + # Public Entity detail + # - National + url( + r"^(?P\d{4}-\d{2})/national/public-entities/(?P[\w-]+)/", + include((public_entity_urlpatterns, "national"), namespace="national"), + kwargs={"sphere_slug": "national", "government_slug": "south-africa"}, + name="national-public-entity", + ), url( r"^robots\.txt$", views.robots, @@ -279,8 +304,8 @@ def trigger_error(request): # Public Entities List url( r"^public-entities$", - views.public_entities_list, - name="public-entities-list", + views.public_entity_list, + name="public-entity-list", ), url("^", include(webflow_urls.urlpatterns)), re_path(r"^cms/", include(wagtailadmin_urls)), diff --git a/budgetportal/views.py b/budgetportal/views.py index 3e43a2f01..d14e052b0 100644 --- a/budgetportal/views.py +++ b/budgetportal/views.py @@ -277,6 +277,59 @@ def department_list_data(financial_year_id): return page_data +def public_entity_list_data(financial_year_id): + selected_year = get_object_or_404(FinancialYear, slug=financial_year_id) + page_data = { + "financial_years": [], + "selected_financial_year": selected_year.slug, + "selected_tab": "public_entities", + "slug": "public-entities", + "title": "Public Entities Budgets for %s - vulekamali" % selected_year.slug, + "public_entities": [], + "description": "Public Entities budgets for the %s financial year %s" + % (selected_year.slug, COMMON_DESCRIPTION_ENDING), + } + + for year in FinancialYear.get_available_years(): + is_selected = year.slug == financial_year_id + page_data["financial_years"].append( + { + "id": year.slug, + "is_selected": is_selected, + "closest_match": { + "is_exact_match": True, + "url_path": "/%s/public-entities" % year.slug, + }, + } + ) + + for government in ( + selected_year.spheres.filter(slug="national").first().governments.all() + ): + public_entities = [] + for public_entity in government.public_entities.all(): + public_entities.append( + { + "name": public_entity.name, + "slug": str(public_entity.slug), + "url_path": public_entity.get_url_path(), + "department": public_entity.department.name, + "department_slug": public_entity.department.slug, + "department_sphere": public_entity.department.government.sphere.slug, + "functiongroup1": public_entity.functiongroup1, + "selected_year_slug": selected_year.slug, + } + ) + public_entities = sorted(public_entities, key=lambda d: d["name"]) + page_data["public_entities"].append(public_entities) + + page_data["public_entities"] = [ + item for sublist in page_data["public_entities"] for item in sublist + ] + + return page_data + + def department_page( request, financial_year_id, sphere_slug, government_slug, department_slug ): @@ -455,6 +508,77 @@ def department_page( return render(request, "department.html", context) +def public_entity_page( + request, financial_year_id, sphere_slug, government_slug, public_entity_slug +): + public_entity = None + selected_year = get_object_or_404(FinancialYear, slug=financial_year_id) + + years = FinancialYear.get_available_years() + for year in years: + if year.slug == financial_year_id: + selected_year = year + sphere = selected_year.spheres.filter(slug=sphere_slug).first() + government = sphere.governments.filter(slug=government_slug).first() + public_entity = government.public_entities.filter( + slug=public_entity_slug + ).first() + + financial_years_context = [] + for year in years: + closest_match, closest_is_exact = year.get_closest_match(public_entity) + financial_years_context.append( + { + "id": year.slug, + "is_selected": year.slug == financial_year_id, + "closest_match": { + "url_path": closest_match.get_url_path(), + "is_exact_match": closest_is_exact, + }, + } + ) + + govt_label = "National" + + context = { + "comments_enabled": settings.COMMENTS_ENABLED, + "financial_years": financial_years_context, + "government": { + "name": public_entity.government.name, + "label": govt_label, + "slug": str(public_entity.government.slug), + }, + "intro": public_entity.intro, + "name": public_entity.name, + "slug": str(public_entity.slug), + "sphere": { + "name": public_entity.government.sphere.name, + "slug": public_entity.government.sphere.slug, + }, + "selected_financial_year": financial_year_id, + "selected_tab": "public_entities", + "title": "%s budget %s - vulekamali" + % (public_entity.name, selected_year.slug), + "description": "%s public entity: %s budget data for the %s financial year %s" + % ( + govt_label, + public_entity.name, + selected_year.slug, + COMMON_DESCRIPTION_ENDING, + ), + } + context["navbar"] = MainMenuItem.objects.prefetch_related("children").all() + context["latest_year"] = FinancialYear.get_latest_year().slug + context["global_values"] = read_object_from_yaml( + str(settings.ROOT_DIR.path("_data/global_values.yaml")) + ) + context["admin_url"] = reverse( + "admin:budgetportal_department_change", args=(public_entity.pk,) + ) + + return render(request, "public_entity.html", context) + + def get_in_year_spending_urls(department, financial_years): urls = {} @@ -1019,6 +1143,18 @@ def department_list(request, financial_year_id): return render(request, "department_list.html", context) +def latest_public_entity_list(request): + url = reverse("public-entity-list", args=(FinancialYear.get_latest_year().slug,)) + return redirect(url, permanent=False) + + +def public_entity_list(request, financial_year_id): + context = public_entity_list_data(financial_year_id) + context["navbar"] = MainMenuItem.objects.prefetch_related("children").all() + context["latest_year"] = FinancialYear.get_latest_year().slug + return render(request, "public_entity_list.html", context) + + def department_list_json(request, financial_year_id): response_json = json.dumps( department_list_data(financial_year_id), @@ -1188,13 +1324,3 @@ def budget_summary_view(request): and latest_provincial_year.slug, } return render(request, "budget-summary.html", context) - - -def public_entities_list(request): - context = { - "title": "Public Entities - vulekamali", - "description": COMMON_DESCRIPTION + COMMON_DESCRIPTION_ENDING, - "selected_tab": "public-entities", - "navbar": MainMenuItem.objects.prefetch_related("children").all(), - } - return render(request, "public-entities_list.html", context) diff --git a/yarn.lock b/yarn.lock index 3d732971b..25682e303 100644 --- a/yarn.lock +++ b/yarn.lock @@ -79,7 +79,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@>=7.2.2", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.10", "@babel/core@^7.2.2", "@babel/core@^7.4.5", "@babel/core@^7.7.5": +"@babel/core@>=7.2.2", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.4.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.5.tgz#6e23f2acbcb77ad283c5ed141f824fd9f70101c7" integrity sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g== @@ -100,7 +100,38 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.2.2", "@babel/generator@^7.23.5", "@babel/generator@^7.4.0": +"@babel/core@^7.12.10", "@babel/core@^7.2.2", "@babel/core@^7.7.5": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f" + integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.7" + "@babel/parser" "^7.23.6" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== + dependencies: + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/generator@^7.2.2", "@babel/generator@^7.23.5", "@babel/generator@^7.4.0": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.5.tgz#17d0a1ea6b62f351d281350a5f80b87a810c4755" integrity sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA== @@ -124,7 +155,18 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6": +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== @@ -135,7 +177,22 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.5", "@babel/helper-create-class-features-plugin@^7.3.0": +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.23.6", "@babel/helper-create-class-features-plugin@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.7.tgz#b2e6826e0e20d337143655198b79d58fdc9bd43d" + integrity sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.5", "@babel/helper-create-class-features-plugin@^7.3.0": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz#2a8792357008ae9ce8c0f2b78b9f646ac96b314b" integrity sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A== @@ -192,6 +249,28 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" +"@babel/helper-define-polyfill-provider@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz#64df615451cb30e94b59a9696022cffac9a10088" + integrity sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-define-polyfill-provider@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz#465805b7361f461e86c680f1de21eaf88c25901b" + integrity sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + "@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" @@ -317,7 +396,16 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.19" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.2.0", "@babel/helpers@^7.23.5": +"@babel/helpers@^7.12.5", "@babel/helpers@^7.23.7": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.8.tgz#fc6b2d65b16847fd50adddbd4232c76378959e34" + integrity sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" + +"@babel/helpers@^7.2.0", "@babel/helpers@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.5.tgz#52f522840df8f1a848d06ea6a79b79eefa72401e" integrity sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg== @@ -335,11 +423,16 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.18.4", "@babel/parser@^7.2.2", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.5", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.8.3": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.2.2", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.5", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.8.3": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.5.tgz#37dee97c4752af148e1d38c34b856b2507660563" integrity sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ== +"@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a" @@ -364,6 +457,14 @@ "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz#516462a95d10a9618f197d39ad291a9b47ae1d7b" + integrity sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" @@ -400,14 +501,12 @@ "@babel/plugin-syntax-decorators" "^7.2.0" "@babel/plugin-proposal-decorators@^7.12.12": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.5.tgz#eeaa49d0dc9229aec4d23378653738cdc5a3ea0a" - integrity sha512-6IsY8jOeWibsengGlWIezp7cuZEFzNlAghFpzh9wiZwhQ42/hRcPnY/QV9HJoKTlujupinSlnQPiEy/u2C1ZfQ== + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.7.tgz#1d827902cbd3d9054e54fb2f2056cdd1eaa0e368" + integrity sha512-b1s5JyeMvqj7d9m9KhJNHKc18gEJiSyVzVX3bwbiPalQBQpuvfPh6lA9F7Kk/dWH0TIiXRpB9yicwijY6buPng== dependencies: - "@babel/helper-create-class-features-plugin" "^7.23.5" + "@babel/helper-create-class-features-plugin" "^7.23.7" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.20" - "@babel/helper-split-export-declaration" "^7.22.6" "@babel/plugin-syntax-decorators" "^7.23.3" "@babel/plugin-proposal-export-default-from@^7.12.1": @@ -703,6 +802,16 @@ "@babel/helper-remap-async-to-generator" "^7.22.20" "@babel/plugin-syntax-async-generators" "^7.8.4" +"@babel/plugin-transform-async-generator-functions@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz#3aa0b4f2fa3788b5226ef9346cf6d16ec61f99cd" + integrity sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-transform-async-to-generator@^7.2.0", "@babel/plugin-transform-async-to-generator@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz#d1f513c7a8a506d43f47df2bf25f9254b0b051fa" @@ -757,7 +866,21 @@ "@babel/helper-split-export-declaration" "^7.0.0" globals "^11.1.0" -"@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.2.0", "@babel/plugin-transform-classes@^7.23.5": +"@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.23.8": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz#d08ae096c240347badd68cdf1b6d1624a6435d92" + integrity sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-split-export-declaration" "^7.22.6" + globals "^11.1.0" + +"@babel/plugin-transform-classes@^7.2.0", "@babel/plugin-transform-classes@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz#e7a75f815e0c534cc4c9a39c56636c84fc0d64f2" integrity sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg== @@ -849,7 +972,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-flow" "^7.23.3" -"@babel/plugin-transform-for-of@^7.12.1", "@babel/plugin-transform-for-of@^7.2.0", "@babel/plugin-transform-for-of@^7.23.3": +"@babel/plugin-transform-for-of@^7.12.1", "@babel/plugin-transform-for-of@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e" + integrity sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-for-of@^7.2.0", "@babel/plugin-transform-for-of@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz#afe115ff0fbce735e02868d41489093c63e15559" integrity sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw== @@ -1159,7 +1290,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-typescript@^7.1.0", "@babel/plugin-transform-typescript@^7.23.3": +"@babel/plugin-transform-typescript@^7.1.0": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.5.tgz#83da13ef62a1ebddf2872487527094b31c9adb84" integrity sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA== @@ -1169,6 +1300,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.23.3" +"@babel/plugin-transform-typescript@^7.23.3": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz#aa36a94e5da8d94339ae3a4e22d40ed287feb34c" + integrity sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.23.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-typescript" "^7.23.3" + "@babel/plugin-transform-unicode-escapes@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz#1f66d16cab01fab98d784867d24f70c1ca65b925" @@ -1257,7 +1398,7 @@ js-levenshtein "^1.1.3" semver "^5.3.0" -"@babel/preset-env@^7.1.6", "@babel/preset-env@^7.12.11", "@babel/preset-env@^7.4.5": +"@babel/preset-env@^7.1.6": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.23.5.tgz#350a3aedfa9f119ad045b068886457e895ba0ca1" integrity sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A== @@ -1343,6 +1484,92 @@ core-js-compat "^3.31.0" semver "^6.3.1" +"@babel/preset-env@^7.12.11", "@babel/preset-env@^7.4.5": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.23.8.tgz#7d6f8171ea7c221ecd28059e65ad37c20e441e3e" + integrity sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.23.3" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.23.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.23.7" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.23.3" + "@babel/plugin-syntax-import-attributes" "^7.23.3" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.23.3" + "@babel/plugin-transform-async-generator-functions" "^7.23.7" + "@babel/plugin-transform-async-to-generator" "^7.23.3" + "@babel/plugin-transform-block-scoped-functions" "^7.23.3" + "@babel/plugin-transform-block-scoping" "^7.23.4" + "@babel/plugin-transform-class-properties" "^7.23.3" + "@babel/plugin-transform-class-static-block" "^7.23.4" + "@babel/plugin-transform-classes" "^7.23.8" + "@babel/plugin-transform-computed-properties" "^7.23.3" + "@babel/plugin-transform-destructuring" "^7.23.3" + "@babel/plugin-transform-dotall-regex" "^7.23.3" + "@babel/plugin-transform-duplicate-keys" "^7.23.3" + "@babel/plugin-transform-dynamic-import" "^7.23.4" + "@babel/plugin-transform-exponentiation-operator" "^7.23.3" + "@babel/plugin-transform-export-namespace-from" "^7.23.4" + "@babel/plugin-transform-for-of" "^7.23.6" + "@babel/plugin-transform-function-name" "^7.23.3" + "@babel/plugin-transform-json-strings" "^7.23.4" + "@babel/plugin-transform-literals" "^7.23.3" + "@babel/plugin-transform-logical-assignment-operators" "^7.23.4" + "@babel/plugin-transform-member-expression-literals" "^7.23.3" + "@babel/plugin-transform-modules-amd" "^7.23.3" + "@babel/plugin-transform-modules-commonjs" "^7.23.3" + "@babel/plugin-transform-modules-systemjs" "^7.23.3" + "@babel/plugin-transform-modules-umd" "^7.23.3" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.23.3" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.23.4" + "@babel/plugin-transform-numeric-separator" "^7.23.4" + "@babel/plugin-transform-object-rest-spread" "^7.23.4" + "@babel/plugin-transform-object-super" "^7.23.3" + "@babel/plugin-transform-optional-catch-binding" "^7.23.4" + "@babel/plugin-transform-optional-chaining" "^7.23.4" + "@babel/plugin-transform-parameters" "^7.23.3" + "@babel/plugin-transform-private-methods" "^7.23.3" + "@babel/plugin-transform-private-property-in-object" "^7.23.4" + "@babel/plugin-transform-property-literals" "^7.23.3" + "@babel/plugin-transform-regenerator" "^7.23.3" + "@babel/plugin-transform-reserved-words" "^7.23.3" + "@babel/plugin-transform-shorthand-properties" "^7.23.3" + "@babel/plugin-transform-spread" "^7.23.3" + "@babel/plugin-transform-sticky-regex" "^7.23.3" + "@babel/plugin-transform-template-literals" "^7.23.3" + "@babel/plugin-transform-typeof-symbol" "^7.23.3" + "@babel/plugin-transform-unicode-escapes" "^7.23.3" + "@babel/plugin-transform-unicode-property-regex" "^7.23.3" + "@babel/plugin-transform-unicode-regex" "^7.23.3" + "@babel/plugin-transform-unicode-sets-regex" "^7.23.3" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.7" + babel-plugin-polyfill-corejs3 "^0.8.7" + babel-plugin-polyfill-regenerator "^0.5.4" + core-js-compat "^3.31.0" + semver "^6.3.1" + "@babel/preset-flow@^7.0.0", "@babel/preset-flow@^7.12.1": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.23.3.tgz#8084e08b9ccec287bd077ab288b286fab96ffab1" @@ -1404,14 +1631,14 @@ "@babel/plugin-transform-typescript" "^7.23.3" "@babel/register@^7.12.1": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.22.15.tgz#c2c294a361d59f5fa7bcc8b97ef7319c32ecaec7" - integrity sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg== + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.23.7.tgz#485a5e7951939d21304cae4af1719fdb887bc038" + integrity sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" make-dir "^2.1.0" - pirates "^4.0.5" + pirates "^4.0.6" source-map-support "^0.5.16" "@babel/regjsgen@^0.8.0": @@ -1426,13 +1653,20 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.5.tgz#11edb98f8aeec529b82b211028177679144242db" integrity sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w== dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650" + integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.12.7", "@babel/template@^7.2.2", "@babel/template@^7.22.15", "@babel/template@^7.4.0": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1442,7 +1676,7 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.6", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.2.2", "@babel/traverse@^7.23.5", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3": +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.2.2", "@babel/traverse@^7.23.5", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.5.tgz#f546bf9aba9ef2b042c0e00d245990c15508e7ec" integrity sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w== @@ -1458,7 +1692,23 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.6", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.5", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0": +"@babel/traverse@^7.1.6", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" + integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.2.2", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.5", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.5.tgz#48d730a00c95109fa4393352705954d74fb5b602" integrity sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w== @@ -1467,6 +1717,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.2.0", "@babel/types@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4" @@ -1817,7 +2076,15 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.20": + version "0.3.22" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz#72a621e5de59f5f1ef792d0793a82ee20f645e4c" + integrity sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": version "0.3.20" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== @@ -2170,11 +2437,11 @@ integrity sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A== "@storybook/addon-a11y@*": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-7.6.3.tgz#405a5f7aed9e1b67a0caa9709724d8626d99c287" - integrity sha512-z/vaDkZgbLLqrLz2C1qr3lav5xuZDbBggtNdvnM1TFKqiaQu8MPC0oEe6QSFf2phREf7cB2Qa5LsW7ak16RddQ== + version "7.6.10" + resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-7.6.10.tgz#ad2a266d52c0b9909f221133ea41b9ffb0e7d9f7" + integrity sha512-TP17m4TAWLSSd2x9cWNg7d0MCZZCojYIG83RZMXAb55jt8gKJBMDbupOoDLydBsABQa5Uk9ZP0D/CvumMon8RA== dependencies: - "@storybook/addon-highlight" "7.6.3" + "@storybook/addon-highlight" "7.6.10" axe-core "^4.2.0" "@storybook/addon-a11y@^5.0.3": @@ -2199,10 +2466,10 @@ ts-dedent "^1.1.0" util-deprecate "^1.0.2" -"@storybook/addon-highlight@7.6.3": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-7.6.3.tgz#e6c9b8fe88acbf91c76c82e519eda7f2d129da61" - integrity sha512-Z9AJ05XCTzFZPAxQSkQf9/Hazf5/QQI0jYSsvKqt7Vk+03q5727oD9KcIY5IHPYqQqN9fHExQh1eyqY8AnS8mg== +"@storybook/addon-highlight@7.6.10": + version "7.6.10" + resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-7.6.10.tgz#19cad3d67655e9b9eef3d2f6760789fc29ba0790" + integrity sha512-dIuS5QmoT1R+gFOcf6CoBa6D9UR5/wHCfPqPRH8dNNcCLtIGSHWQ4v964mS5OCq1Huj7CghmR15lOUk7SaYwUA== dependencies: "@storybook/global" "^5.0.0" @@ -3443,14 +3710,14 @@ integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== "@types/eslint@*": - version "8.44.8" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.8.tgz#f4fe1dab9b3d3dd98082d4b9f80e59ab40f1261c" - integrity sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw== + version "8.56.2" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.2.tgz#1c72a9b794aa26a8b94ad26d5b9aa51c8a6384bb" + integrity sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^1.0.0": +"@types/estree@*", "@types/estree@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== @@ -3477,9 +3744,9 @@ "@types/node" "*" "@types/hast@^2.0.0": - version "2.3.8" - resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.8.tgz#4ac5caf38b262b7bd5ca3202dda71f0271635660" - integrity sha512-aMIqAlFd2wTIDZuvLbhUT+TGvMxrNC8ECUIVtH6xxy0sQLs3iu6NO8Kp/VT5je7i5ufnebXzdV1dNDMnvaH6IQ== + version "2.3.9" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.9.tgz#a9a1b5bbce46e8a1312e977364bacabc8e93d2cf" + integrity sha512-pTHyNlaMD/oKJmS+ZZUyFUcsZeBZpC0lmGquw98CqRVNgAdJZJeD7GoeLiT6Xbx5rU9VCjSt0RwEvDgzh4obFw== dependencies: "@types/unist" "^2" @@ -3572,9 +3839,9 @@ integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== "@types/node-fetch@^2.5.7": - version "2.6.9" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.9.tgz#15f529d247f1ede1824f7e7acdaa192d5f28071e" - integrity sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA== + version "2.6.11" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" + integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== dependencies: "@types/node" "*" form-data "^4.0.0" @@ -3592,9 +3859,9 @@ integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0": - version "16.18.67" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.67.tgz#518feb681958dedf2d187b8b4d20bf3530afe1fb" - integrity sha512-gUa0tDO9oxyAYO9V9tqxDJguVMDpqUwH5I5Q9ASYBCso+8CUdJlKPKDYS1YSS9kyZWIduDafZvucGM0zGNKFjg== + version "16.18.75" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.75.tgz#88460b2706e5be1788f5ed6ef51152283b7703a2" + integrity sha512-+FSfZd5mpMDTcIK7bp2GueIcAespzR4FROOXnEst248c85vwthIEwtXYOLgVc/sI4ihE1K/7yO1lEiSgvwAOxA== "@types/normalize-package-data@^2.4.0": version "2.4.4" @@ -3634,29 +3901,29 @@ integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw== "@types/qs@^6.9.5": - version "6.9.10" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.10.tgz#0af26845b5067e1c9a622658a51f60a3934d51e8" - integrity sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw== + version "6.9.11" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.11.tgz#208d8a30bc507bd82e03ada29e4732ea46a6bbda" + integrity sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ== "@types/reach__router@^1.2.3": - version "1.3.14" - resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.3.14.tgz#0979270997253ebdf43052a65c5f0419c77774cb" - integrity sha512-2iOQZbwfw1ZYwYK+dRp7D1b8kU6GlFPJ/iEt33zDYxfId5CAKT7vX3lN/XmJ+FaMZ3FyB99tPgfajcmZnTqdtg== + version "1.3.15" + resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.3.15.tgz#be4e23ee57786a9a16db9af3cff4c085de9e0db0" + integrity sha512-5WEHKGglRjq/Ae3F8UQxg+GYUIhTUEiyBT9GKPoOLU/vPTn8iZrRbdzxqvarOaGludIejJykHLMdOCdhgWqaxA== dependencies: "@types/react" "*" "@types/react-color@^3.0.1": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.10.tgz#f869ab3a46938fb97a5c2ee568bc6469f82b14dd" - integrity sha512-6K5BAn3zyd8lW8UbckIAVeXGxR82Za9jyGD2DBEynsa7fKaguLDVtjfypzs7fgEV7bULgs7uhds8A8v1wABTvQ== + version "3.0.11" + resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.11.tgz#65992e9fdfef70b2db939a98ce3d6a8b8456ecae" + integrity sha512-20m5GpzmdqwmSdnPeMs4UPPUuvkS4ESwakL6u2YN1hbo+ajWiiTwGYIMGhdcJFGeoLyAsr7TVonbZrMhU3+pdw== dependencies: "@types/react" "*" "@types/reactcss" "*" "@types/react-redux@^7.1.20": - version "7.1.32" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.32.tgz#bf162289e0c69e44a649dfcadb30f7f7c4cb00e4" - integrity sha512-YJYV0M27cyHHJIacaRsZRx5OETzK8KWjEGnix7UH3ngItYo4It0MUBzU6WNwqnwhbrPw5wx9KXluuoTZ85Gg7A== + version "7.1.33" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.33.tgz#53c5564f03f1ded90904e3c90f77e4bd4dc20b15" + integrity sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg== dependencies: "@types/hoist-non-react-statics" "^3.3.0" "@types/react" "*" @@ -3701,9 +3968,9 @@ csstype "^3.0.2" "@types/reactcss@*": - version "1.2.10" - resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.10.tgz#89feb81e913ccf68ff599dbabcc823e1b49c4f7c" - integrity sha512-gf5qJ1wOYP8N5q9H8/5c3QZHQzu8ltPClhM0vEWuBu9SGg4KSzgpJd2TShEsQDwsYn+mtnJ1xHUdJyzj/r9WrA== + version "1.2.11" + resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.11.tgz#bfeadb67a704b4dba24c6c58d3c0e5cf87a0cb1b" + integrity sha512-0fFy0ubuPlhksId8r9V8nsLcxBAPQnn15g/ERAElgE9L6rOquMj2CapsxqfyBuHlkp0/ndEUVnkYI7MkTtkGpw== dependencies: "@types/react" "*" @@ -3872,14 +4139,16 @@ lodash.unescape "4.0.1" semver "5.5.0" -"@vue/compiler-sfc@2.7.15": - version "2.7.15" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-2.7.15.tgz#62135fb2f69559fc723fd9c56b8e8b0ac7864a0b" - integrity sha512-FCvIEevPmgCgqFBH7wD+3B97y7u7oj/Wr69zADBf403Tui377bThTjBvekaZvlRr4IwUAu3M6hYZeULZFJbdYg== +"@vue/compiler-sfc@2.7.16": + version "2.7.16" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz#ff81711a0fac9c68683d8bb00b63f857de77dc83" + integrity sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg== dependencies: - "@babel/parser" "^7.18.4" + "@babel/parser" "^7.23.5" postcss "^8.4.14" source-map "^0.6.1" + optionalDependencies: + prettier "^1.18.2 || ^2.0.0" "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": version "1.11.6" @@ -4384,11 +4653,16 @@ acorn@^7.1.1, acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2: +acorn@^8.2.4: version "8.11.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== +acorn@^8.7.1, acorn@^8.8.2: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + address@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -5000,9 +5274,9 @@ axe-core@^3.3.2: integrity sha512-LEUDjgmdJoA3LqklSTwKYqkjcZ4HKc4ddIYGSAiSkr46NTjzg2L9RNB+lekO9P7Dlpa87+hBtzc2Fzn/+GUWMQ== axe-core@^4.2.0: - version "4.8.2" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae" - integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g== + version "4.8.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.3.tgz#205df863dd9917d5979e9435dab4d47692759051" + integrity sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw== axios@^0.18.0: version "0.18.1" @@ -5516,6 +5790,15 @@ babel-plugin-polyfill-corejs2@^0.4.6: "@babel/helper-define-polyfill-provider" "^0.4.3" semver "^6.3.1" +babel-plugin-polyfill-corejs2@^0.4.7: + version "0.4.8" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz#dbcc3c8ca758a290d47c3c6a490d59429b0d2269" + integrity sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.5.0" + semver "^6.3.1" + babel-plugin-polyfill-corejs3@^0.1.0: version "0.1.7" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz#80449d9d6f2274912e05d9e182b54816904befd0" @@ -5532,6 +5815,14 @@ babel-plugin-polyfill-corejs3@^0.8.5: "@babel/helper-define-polyfill-provider" "^0.4.3" core-js-compat "^3.33.1" +babel-plugin-polyfill-corejs3@^0.8.7: + version "0.8.7" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz#941855aa7fdaac06ed24c730a93450d2b2b76d04" + integrity sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.4" + core-js-compat "^3.33.1" + babel-plugin-polyfill-regenerator@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz#d4c49e4b44614607c13fb769bcd85c72bb26a4a5" @@ -5539,6 +5830,13 @@ babel-plugin-polyfill-regenerator@^0.5.3: dependencies: "@babel/helper-define-polyfill-provider" "^0.4.3" +babel-plugin-polyfill-regenerator@^0.5.4: + version "0.5.5" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz#8b0c8fc6434239e5d7b8a9d1f832bb2b0310f06a" + integrity sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.5.0" + babel-plugin-react-docgen@^4.0.0, babel-plugin-react-docgen@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/babel-plugin-react-docgen/-/babel-plugin-react-docgen-4.2.1.tgz#7cc8e2f94e8dc057a06e953162f0810e4e72257b" @@ -6623,7 +6921,7 @@ browserslist@^3.2.6: caniuse-lite "^1.0.30000844" electron-to-chromium "^1.3.47" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.1, browserslist@^4.16.8, browserslist@^4.21.9, browserslist@^4.22.2, browserslist@^4.3.4, browserslist@^4.3.5: +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.1, browserslist@^4.16.8, browserslist@^4.21.10, browserslist@^4.21.9, browserslist@^4.22.2, browserslist@^4.3.4, browserslist@^4.3.5: version "4.22.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== @@ -6970,11 +7268,16 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001566.tgz#bb66eba4cc64ee7754ac21d96474c537dc08988b" integrity sha512-+Ef8QGfVyMIXCk1EGfkF1YvZBdVgXv7tcl0gSTUqiQ6RFO/5IgFBIGQM9w4xVAe1pr1TXBdooRwlUo++AREwxg== -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000918, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001179, caniuse-lite@^1.0.30001251, caniuse-lite@^1.0.30001565: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000918, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001179, caniuse-lite@^1.0.30001251, caniuse-lite@^1.0.30001565: version "1.0.30001566" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz#61a8e17caf3752e3e426d4239c549ebbb37fef0d" integrity sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA== +caniuse-lite@^1.0.30000989: + version "1.0.30001580" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz#e3c76bc6fe020d9007647044278954ff8cd17d1e" + integrity sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA== + canvg-browser@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/canvg-browser/-/canvg-browser-1.0.0.tgz#c63cb5a9e7a0c70698a9c87783473e60915ea483" @@ -7702,17 +8005,24 @@ copy-to-clipboard@^3.0.8: dependencies: toggle-selection "^1.0.6" -core-js-compat@^3.31.0, core-js-compat@^3.33.1, core-js-compat@^3.8.1: +core-js-compat@^3.31.0, core-js-compat@^3.33.1: version "3.34.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.34.0.tgz#61a4931a13c52f8f08d924522bba65f8c94a5f17" integrity sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA== dependencies: browserslist "^4.22.2" +core-js-compat@^3.8.1: + version "3.35.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.35.1.tgz#215247d7edb9e830efa4218ff719beb2803555e2" + integrity sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw== + dependencies: + browserslist "^4.22.2" + core-js-pure@^3.0.1, core-js-pure@^3.23.3: - version "3.34.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.34.0.tgz#981e462500708664c91b827a75b011f04a8134a0" - integrity sha512-pmhivkYXkymswFfbXsANmBAewXx86UBfmagP+w0wkK06kLsLlTK5oQmsURPivzMkIBQiYq2cjamcZExIwlFQIg== + version "3.35.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.35.1.tgz#f33ad7fdf9dddae260339a30e5f8363f5c49a3bc" + integrity sha512-zcIdi/CL3MWbBJYo5YCeVAAx+Sy9yJE9I3/u9LkFABwbeaPhTMRWraM8mYFp9jW5Z50hOy7FVzCc8dCrpZqtIQ== core-js@2.6.4: version "2.6.4" @@ -7729,7 +8039,12 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.0.1, core-js@^3.0.4, core-js@^3.16.2, core-js@^3.4.1, core-js@^3.4.2, core-js@^3.6.5, core-js@^3.8.2: +core-js@^3.0.1, core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: + version "3.35.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.35.1.tgz#9c28f8b7ccee482796f8590cc8d15739eaaf980c" + integrity sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw== + +core-js@^3.16.2, core-js@^3.4.1, core-js@^3.4.2: version "3.34.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.34.0.tgz#5705e6ad5982678612e96987d05b27c6c7c274a5" integrity sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag== @@ -8259,11 +8574,16 @@ csstype@^2.0.0, csstype@^2.5.2, csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== -csstype@^3.0.2, csstype@^3.1.0: +csstype@^3.0.2: version "3.1.2" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csstype@^3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -8426,7 +8746,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6. dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -9014,11 +9334,16 @@ ejs@^2.7.4: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.30, electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.564, electron-to-chromium@^1.4.601: +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30, electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.564, electron-to-chromium@^1.4.601: version "1.4.605" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.605.tgz#d01f4e342b896d9ca7fae25d322e9ff4f0e41194" integrity sha512-V52j+P5z6cdRqTjPR/bYNxx7ETCHIkm5VIGuyCy3CMrfSnbEpIlLnk5oHmZo7gYvDfh2TfHeanB6rawyQ23ktg== +electron-to-chromium@^1.3.247: + version "1.4.645" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.645.tgz#117f964252eb2f0ff00fc7360cb3080e2cf66e3c" + integrity sha512-EeS1oQDCmnYsRDRy2zTeC336a/4LZ6WKqvSaM1jLocEk5ZuyszkQtCpsqvuvaIXGOUjwtvF6LTcS8WueibXvSw== + element-resize-detector@^1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.2.4.tgz#3e6c5982dd77508b5fa7e6d5c02170e26325c9b1" @@ -15665,7 +15990,7 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== -pirates@^4.0.1, pirates@^4.0.5: +pirates@^4.0.1, pirates@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== @@ -16732,7 +17057,15 @@ postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: +postcss-selector-parser@^6.0.0: + version "6.0.15" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" + integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-selector-parser@^6.0.2: version "6.0.13" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== @@ -16837,7 +17170,7 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2 picocolors "^0.2.1" source-map "^0.6.1" -postcss@^8.2.4, postcss@^8.4.14: +postcss@^8.2.4: version "8.4.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== @@ -16846,6 +17179,15 @@ postcss@^8.2.4, postcss@^8.4.14: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.14: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -16873,6 +17215,11 @@ prettier@^1.17.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +"prettier@^1.18.2 || ^2.0.0": + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + pretty-bytes@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" @@ -17399,9 +17746,9 @@ react-fast-compare@^3.2.0: integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== react-focus-lock@^2.1.0: - version "2.9.6" - resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.9.6.tgz#cad168a150fdd72d5ab2419ba8e62780788011b1" - integrity sha512-B7gYnCjHNrNYwY2juS71dHbf0+UpXXojt02svxybj8N5bxceAkzPChKEncHuratjUHkIFNCn06k2qj1DRlzTug== + version "2.9.7" + resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.9.7.tgz#778358691b55db38a9954a989341048bd4065935" + integrity sha512-EfhX040SELLqnQ9JftqsmQCG49iByg8F5X5m19Er+n371OaETZ35dlNPZrLOOTlnnwD4c2Zv0KDgabDTc7dPHw== dependencies: "@babel/runtime" "^7.0.0" focus-lock "^1.0.0" @@ -18790,9 +19137,9 @@ serialize-javascript@^5.0.1: randombytes "^2.1.0" serialize-javascript@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" @@ -20157,16 +20504,16 @@ terser-webpack-plugin@^4.2.3: terser "^5.3.4" webpack-sources "^1.4.3" -terser-webpack-plugin@^5.3.7: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== +terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== dependencies: - "@jridgewell/trace-mapping" "^0.3.17" + "@jridgewell/trace-mapping" "^0.3.20" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.1" - terser "^5.16.8" + terser "^5.26.0" terser@^3.16.1: version "3.17.0" @@ -20186,10 +20533,10 @@ terser@^4.1.2, terser@^4.6.12, terser@^4.6.3: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^5.16.8, terser@^5.3.4: - version "5.25.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.25.0.tgz#6579b4cca45b08bf0fdaa1a04605fd5860dfb2ac" - integrity sha512-we0I9SIsfvNUMP77zC9HG+MylwYYsGFSBG8qm+13oud2Yh+O104y614FRbyjpxys16jZwot72Fpi827YvGzuqg== +terser@^5.26.0, terser@^5.3.4: + version "5.27.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.27.0.tgz#70108689d9ab25fef61c4e93e808e9fd092bf20c" + integrity sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -21030,9 +21377,9 @@ url@^0.11.0: qs "^6.11.2" use-callback-ref@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz#772199899b9c9a50526fedc4993fc7fa1f7e32d5" - integrity sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w== + version "1.3.1" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.1.tgz#9be64c3902cbd72b07fe55e56408ae3a26036fd0" + integrity sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ== dependencies: tslib "^2.0.0" @@ -21234,11 +21581,11 @@ vm-browserify@^1.0.1: integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== vue@^2.0.0: - version "2.7.15" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.15.tgz#94cd34e6e9f22cd2d35a02143f96a5beac1c1f54" - integrity sha512-a29fsXd2G0KMRqIFTpRgpSbWaNBK3lpCTOLuGLEDnlHWdjB8fwl6zyYZ8xCrqkJdatwZb4mGHiEfJjnw0Q6AwQ== + version "2.7.16" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.16.tgz#98c60de9def99c0e3da8dae59b304ead43b967c9" + integrity sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw== dependencies: - "@vue/compiler-sfc" "2.7.15" + "@vue/compiler-sfc" "2.7.16" csstype "^3.1.0" vuex@^3.1.0: @@ -21442,9 +21789,9 @@ webpack-filter-warnings-plugin@^1.2.1: integrity sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg== webpack-hot-middleware@^2.25.0, webpack-hot-middleware@^2.25.1: - version "2.25.4" - resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.4.tgz#d8bc9e9cb664fc3105c8e83d2b9ed436bee4e193" - integrity sha512-IRmTspuHM06aZh98OhBJtqLpeWFM8FXJS5UYpKYxCJzyFoyWj1w6VGFfomZU7OPA55dMLrQK0pRT1eQ3PACr4w== + version "2.26.0" + resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.26.0.tgz#0a103c9b2836c1f27d7f74bbe0e96c99c82d0265" + integrity sha512-okzjec5sAEy4t+7rzdT8eRyxsk0FDSmBPN2KwX4Qd+6+oQCfe5Ve07+u7cJvofgB+B4w5/4dO4Pz0jhhHyyPLQ== dependencies: ansi-html-community "0.0.8" html-entities "^2.1.0" @@ -21553,18 +21900,18 @@ webpack@4.41.x: webpack-sources "^1.4.1" "webpack@>=4.43.0 <6.0.0": - version "5.89.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc" - integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== + version "5.90.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.90.0.tgz#313bfe16080d8b2fee6e29b6c986c0714ad4290e" + integrity sha512-bdmyXRCXeeNIePv6R6tGPyy20aUobw4Zy8r0LUS2EWO+U+Ke/gYDgsCh7bl5rB6jPpr4r0SZa6dPxBxLooDT3w== dependencies: "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" + "@types/estree" "^1.0.5" "@webassemblyjs/ast" "^1.11.5" "@webassemblyjs/wasm-edit" "^1.11.5" "@webassemblyjs/wasm-parser" "^1.11.5" acorn "^8.7.1" acorn-import-assertions "^1.9.0" - browserslist "^4.14.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" enhanced-resolve "^5.15.0" es-module-lexer "^1.2.1" @@ -21578,7 +21925,7 @@ webpack@4.41.x: neo-async "^2.6.2" schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" + terser-webpack-plugin "^5.3.10" watchpack "^2.4.0" webpack-sources "^3.2.3" @@ -21957,9 +22304,9 @@ ws@^7.4.6: integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== ws@^8.2.3: - version "8.14.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" - integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== + version "8.16.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== x-default-browser@^0.4.0: version "0.4.0" From e3aa0ed6503e08e53c8476440269fedcf4329202 Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Mon, 18 Mar 2024 14:59:29 +0200 Subject: [PATCH 07/18] Show selected year for expenditure column --- .../public-entity-budgets/PublicEntityGroup/index.jsx | 6 +++--- budgetportal/views.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx b/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx index 5cea191f5..8ede20c4c 100644 --- a/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx +++ b/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx @@ -27,7 +27,7 @@ function public_entities(linksArray) { Entity name Relevant department PFMA - Expenditure {} + Expenditure ({linksArray[0].selected_year_slug}) @@ -36,11 +36,11 @@ function public_entities(linksArray) { { name, url_path: url, - functiongroup1, department, department_slug, department_sphere, selected_year_slug, + pfma }, index ) => { @@ -66,7 +66,7 @@ function public_entities(linksArray) { {department} - {functiongroup1} + {pfma}
Date: Mon, 18 Mar 2024 16:43:30 +0200 Subject: [PATCH 08/18] List department public entities --- .../PublicEntityControl/styles.scss | 13 +++++++++++ budgetportal/templates/department.html | 23 +++++++++++++++++++ budgetportal/views.py | 11 +++++++++ 3 files changed, 47 insertions(+) diff --git a/assets/scss/components/public-entity-budgets/PublicEntityControl/styles.scss b/assets/scss/components/public-entity-budgets/PublicEntityControl/styles.scss index 95ab007d9..b1940f89c 100644 --- a/assets/scss/components/public-entity-budgets/PublicEntityControl/styles.scss +++ b/assets/scss/components/public-entity-budgets/PublicEntityControl/styles.scss @@ -32,3 +32,16 @@ padding: 0; } } + +.DepartmentPublicEntity-list { + margin: 0; + padding: 0; + list-style-type: none; + columns: 2; + -webkit-columns: 2; + -moz-columns: 2; + + li { + list-style-type: none; + } +} diff --git a/budgetportal/templates/department.html b/budgetportal/templates/department.html index 75f8f048d..cbc1eaf49 100644 --- a/budgetportal/templates/department.html +++ b/budgetportal/templates/department.html @@ -86,6 +86,29 @@

{% endif %} {% include 'components/department-budgets/IntroSection/index.html' with description=intro datasets=treasury_datasets location=department_location %} + {% if public_entities %} +
+
+
+
+
+

+ New Public Entities associated with this department: +

+
+
+
+
+ +
+ {% endif %} +
The Budget Cycle
diff --git a/budgetportal/views.py b/budgetportal/views.py index f69d064d7..0393ec258 100644 --- a/budgetportal/views.py +++ b/budgetportal/views.py @@ -39,6 +39,7 @@ Video, ShowcaseItem, ) +from .models.government import PublicEntity from .summaries import ( InYearSpending, DepartmentProgrammesEcon4, @@ -506,6 +507,16 @@ def department_page( department, financial_years_context ) + context["public_entities"] = [] + + for public_entity in PublicEntity.objects.filter(department__slug=department_slug): + context["public_entities"].append( + { + "name": public_entity.name, + "url_path": public_entity.get_url_path(), + } + ) + return render(request, "department.html", context) From 6a27a46c8ebc66f373f7010187746d627d2c8601 Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Mon, 18 Mar 2024 23:32:32 +0200 Subject: [PATCH 09/18] Imported public entity expenditures --- .../PublicEntityGroup/index.jsx | 13 +-- .../partials/filterDepartments.js | 1 - .../partials/filterKeywords.js | 1 - .../PublicEntitySearch/scripts.jsx | 85 +++++++++++++------ budgetportal/admin.py | 29 ++++--- .../migrations/0074_auto_20240318_1608.py | 18 ++++ .../migrations/0075_auto_20240318_1608.py | 18 ++++ .../0076_publicentityexpenditure.py | 32 +++++++ .../migrations/0077_publicentity_amount.py | 18 ++++ budgetportal/models/government.py | 37 ++++++-- budgetportal/templates/department.html | 2 +- budgetportal/views.py | 4 +- 12 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 budgetportal/migrations/0074_auto_20240318_1608.py create mode 100644 budgetportal/migrations/0075_auto_20240318_1608.py create mode 100644 budgetportal/migrations/0076_publicentityexpenditure.py create mode 100644 budgetportal/migrations/0077_publicentity_amount.py diff --git a/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx b/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx index 8ede20c4c..a0f797e8b 100644 --- a/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx +++ b/assets/js/components/public-entity-budgets/PublicEntityGroup/index.jsx @@ -20,11 +20,13 @@ function notAvailableMessage() { } function public_entities(linksArray) { + linksArray.sort((a, b) => {return parseInt(b.amount) - parseInt(a.amount)}); + const totalAmount = linksArray.reduce((acc, curr) => acc + parseInt(curr.amount), 0); return ( - + @@ -40,7 +42,8 @@ function public_entities(linksArray) { department_slug, department_sphere, selected_year_slug, - pfma + pfma, + amount, }, index ) => { @@ -72,13 +75,11 @@ function public_entities(linksArray) {
Entity nameEntity name Relevant department PFMA Expenditure ({linksArray[0].selected_year_slug})
+ + + + + + + + + + + + + + + + +
PFMA Schedule:{{ public_entity.pfma }}
Total expenditure ({{ selected_financial_year }}):R{{ public_entity.amount|intcomma }}
Total expenditure as % of {{ department }}:{{ percentage_of_total_department_amount|stringformat:".2f%%" }}
+ Total expenditure as % of all entities: + {{ percentage_of_total_amount|stringformat:".2f%%" }}
+

+
+

{{ department }} expenditure:

+
+

+ {{ department }} +
+ R{{ total_department_amount|intcomma }} +

+ + +

+ {{ name }} - - - {{ department_location }} Public Entity Budget for {{ selected_financial_year }} - - {% if website_url %} - - {{ website_url }} - - {% endif %} - {% include 'components/department-budgets/IntroSection/index.html' with description=intro datasets=treasury_datasets location=department_location %} - -

- The Budget Cycle -
- -
- {% include 'components/department-budgets/ArrowButtons/index.html' with link_1="#section-plan" link_2="#section-implement" link_3="#section-review" %} -
- -
-
- Plan -
-
- -
-

{{ selected_financial_year }} Budget

-

- The {{ source_type_revenue }} ({{ source_type_revenue_short }}) is a book published along with the - tabling of the budget for the new financial year. -

- - {% assign "View the "|add:source_type_revenue_short|add:" chapter for "|add:chapter_name|add:" (PDF)" as text1 %} - {% assign "View tables in the "|add:source_type_revenue_short|add:" chapter (Excel)" as text2 %} - - {% if pdf_link or excel_link %} - - {% endif %} -
- -
- {% include 'scenes/department/ProgrammesSection/index.html' with source_type=source_type year=selected_financial_year pdf=pdf excel=excel guide=guide %} +

- -
- {% include 'scenes/department/EconClassPackedCircles/econ-class-packed-circles.html' with source_type=source_type year=selected_financial_year pdf=pdf excel=excel guide=guide %} -
- -
- {% include 'scenes/department/ProgramEconSmallMultiples/programme-econ-small-muls.html' with source_type=source_type year=selected_financial_year pdf=pdf excel=excel guide=guide %} -
- -

{{ selected_financial_year }} Adjusted Budget

-

- The {{ source_type_adjusted }} ({{ source_type_adjusted_short }}) is a book published along with the - tabling of the adjusted budget. -

- - {% assign "View the "|add:source_type_adjusted_short|add:" chapter for "|add:chapter_name|add:" (PDF)." as text1 %} - {% assign "View tables in the "|add:source_type_adjusted_short|add:" chapter (Excel)." as text2 %} - - {% if pdf_link_adjusted or excel_link_adjusted %} - - {% else %} - - {% if excel_link_adjusted %} -
-
- {% include 'components/Icon/index.html' with type="info" %} - - Please note - -
-
The {{ source_type_adjusted_short }} chapter - for {{ chapter_name }} (PDF) is not available yet -
-
- {% elif pdf_link_adjusted %} -
-
- {% include 'components/Icon/index.html' with type="info" %} - - Please note - -
-
The tables in {{ source_type_revenue_short }} - chapter (Excel) is not available yet -
-
- {% else %} - {% assign "The "|add:source_type_revenue_short|add:" chapter for "|add:chapter_name|add:" (PDF) is not available yet" as info2 %} - {% assign "The tables in "|add:source_type_revenue_short|add:" chapter (Excel)" as info3 %} - -
-
- {% include 'components/Icon/index.html' with type="info" %} - - Please note - -
-
The adjusted budget documents are not - available yet on vulekamali.gov.za -
-
- {% endif %} - {% endif %} - - - {% if government.slug == 'south-africa' %} -
- {% include 'scenes/department/AdjustedSection/index.html' with type="adjusted" items=adjusted_budget_summary source_type=source_type source_type_adjusted=source_type_adjusted year=selected_financial_year pdf=pdf excel=excel pdf_adjusted=pdf_link_adjusted excel_adjusted=excel_link_adjusted csv=adjusted_budget_summary.department_data_csv dataset=adjusted_budget_summary.dataset_detail_page parliament=parliament title="Programme budgets" subtitle=subtitle description="Activities of this department" %} -
- {% endif %} - - {% if procurement_resource_links %} -
- {% include 'scenes/department/ResourceLinks/resource-links.html' with resource_links=procurement_resource_links section_title="Procurement resources" more_link="/datasets/procurement-portals-and-resources" %} -
- {% endif %} - -
-
- Implement -
-
- - {% if infra_enabled %} -
-

Department infrastructure projects

- -

Largest infrastructure projects by this department.

- -
- - - - - - - {% for project in projects %} - - - - - - {% empty %} - - - - {% endfor %} -
Project nameEstimated completion date
- - {{ project.name }} - - {{ project.estimated_completion_date|default:"Not available" }}
No projects available for this department.
-
- - - -
- {% endif %} - - {% if in_year_monitoring_resource_links %} -
- {% include 'scenes/department/ResourceLinks/resource-links.html' with resource_links=in_year_monitoring_resource_links section_title="In-year monitoring resources" %} -
- {% endif %} - - {% if performance_resource_links %} -
- {% include 'scenes/department/ResourceLinks/resource-links.html' with resource_links=performance_resource_links section_title="Performance monitoring resources" %} -
- {% endif %} - - {% if eqprs_data_enabled %} -
- {% endif %} - - {% if not infra_enabled and not in_year_monitoring_resource_links and not performance_resource_links %} -
-
- {% include 'components/Icon/index.html' with type="info" size="l" %} - - Please note - -
-
Implementation data coming soon.
-
- {% endif %} - -
-
- Review -
-
- - {% if expenditure_over_time %} -
- {% include 'scenes/department/ExpenditureSection/index.html' with items=expenditure_over_time.expenditure cpi=global_values.cpi_dataset_url source_type=source_type year=selected_financial_year dataset=expenditure_over_time.dataset_detail_page pdf=pdf_link excel=excel csv=expenditure_over_time.department_data_csv guide=guide color="purple" title="Planned compared to historical expenditure" subtitle=subtitle description="Expenditure changes over time" %} -
- {% endif %} - - {% with ""|add:department_location|add:" "|add:name|add:" Department" as text %} - - {% if budget_actual %} -
- {% include 'scenes/department/ExpenditurePhaseSection/index.html' with items=budget_actual.expenditure cpi=global_values.cpi_dataset_url source_type="Expenditure Time Series" year=selected_financial_year dataset=budget_actual.dataset_detail_page csv=budget_actual.department_data_csv color="purple" description="Budgeted and Actual Expenditure comparison" subtitle=review_subtitle notices=budget_actual.notices website_url=website_url %} -
- {% endif %} - - {% if budget_actual_programmes %} -
- {% include 'scenes/department/ExpenditureMultiplesSection/index.html' with items=budget_actual_programmes.programmes cpi=global_values.cpi_dataset_url source_type="Expenditure Time Series" year=selected_financial_year dataset=budget_actual_programmes.dataset_detail_page csv=budget_actual_programmes.department_data_csv color="purple" subtitle=review_subtitle description="Budgeted and Actual Expenditure comparison by Programme" notices=budget_actual_programmes.notices %} -
- {% endif %} - {% endwith %} -
-
-
- {% include 'components/department-budgets/ContributedData/index.html' with datasets=contributed_datasets %} -
- -
- {% include 'components/universal/Participate/index.html' with title="Timelines for this department and ways to participate" description="National Treasury, departments and commitees are busy with different things depending on the time of year:" %} - - {% if comments_enabled %} -
-

Discuss this budget with others

-
-
-
-
- {% endif %} -
-
-
- - +
+
+
+ +
+

Detailed financial information:

+
+ + + + + + + + + + + + + + + + {% for expenditure in public_entity_expenditure %} + + + + + + + + + + + + {% endfor %} + +
Consol indiClassification 1Classification 2Classification 3Classification 4Classification 5Classification 6Budget phaseAmount
{{ expenditure.consol_indi }}{{ expenditure.economic_classification1|truncatechars:40 }}{{ expenditure.economic_classification2|truncatechars:40 }}{{ expenditure.economic_classification3|truncatechars:40 }}{{ expenditure.economic_classification4|truncatechars:40 }}{{ expenditure.economic_classification5|truncatechars:40 }}{{ expenditure.economic_classification6|truncatechars:40 }}{{ expenditure.budget_phase|truncatechars:40 }}{{ expenditure.amount|intcomma }}
+
{% endblock %} diff --git a/budgetportal/views.py b/budgetportal/views.py index f772a3b3d..f12216e23 100644 --- a/budgetportal/views.py +++ b/budgetportal/views.py @@ -1,3 +1,4 @@ +import simplejson import json import logging from csv import DictWriter @@ -39,7 +40,7 @@ Video, ShowcaseItem, ) -from .models.government import PublicEntity +from .models.government import PublicEntity, PublicEntityExpenditure from .summaries import ( InYearSpending, DepartmentProgrammesEcon4, @@ -509,7 +510,9 @@ def department_page( context["public_entities"] = [] - for public_entity in PublicEntity.objects.filter(department__slug=department_slug, government=department.government): + for public_entity in PublicEntity.objects.filter( + department__slug=department_slug, government=department.government + ): context["public_entities"].append( { "name": public_entity.name, @@ -523,62 +526,79 @@ def department_page( def public_entity_page( request, financial_year_id, sphere_slug, government_slug, public_entity_slug ): - raise() - public_entity = None + # Get public entity by public_entity_slug + selected_public_entity = PublicEntity.objects.filter( + slug=public_entity_slug + ).first() selected_year = get_object_or_404(FinancialYear, slug=financial_year_id) - years = FinancialYear.get_available_years() - for year in years: - if year.slug == financial_year_id: - selected_year = year - sphere = selected_year.spheres.filter(slug=sphere_slug).first() - government = sphere.governments.filter(slug=government_slug).first() - public_entity = government.public_entities.filter( - slug=public_entity_slug - ).first() - - financial_years_context = [] - for year in years: - closest_match, closest_is_exact = year.get_closest_match(public_entity) - financial_years_context.append( - { - "id": year.slug, - "is_selected": year.slug == financial_year_id, - "closest_match": { - "url_path": closest_match.get_url_path(), - "is_exact_match": closest_is_exact, - }, - } + # Total up public entityies amount + total_amount = 0 + for government in ( + selected_year.spheres.filter(slug="national").first().governments.all() + ): + for public_entity in government.public_entities.all(): + total_amount += public_entity.amount + + # Total up public entities in same department + total_department_amount = 0 + department_public_entities = [] + chart_data = [] + for department_public_entity in PublicEntity.objects.filter( + department=selected_public_entity.department + ): + total_department_amount += department_public_entity.amount + department_public_entities.append(department_public_entity) + # if department_public_entity is selected_public_entity then color_group = 2 else 1 + colour_group = 2 if department_public_entity == selected_public_entity else 1 + chart_data.append( + [ + colour_group, + simplejson.dumps(department_public_entity.amount, use_decimal=True), + department_public_entity.name, + simplejson.dumps(department_public_entity.id), + department_public_entity.slug, + ] ) - govt_label = "National" + # Get public entity expenditure + public_entity_expenditure = PublicEntityExpenditure.objects.filter( + public_entity=selected_public_entity + ) + + # Public entity amount percentage of total department amount + percentage_of_total_department_amount = ( + selected_public_entity.amount / total_department_amount + ) * 100 + + # Public entity amount percentage of total amount + percentage_of_total_amount = (selected_public_entity.amount / total_amount) * 100 context = { - "comments_enabled": settings.COMMENTS_ENABLED, - "financial_years": financial_years_context, - "government": { - "name": public_entity.government.name, - "label": govt_label, - "slug": str(public_entity.government.slug), - }, - "intro": public_entity.intro, - "name": public_entity.name, - "slug": str(public_entity.slug), - "sphere": { - "name": public_entity.government.sphere.name, - "slug": public_entity.government.sphere.slug, - }, + "public_entity_id": selected_public_entity.id, + "intro": selected_public_entity.intro, + "name": selected_public_entity.name, + "department": selected_public_entity.department.name, + "department_slug": selected_public_entity.department.slug, + "slug": str(selected_public_entity.slug), "selected_financial_year": financial_year_id, "selected_tab": "public_entities", - "title": "%s budget %s - vulekamali" - % (public_entity.name, selected_year.slug), - "description": "%s public entity: %s budget data for the %s financial year %s" + "title": "%s expenditure %s - vulekamali" + % (selected_public_entity.name, selected_year.slug), + "description": "%s public entity: Expenditure data for the %s financial year %s" % ( - govt_label, - public_entity.name, + selected_public_entity.name, selected_year.slug, COMMON_DESCRIPTION_ENDING, ), + "public_entity": selected_public_entity, + "total_amount": total_amount, + "total_department_amount": total_department_amount, + "percentage_of_total_amount": percentage_of_total_amount, + "percentage_of_total_department_amount": percentage_of_total_department_amount, + "department_public_entities": department_public_entities, + "chart_data": chart_data, + "public_entity_expenditure": public_entity_expenditure, } context["navbar"] = MainMenuItem.objects.prefetch_related("children").all() context["latest_year"] = FinancialYear.get_latest_year().slug @@ -586,7 +606,7 @@ def public_entity_page( str(settings.ROOT_DIR.path("_data/global_values.yaml")) ) context["admin_url"] = reverse( - "admin:budgetportal_department_change", args=(public_entity.pk,) + "admin:budgetportal_department_change", args=(selected_public_entity.pk,) ) return render(request, "public_entity.html", context) @@ -1157,7 +1177,7 @@ def department_list(request, financial_year_id): def latest_public_entity_list(request): - department = request.GET.get('department', None) + department = request.GET.get("department", None) url = reverse("public-entity-list", args=(FinancialYear.get_latest_year().slug,)) url = f"{url}?department={department}" if department else url return redirect(url, permanent=False) diff --git a/import_plublic_entities_expenditure.py b/import_plublic_entities_expenditure.py new file mode 100644 index 000000000..10ac82437 --- /dev/null +++ b/import_plublic_entities_expenditure.py @@ -0,0 +1,186 @@ +import csv +from budgetportal.models import ( + FinancialYear, + Sphere, + Government, + Department, +) + +from budgetportal.models.government import PublicEntity, PublicEntityExpenditure + + +def make_financial_year(year): + # Check if the input is a valid year + try: + year = int(year) + except ValueError: + return "Invalid year format" + + financial_year_str = f"{year - 1}-{str(year)[2:]}" + + return financial_year_str + + +# Open the CSV file +with open("ENT_ENE_CashFlow_202324 - Data.csv", newline="") as csvfile: + # Create a DictReader object with named columns + csvreader = csv.DictReader(csvfile) + + foundGovernments = [] + notFoundFinancialYears = [] + notFoundGovernments = [] + notFoundDepartments = [] + notFoundSpheres = [] + alreadyFoundDepartments = [] + notFoundPublicEntities = [] + + count = 0 + # Loop through each row in the CSV file + for row in csvreader: + if count >= 100000: + break + + # Increment the counter + count += 1 + # Access each column by its name + vote = row["Vote"] + department = row["Department"] + entity_name = row["EntityName"] + consol_indi = row["ConsolIndi"] + pfma = row["PFMA"] + type_ = row["Type"] + economic_classification1 = row["EconomicClassification1"] + economic_classification2 = row["EconomicClassification2"] + economic_classification3 = row["EconomicClassification3"] + economic_classification4 = row["EconomicClassification4"] + economic_classification5 = row["EconomicClassification5"] + economic_classification6 = row["EconomicClassification6"] + function_group1 = row["FunctionGroup1"] + function_group2 = row["FunctionGroup2"] + financial_year = row["FinancialYear"] + budget_phase = row["BudgetPhase"] + amount_r_thou = row["Amount (R-Thou)"] + amount = row["Amount"] + + financial_year_slug = make_financial_year(financial_year) + financialYears = FinancialYear.objects.filter(slug=financial_year_slug) + + if financialYears: + selectedFinancialYear = financialYears.first() + spheres = Sphere.objects.filter( + slug="national", financial_year=selectedFinancialYear + ) + + if spheres: + selectedSphere = spheres.first() + governments = Government.objects.filter(sphere=selectedSphere) + + if governments: + selectedGovernment = governments.first() + foundGovernments.append(selectedGovernment) + + departments = Department.objects.filter( + name=department, government=selectedGovernment + ) + selectedDepartment = None + + if departments: + selectedDepartment = departments.first() + else: + notFoundDepartments.append( + f"Department {department} for financial year {financial_year_slug} does not exist" + ) + try: + selectedDepartment = Department.objects.create( + name=department, + government=selectedGovernment, + vote_number=vote, + ) + except: + alreadyFoundDepartments.append( + f"Department {department} for financial year {financial_year_slug} already exists" + ) + if selectedDepartment: + publicEntities = PublicEntity.objects.filter( + name=entity_name, + department=selectedDepartment, + government=selectedGovernment, + pfma=pfma, + functiongroup1=function_group1, + ) + selectedPublicEntity = None + + if publicEntities: + selectedPublicEntity = publicEntities.first() + else: + selectedPublicEntity = PublicEntity.objects.create( + name=entity_name, + department=selectedDepartment, + government=selectedGovernment, + pfma=pfma, + functiongroup1=function_group1, + ) + + if selectedPublicEntity: + selectedPublicEntityExpenditure = ( + PublicEntityExpenditure.objects.create( + public_entity=selectedPublicEntity, + amount=amount, + budget_phase=budget_phase, + expenditure_type=type_, + economic_classification1=economic_classification1, + economic_classification2=economic_classification2, + economic_classification3=economic_classification3, + economic_classification4=economic_classification4, + economic_classification5=economic_classification5, + economic_classification6=economic_classification6, + consol_indi=consol_indi, + ) + ) + + else: + notFoundPublicEntities.append( + f"Public entity {entity_name} for department {department} for financial year {financial_year_slug} does not exist/not created" + ) + + else: + notFoundGovernments.append( + f"Government for financial year {financial_year_slug} does not exist" + ) + else: + notFoundSpheres.append( + f"National sphere for financial year {financial_year_slug} does not exist" + ) + else: + notFoundFinancialYears.append( + f"Financial year {financial_year_slug} does not exist" + ) + + print("-------------------------------------") + print(foundGovernments) + print("-------------------------------------") + print(notFoundFinancialYears) + print("-------------------------------------") + print(notFoundSpheres) + print("-------------------------------------") + print(notFoundGovernments) + print("-------------------------------------") + print(notFoundDepartments) + print("-------------------------------------") + print(alreadyFoundDepartments) + print("-------------------------------------") + print(notFoundPublicEntities) + + +# Add up publicExpenditure for each publicEntity +for publicEntity in PublicEntity.objects.all(): + totalExpenditure = 0 + for publicEntityExpenditure in PublicEntityExpenditure.objects.filter( + public_entity=publicEntity + ): + totalExpenditure += publicEntityExpenditure.amount + publicEntity.amount = totalExpenditure + publicEntity.save() + print( + f"Public entity {publicEntity.name} has total expenditure of {publicEntity.amount}" + ) From b949c90059c619ff2fdcfc9d7ebb12953230f6a7 Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Thu, 18 Apr 2024 23:38:17 +0200 Subject: [PATCH 14/18] Fix admin --- budgetportal/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/budgetportal/admin.py b/budgetportal/admin.py index 0a7957ef5..565112770 100644 --- a/budgetportal/admin.py +++ b/budgetportal/admin.py @@ -118,7 +118,7 @@ def get_resource_kwargs(self, request, *args, **kwargs): "department", "get_financial_year", ) - list_display_links = "name" + list_display_links = ("name") list_filter = ( "government__sphere__financial_year__slug", "government__sphere__name", From d17fc310a649ee9dca2b154f1d66f8aeab195ea5 Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Thu, 18 Apr 2024 23:55:57 +0200 Subject: [PATCH 15/18] Fix admin --- budgetportal/admin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/budgetportal/admin.py b/budgetportal/admin.py index 565112770..0a3d4c424 100644 --- a/budgetportal/admin.py +++ b/budgetportal/admin.py @@ -118,7 +118,9 @@ def get_resource_kwargs(self, request, *args, **kwargs): "department", "get_financial_year", ) - list_display_links = ("name") + + list_display_links = ("name", "functiongroup1") + list_filter = ( "government__sphere__financial_year__slug", "government__sphere__name", From 4408f500cbd4ce3a17c20a3de539267a84999589 Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Mon, 29 Apr 2024 10:00:14 +0200 Subject: [PATCH 16/18] Do not set default description for Public Entities --- .../migrations/0078_auto_20240418_2212.py | 18 ++++++++++++++++++ budgetportal/models/government.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 budgetportal/migrations/0078_auto_20240418_2212.py diff --git a/budgetportal/migrations/0078_auto_20240418_2212.py b/budgetportal/migrations/0078_auto_20240418_2212.py new file mode 100644 index 000000000..afc035451 --- /dev/null +++ b/budgetportal/migrations/0078_auto_20240418_2212.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.20 on 2024-04-18 22:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('budgetportal', '0077_publicentity_amount'), + ] + + operations = [ + migrations.AlterField( + model_name='publicentity', + name='intro', + field=models.TextField(blank=True, default=''), + ), + ] diff --git a/budgetportal/models/government.py b/budgetportal/models/government.py index 11affc304..b66296658 100644 --- a/budgetportal/models/government.py +++ b/budgetportal/models/government.py @@ -1700,7 +1700,7 @@ class PublicEntity(models.Model): slug = AutoSlugField( populate_from="name", max_length=200, always_update=True, editable=True ) - intro = models.TextField(default="A description of this public entity.") + intro = models.TextField(default="", blank=True) pfma = models.CharField( max_length=10, blank=False, null=False, choices=PFMA_CHOICES From db689290a7980abe8c14f269f09e6cbaab1a6809 Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Mon, 13 May 2024 10:31:43 +0200 Subject: [PATCH 17/18] Public entity descriptions --- import_plublic_entities_expenditure.py | 2 +- import_public_entities_descriptions.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 import_public_entities_descriptions.py diff --git a/import_plublic_entities_expenditure.py b/import_plublic_entities_expenditure.py index 10ac82437..c99d05bf9 100644 --- a/import_plublic_entities_expenditure.py +++ b/import_plublic_entities_expenditure.py @@ -37,7 +37,7 @@ def make_financial_year(year): count = 0 # Loop through each row in the CSV file for row in csvreader: - if count >= 100000: + if count >= 1000000: break # Increment the counter diff --git a/import_public_entities_descriptions.py b/import_public_entities_descriptions.py new file mode 100644 index 000000000..ac6fdca35 --- /dev/null +++ b/import_public_entities_descriptions.py @@ -0,0 +1,26 @@ +import csv +from budgetportal.models.government import PublicEntity + +# Open the CSV file +with open("public_entities_descriptions.csv", newline="") as csvfile: + # Create a DictReader object with named columns + csvreader = csv.DictReader(csvfile) + + updated = [] + not_found = [] + for row in csvreader: + public_entities = PublicEntity.objects.filter(name=row['Entity Name']) + if public_entities: + for entity in public_entities: + entity.description = row['Description'] + entity.save() + print(f"Updated description for {entity.name}") + updated.append(entity.name) + else: + print(f"Could not find public entity {row['Entity Name']}") + not_found.append(row['Entity Name']) + +print(f"Updated {len(updated)} public entities") +print(f"Could not find {len(not_found)} public entities") +print("Not found:") +print(not_found) From 443a4a5cec59228020307c2ed11b649e2556b036 Mon Sep 17 00:00:00 2001 From: paulmwatson Date: Mon, 13 May 2024 10:48:38 +0200 Subject: [PATCH 18/18] Show public entity expenditure amounts in thousands --- budgetportal/templates/public_entity.html | 8 +++++++- budgetportal/templatetags/custom_filters.py | 11 +++++++++++ import_plublic_entities_expenditure.py | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 budgetportal/templatetags/custom_filters.py diff --git a/budgetportal/templates/public_entity.html b/budgetportal/templates/public_entity.html index ae43145e7..8975da77b 100644 --- a/budgetportal/templates/public_entity.html +++ b/budgetportal/templates/public_entity.html @@ -1,6 +1,7 @@ {% extends 'page-shell.html' %} {% load humanize %} {% load define_action %} +{% load custom_filters %} {% block page_content %}
@@ -68,6 +69,11 @@

Detailed financial information:

+ + + @@ -91,7 +97,7 @@

Detailed financial information:

- + {% endfor %} diff --git a/budgetportal/templatetags/custom_filters.py b/budgetportal/templatetags/custom_filters.py new file mode 100644 index 000000000..d614238c9 --- /dev/null +++ b/budgetportal/templatetags/custom_filters.py @@ -0,0 +1,11 @@ +from django import template + +register = template.Library() + + +@register.filter +def divide(value, arg): + try: + return int(value) / int(arg) + except (ValueError, ZeroDivisionError): + return None diff --git a/import_plublic_entities_expenditure.py b/import_plublic_entities_expenditure.py index c99d05bf9..187f270a8 100644 --- a/import_plublic_entities_expenditure.py +++ b/import_plublic_entities_expenditure.py @@ -22,7 +22,7 @@ def make_financial_year(year): # Open the CSV file -with open("ENT_ENE_CashFlow_202324 - Data.csv", newline="") as csvfile: +with open("ENT_ENE_CashFlow_202324 - Data.csv", newline="") as csvfile: # Create a DictReader object with named columns csvreader = csv.DictReader(csvfile) @@ -37,7 +37,7 @@ def make_financial_year(year): count = 0 # Loop through each row in the CSV file for row in csvreader: - if count >= 1000000: + if count >= 10: break # Increment the counter
+ All Financial Values: R'000 +
Consol indi Classification 1 {{ expenditure.economic_classification5|truncatechars:40 }} {{ expenditure.economic_classification6|truncatechars:40 }} {{ expenditure.budget_phase|truncatechars:40 }}{{ expenditure.amount|intcomma }}{{ expenditure.amount|divide:1000|floatformat:"0"|intcomma }}