feat: add scenarios list page with metrics and quick actions
Some checks failed
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / lint (push) Failing after 15s
CI / deploy (push) Has been skipped

- Introduced a new template for listing scenarios associated with a project.
- Added metrics for total, active, draft, and archived scenarios.
- Implemented quick actions for creating new scenarios and reviewing project overview.
- Enhanced navigation with breadcrumbs for better user experience.

refactor: update Opex and Profitability templates for consistency

- Changed titles and button labels for clarity in Opex and Profitability templates.
- Updated form IDs and action URLs for better alignment with new naming conventions.
- Improved navigation links to include scenario and project overviews.

test: add integration tests for Opex calculations

- Created new tests for Opex calculation HTML and JSON flows.
- Validated successful calculations and ensured correct data persistence.
- Implemented tests for currency mismatch and unsupported frequency scenarios.

test: enhance project and scenario route tests

- Added tests to verify scenario list rendering and calculator shortcuts.
- Ensured scenario detail pages link back to the portfolio correctly.
- Validated project detail pages show associated scenarios accurately.
This commit is contained in:
2025-11-13 16:21:36 +01:00
parent 4f00bf0d3c
commit 522b1e4105
54 changed files with 3419 additions and 700 deletions

View File

@@ -6,9 +6,5 @@
<span class="brand-subtitle">Mining Planner</span>
</div>
</a>
<div class="sidebar-nav-controls">
<button id="nav-prev" class="nav-chevron nav-chevron-prev" aria-label="Previous page">&larr;</button>
<button id="nav-next" class="nav-chevron nav-chevron-next" aria-label="Next page">&rarr;</button>
</div>
{% include "partials/sidebar_nav.html" %}
</div>

View File

@@ -1,67 +1,78 @@
{% set dashboard_href = request.url_for('dashboard.home') if request else '/' %}
{% set projects_href = request.url_for('projects.project_list_page') if request
else '/projects/ui' %} {% set project_create_href =
request.url_for('projects.create_project_form') if request else
'/projects/create' %} {% set auth_session = request.state.auth_session if
request else None %} {% set is_authenticated = auth_session and
auth_session.is_authenticated %} {% if is_authenticated %} {% set logout_href =
request.url_for('auth.logout') if request else '/logout' %} {% set account_links
= [ {"href": logout_href, "label": "Logout", "match_prefix": "/logout"} ] %} {%
else %} {% set login_href = request.url_for('auth.login_form') if request else
'/login' %} {% set register_href = request.url_for('auth.register_form') if
request else '/register' %} {% set forgot_href =
request.url_for('auth.password_reset_request_form') if request else
'/forgot-password' %} {% set account_links = [ {"href": login_href, "label":
"Login", "match_prefix": "/login"}, {"href": register_href, "label": "Register",
"match_prefix": "/register"}, {"href": forgot_href, "label": "Forgot Password",
"match_prefix": "/forgot-password"} ] %} {% endif %} {% set nav_groups = [ {
"label": "Workspace", "links": [ {"href": dashboard_href, "label": "Dashboard",
"match_prefix": "/"}, {"href": projects_href, "label": "Projects",
"match_prefix": "/projects"}, {"href": project_create_href, "label": "New
Project", "match_prefix": "/projects/create"}, {"href": "/imports/ui", "label":
"Imports", "match_prefix": "/imports"}, {"href": request.url_for('calculations.profitability_form') if request else '/calculations/profitability', "label": "Profitability Calculator", "match_prefix": "/calculations/profitability"}, {"href": request.url_for('calculations.processing_opex_form') if request else '/calculations/processing-opex', "label": "Processing Opex Planner", "match_prefix": "/calculations/processing-opex"}, {"href": request.url_for('calculations.capex_form') if request else '/calculations/capex', "label": "Initial Capex Planner", "match_prefix": "/calculations/capex"} ] }, { "label": "Insights", "links": [
{"href": "/ui/simulations", "label": "Simulations"}, {"href": "/ui/reporting",
"label": "Reporting"} ] }, { "label": "Configuration", "links": [ { "href":
"/ui/settings", "label": "Settings", "children": [ {"href": "/theme-settings",
"label": "Themes"}, {"href": "/ui/currencies", "label": "Currency Management"} ]
} ] }, { "label": "Account", "links": account_links } ] %}
{% set sidebar_nav = get_sidebar_navigation(request) %}
{% set nav_groups = sidebar_nav.groups if sidebar_nav else [] %}
{% set current_path = request.url.path if request else '' %}
<nav class="sidebar-nav" aria-label="Primary navigation">
{% set current_path = request.url.path if request else '' %} {% for group in
nav_groups %} {% if group.links %}
<div class="sidebar-section">
<div class="sidebar-section-label">{{ group.label }}</div>
<div class="sidebar-section-links">
{% for link in group.links %} {% set href = link.href | string %} {% set
match_prefix = link.get('match_prefix', href) | string %} {% if
match_prefix == '/' %} {% set is_active = current_path == '/' %} {% else
%} {% set is_active = current_path.startswith(match_prefix) %} {% endif %}
<div class="sidebar-link-block">
<a
href="{{ href }}"
class="sidebar-link{% if is_active %} is-active{% endif %}"
>
{{ link.label }}
</a>
{% if link.children %}
<div class="sidebar-sublinks">
{% for child in link.children %} {% set child_prefix =
child.get('match_prefix', child.href) | string %} {% if child_prefix
== '/' %} {% set child_active = current_path == '/' %} {% else %} {%
set child_active = current_path.startswith(child_prefix) %} {% endif
%}
<a
href="{{ child.href | string }}"
class="sidebar-sublink{% if child_active %} is-active{% endif %}"
>
{{ child.label }}
</a>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
<nav
class="sidebar-nav"
aria-label="Primary navigation"
data-navigation-source="{{ 'server' if sidebar_nav else 'fallback' }}"
>
<div class="sidebar-nav-controls">
<button id="nav-prev" class="nav-chevron nav-chevron-prev" aria-label="Previous page"></button>
<button id="nav-next" class="nav-chevron nav-chevron-next" aria-label="Next page"></button>
</div>
{% endif %} {% endfor %}
{% if nav_groups %}
{% for group in nav_groups %}
{% if group.links %}
<div class="sidebar-section" data-group-id="{{ group.id }}">
<div class="sidebar-section-label">{{ group.label }}</div>
<div class="sidebar-section-links">
{% for link in group.links %}
{% set href = link.href %}
{% if href %}
{% set match_prefix = link.match_prefix or href %}
{% if match_prefix == '/' %}
{% set is_active = current_path == '/' %}
{% else %}
{% set is_active = current_path.startswith(match_prefix) %}
{% endif %}
<div class="sidebar-link-block" data-link-id="{{ link.id }}">
<a
href="{{ href }}"
class="sidebar-link{% if is_active %} is-active{% endif %}{% if link.is_external %} is-external{% endif %}"
data-match-prefix="{{ match_prefix }}"
{% if link.tooltip %}title="{{ link.tooltip }}"{% endif %}
{% if link.is_external %}target="_blank" rel="noopener noreferrer"{% endif %}
>
{{ link.label }}
</a>
{% if link.children %}
<div class="sidebar-sublinks">
{% for child in link.children %}
{% set child_href = child.href %}
{% if child_href %}
{% set child_prefix = child.match_prefix or child_href %}
{% if child_prefix == '/' %}
{% set child_active = current_path == '/' %}
{% else %}
{% set child_active = current_path.startswith(child_prefix) %}
{% endif %}
<a
href="{{ child_href }}"
class="sidebar-sublink{% if child_active %} is-active{% endif %}{% if child.is_external %} is-external{% endif %}"
data-match-prefix="{{ child_prefix }}"
{% if child.tooltip %}title="{{ child.tooltip }}"{% endif %}
{% if child.is_external %}target="_blank" rel="noopener noreferrer"{% endif %}
>
{{ child.label }}
</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
{% else %}
<div class="sidebar-section sidebar-empty-state">
<div class="sidebar-section-label">Navigation</div>
<div class="sidebar-section-links">
<p class="sidebar-empty-copy">Navigation is unavailable.</p>
</div>
</div>
{% endif %}
</nav>