feat: enhance project and scenario detail pages with metrics, improved layouts, and updated styles

This commit is contained in:
2025-11-09 19:15:48 +01:00
parent 7f5ed6a42d
commit 400f85c907
9 changed files with 419 additions and 213 deletions

View File

@@ -12,3 +12,4 @@
- Added scenario comparison validator, FastAPI comparison endpoint, and comprehensive unit tests to enforce FR-009 validation rules through API errors. - Added scenario comparison validator, FastAPI comparison endpoint, and comprehensive unit tests to enforce FR-009 validation rules through API errors.
- Delivered a new dashboard experience with `templates/dashboard.html`, dedicated styling, and a FastAPI route supplying real project/scenario metrics via repository helpers. - Delivered a new dashboard experience with `templates/dashboard.html`, dedicated styling, and a FastAPI route supplying real project/scenario metrics via repository helpers.
- Extended repositories with count/recency utilities and added pytest coverage, including a dashboard rendering smoke test validating empty-state messaging. - Extended repositories with count/recency utilities and added pytest coverage, including a dashboard rendering smoke test validating empty-state messaging.
- Brought project and scenario detail pages plus their forms in line with the dashboard visuals, adding metric cards, layout grids, and refreshed CTA styles.

View File

@@ -7,7 +7,7 @@ from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from dependencies import get_unit_of_work from dependencies import get_unit_of_work
from models import MiningOperationType, Project from models import MiningOperationType, Project, ScenarioStatus
from schemas.project import ProjectCreate, ProjectRead, ProjectUpdate from schemas.project import ProjectCreate, ProjectRead, ProjectUpdate
from services.exceptions import EntityConflictError, EntityNotFoundError from services.exceptions import EntityConflictError, EntityNotFoundError
from services.unit_of_work import UnitOfWork from services.unit_of_work import UnitOfWork
@@ -205,12 +205,23 @@ def view_project(
) from exc ) from exc
scenarios = sorted(project.scenarios, key=lambda s: s.created_at) scenarios = sorted(project.scenarios, key=lambda s: s.created_at)
scenario_stats = {
"total": len(scenarios),
"active": sum(1 for scenario in scenarios if scenario.status == ScenarioStatus.ACTIVE),
"draft": sum(1 for scenario in scenarios if scenario.status == ScenarioStatus.DRAFT),
"archived": sum(1 for scenario in scenarios if scenario.status == ScenarioStatus.ARCHIVED),
"latest_update": max(
(scenario.updated_at for scenario in scenarios if scenario.updated_at),
default=None,
),
}
return templates.TemplateResponse( return templates.TemplateResponse(
"projects/detail.html", "projects/detail.html",
{ {
"request": request, "request": request,
"project": project, "project": project,
"scenarios": scenarios, "scenarios": scenarios,
"scenario_stats": scenario_stats,
}, },
) )

View File

@@ -339,12 +339,20 @@ def view_scenario(
scenario.simulation_parameters, key=lambda item: item.created_at scenario.simulation_parameters, key=lambda item: item.created_at
) )
scenario_metrics = {
"financial_count": len(financial_inputs),
"parameter_count": len(simulation_parameters),
"currency": scenario.currency,
"primary_resource": scenario.primary_resource.value.replace('_', ' ').title() if scenario.primary_resource else None,
}
return templates.TemplateResponse( return templates.TemplateResponse(
"scenarios/detail.html", "scenarios/detail.html",
{ {
"request": request, "request": request,
"project": project, "project": project,
"scenario": scenario, "scenario": scenario,
"scenario_metrics": scenario_metrics,
"financial_inputs": financial_inputs, "financial_inputs": financial_inputs,
"simulation_parameters": simulation_parameters, "simulation_parameters": simulation_parameters,
}, },

View File

@@ -4,23 +4,59 @@
--hover-highlight: rgba(241, 178, 26, 0.12); --hover-highlight: rgba(241, 178, 26, 0.12);
} }
.projects-table { .header-actions {
width: 100%; display: flex;
border-collapse: collapse; gap: 0.75rem;
border-radius: var(--table-radius); flex-wrap: wrap;
overflow: hidden; justify-content: flex-end;
box-shadow: var(--shadow);
} }
.projects-table th, .project-metrics {
.projects-table td { display: grid;
padding: 0.875rem 1rem; gap: 1.5rem;
border-bottom: 1px solid var(--card-border); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
margin-bottom: 2rem;
}
.metric-card {
background: var(--card-bg); background: var(--card-bg);
border-radius: var(--radius);
padding: 1.5rem;
box-shadow: var(--shadow);
border: 1px solid var(--card-border);
display: flex;
flex-direction: column;
gap: 0.35rem;
} }
.projects-table tbody tr:hover { .metric-card h2 {
background: var(--hover-highlight); margin: 0;
font-size: 1rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.metric-value {
font-size: 2rem;
font-weight: 700;
margin: 0;
}
.metric-caption {
color: var(--color-text-subtle);
font-size: 0.85rem;
}
.project-form {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 1.75rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
} }
.definition-list { .definition-list {
@@ -62,6 +98,61 @@
margin: 0; margin: 0;
} }
.project-layout {
display: grid;
gap: 1.5rem;
}
.table-responsive {
overflow-x: auto;
border-radius: var(--table-radius);
}
.table {
width: 100%;
border-collapse: collapse;
border-radius: var(--table-radius);
overflow: hidden;
box-shadow: var(--shadow);
}
.table th,
.table td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--card-border);
background: rgba(21, 27, 35, 0.85);
}
.table tbody tr:hover {
background: var(--hover-highlight);
}
.table-link {
color: var(--brand-2);
text-decoration: none;
margin-left: 0.5rem;
}
.table-link:hover,
.table-link:focus {
text-decoration: underline;
}
.text-right {
text-align: right;
}
@media (min-width: 960px) {
.project-layout {
grid-template-columns: 1.1fr 1.9fr;
align-items: start;
}
.header-actions {
justify-content: flex-start;
}
}
.alert { .alert {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);

View File

@@ -37,44 +37,48 @@
text-decoration: none; text-decoration: none;
} }
.actions { .header-actions {
display: flex; display: flex;
gap: 0.75rem; gap: 0.75rem;
flex-wrap: wrap;
justify-content: flex-end;
} }
.btn { .scenario-metrics {
display: inline-flex; display: grid;
align-items: center; gap: 1.5rem;
justify-content: center; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
padding: 0.6rem 1.1rem; margin-bottom: 2rem;
border-radius: var(--radius-sm);
text-decoration: none;
font-weight: 600;
transition: transform 0.2s ease, box-shadow 0.2s ease;
} }
.btn-primary { .metric-card {
background: linear-gradient(90deg, var(--brand) 0%, var(--brand-2) 100%); background: rgba(21, 27, 35, 0.85);
color: var(--color-text-dark); border-radius: var(--radius);
box-shadow: 0 8px 18px rgba(241, 178, 26, 0.25); padding: 1.5rem;
box-shadow: var(--shadow);
border: 1px solid var(--color-border);
display: flex;
flex-direction: column;
gap: 0.35rem;
} }
.btn-secondary { .metric-card h2 {
background: rgba(148, 197, 255, 0.2); margin: 0;
color: var(--color-text-invert); font-size: 1rem;
border: 1px solid rgba(148, 197, 255, 0.35); color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.08em;
} }
.btn-link { .metric-value {
padding: 0.35rem 0.5rem; font-size: 2rem;
color: var(--brand-3); font-weight: 700;
text-decoration: none; margin: 0;
} }
.btn:hover, .metric-caption {
.btn:focus { color: var(--color-text-subtle);
transform: translateY(-1px); font-size: 0.85rem;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.25);
} }
.scenario-filters { .scenario-filters {
@@ -106,6 +110,17 @@
color: var(--text); color: var(--text);
} }
.scenario-form {
background: rgba(21, 27, 35, 0.85);
border: 1px solid var(--color-border);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 1.75rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.table-responsive { .table-responsive {
width: 100%; width: 100%;
overflow-x: auto; overflow-x: auto;
@@ -159,3 +174,24 @@
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
} }
} }
.scenario-layout {
display: grid;
gap: 1.5rem;
}
.empty-state {
color: var(--muted);
font-style: italic;
}
@media (min-width: 960px) {
.header-actions {
justify-content: flex-start;
}
.scenario-layout {
grid-template-columns: 1.1fr 1.9fr;
align-items: start;
}
}

View File

@@ -16,67 +16,98 @@
<h1>{{ project.name }}</h1> <h1>{{ project.name }}</h1>
<p class="text-muted">{{ project.operation_type.value.replace('_', ' ') | title }}</p> <p class="text-muted">{{ project.operation_type.value.replace('_', ' ') | title }}</p>
</div> </div>
<div class="actions"> <div class="header-actions">
<a class="btn btn-secondary" href="{{ url_for('projects.edit_project_form', project_id=project.id) }}">Edit</a> <a class="btn" href="{{ url_for('projects.edit_project_form', project_id=project.id) }}">Edit Project</a>
<a class="btn btn-primary" href="{{ url_for('scenarios.create_scenario_form', project_id=project.id) }}">New Scenario</a> <a class="btn primary" href="{{ url_for('scenarios.create_scenario_form', project_id=project.id) }}">New Scenario</a>
</div> </div>
</header> </header>
<section class="card"> <section class="project-metrics">
<h2>Project Overview</h2> <article class="metric-card">
<dl class="definition-list"> <h2>Total Scenarios</h2>
<div> <p class="metric-value">{{ scenario_stats.total }}</p>
<dt>Location</dt> <span class="metric-caption">Across this project</span>
<dd>{{ project.location or '—' }}</dd> </article>
</div> <article class="metric-card">
<div> <h2>Active</h2>
<dt>Description</dt> <p class="metric-value">{{ scenario_stats.active }}</p>
<dd>{{ project.description or 'No description provided.' }}</dd> <span class="metric-caption">Currently live analyses</span>
</div> </article>
<div> <article class="metric-card">
<dt>Created</dt> <h2>Draft</h2>
<dd>{{ project.created_at.strftime('%Y-%m-%d %H:%M') }}</dd> <p class="metric-value">{{ scenario_stats.draft }}</p>
</div> <span class="metric-caption">Awaiting validation</span>
<div> </article>
<dt>Updated</dt> <article class="metric-card">
<dd>{{ project.updated_at.strftime('%Y-%m-%d %H:%M') }}</dd> <h2>Archived</h2>
</div> <p class="metric-value">{{ scenario_stats.archived }}</p>
</dl> <span class="metric-caption">Historical references</span>
</article>
</section> </section>
<section class="card"> <div class="project-layout">
<header class="card-header"> <section class="card">
<h2>Scenarios</h2> <h2>Project Overview</h2>
<a class="btn btn-link" href="{{ url_for('scenarios.create_scenario_form', project_id=project.id) }}">Add Scenario</a> <dl class="definition-list">
</header> <div>
{% if scenarios %} <dt>Location</dt>
<table class="table"> <dd>{{ project.location or '—' }}</dd>
<thead> </div>
<tr> <div>
<th>Name</th> <dt>Description</dt>
<th>Status</th> <dd>{{ project.description or 'No description provided.' }}</dd>
<th>Currency</th> </div>
<th>Primary Resource</th> <div>
<th></th> <dt>Created</dt>
</tr> <dd>{{ project.created_at.strftime('%Y-%m-%d %H:%M') }}</dd>
</thead> </div>
<tbody> <div>
{% for scenario in scenarios %} <dt>Updated</dt>
<tr> <dd>{{ project.updated_at.strftime('%Y-%m-%d %H:%M') }}</dd>
<td>{{ scenario.name }}</td> </div>
<td>{{ scenario.status.value.title() }}</td> <div>
<td>{{ scenario.currency or '—' }}</td> <dt>Latest Scenario Update</dt>
<td>{{ scenario.primary_resource.value.replace('_', ' ') | title if scenario.primary_resource else '—' }}</td> <dd>{{ scenario_stats.latest_update.strftime('%Y-%m-%d %H:%M') if scenario_stats.latest_update else '—' }}</dd>
<td class="text-right"> </div>
<a class="btn btn-link" href="{{ url_for('scenarios.view_scenario', scenario_id=scenario.id) }}">View</a> </dl>
<a class="btn btn-link" href="{{ url_for('scenarios.edit_scenario_form', scenario_id=scenario.id) }}">Edit</a> </section>
</td>
</tr> <section class="card">
{% endfor %} <header class="card-header">
</tbody> <h2>Scenarios</h2>
</table> <a class="btn" href="{{ url_for('scenarios.create_scenario_form', project_id=project.id) }}">Add Scenario</a>
{% else %} </header>
<p>No scenarios yet.</p> {% if scenarios %}
{% endif %} <div class="table-responsive">
</section> <table class="table">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th>Currency</th>
<th>Primary Resource</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
{% for scenario in scenarios %}
<tr>
<td>{{ scenario.name }}</td>
<td>{{ scenario.status.value.title() }}</td>
<td>{{ scenario.currency or '—' }}</td>
<td>{{ scenario.primary_resource.value.replace('_', ' ') | title if scenario.primary_resource else '—' }}</td>
<td class="text-right">
<a class="table-link" href="{{ url_for('scenarios.view_scenario', scenario_id=scenario.id) }}">View</a>
<a class="table-link" href="{{ url_for('scenarios.edit_scenario_form', scenario_id=scenario.id) }}">Edit</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No scenarios yet. <a href="{{ url_for('scenarios.create_scenario_form', project_id=project.id) }}">Create the first scenario.</a></p>
{% endif %}
</section>
</div>
{% endblock %} {% endblock %}

View File

@@ -21,40 +21,50 @@
<h1>{% if project %}Edit Project{% else %}Create Project{% endif %}</h1> <h1>{% if project %}Edit Project{% else %}Create Project{% endif %}</h1>
<p class="text-muted">Provide core information about the mining project.</p> <p class="text-muted">Provide core information about the mining project.</p>
</div> </div>
<div class="header-actions">
<a class="btn" href="{{ cancel_url }}">Cancel</a>
<button class="btn primary" type="submit">Save Project</button>
</div>
</header> </header>
{% if error %} {% if error %}
<div class="alert alert-error">{{ error }}</div> <div class="alert alert-error">{{ error }}</div>
{% endif %} {% endif %}
<form class="form" method="post" action="{{ form_action }}"> {% if error %}
<div class="form-group"> <div class="alert alert-error">{{ error }}</div>
<label for="name">Name</label> {% endif %}
<input id="name" name="name" type="text" required value="{{ project.name if project else '' }}" />
</div>
<div class="form-group"> <form class="form project-form" method="post" action="{{ form_action }}">
<label for="location">Location</label> <div class="form-grid">
<input id="location" name="location" type="text" value="{{ project.location if project else '' }}" /> <div class="form-group">
</div> <label for="name">Name</label>
<input id="name" name="name" type="text" required value="{{ project.name if project else '' }}" />
</div>
<div class="form-group"> <div class="form-group">
<label for="operation_type">Operation Type</label> <label for="location">Location</label>
<select id="operation_type" name="operation_type" required> <input id="location" name="location" type="text" value="{{ project.location if project else '' }}" />
{% for value, label in operation_types %} </div>
<option value="{{ value }}" {% if project and project.operation_type.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %} <div class="form-group">
</select> <label for="operation_type">Operation Type</label>
<select id="operation_type" name="operation_type" required>
{% for value, label in operation_types %}
<option value="{{ value }}" {% if project and project.operation_type.value == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">Description</label> <label for="description">Description</label>
<textarea id="description" name="description" rows="4">{{ project.description if project else '' }}</textarea> <textarea id="description" name="description" rows="5">{{ project.description if project else '' }}</textarea>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<a class="btn btn-secondary" href="{{ cancel_url }}">Cancel</a> <a class="btn" href="{{ cancel_url }}">Cancel</a>
<button class="btn btn-primary" type="submit">Save</button> <button class="btn primary" type="submit">Save Project</button>
</div> </div>
</form> </form>
{% endblock %} {% endblock %}

View File

@@ -17,104 +17,118 @@
<h1>{{ scenario.name }}</h1> <h1>{{ scenario.name }}</h1>
<p class="text-muted">Status: {{ scenario.status.value.title() }}</p> <p class="text-muted">Status: {{ scenario.status.value.title() }}</p>
</div> </div>
<a class="btn btn-secondary" href="{{ url_for('scenarios.edit_scenario_form', scenario_id=scenario.id) }}">Edit</a> <div class="header-actions">
<a class="btn" href="{{ url_for('projects.view_project', project_id=project.id) }}">Back to Project</a>
<a class="btn primary" href="{{ url_for('scenarios.edit_scenario_form', scenario_id=scenario.id) }}">Edit Scenario</a>
</div>
</header> </header>
<section class="card"> <section class="scenario-metrics">
<h2>Scenario Details</h2> <article class="metric-card">
<dl class="definition-list"> <h2>Financial Inputs</h2>
<div> <p class="metric-value">{{ scenario_metrics.financial_count }}</p>
<dt>Description</dt> <span class="metric-caption">Line items captured</span>
<dd>{{ scenario.description or 'No description provided.' }}</dd> </article>
</div> <article class="metric-card">
<div> <h2>Simulation Parameters</h2>
<dt>Timeline</dt> <p class="metric-value">{{ scenario_metrics.parameter_count }}</p>
<dd> <span class="metric-caption">Inputs driving forecasts</span>
{% if scenario.start_date %} </article>
{{ scenario.start_date }} <article class="metric-card">
{% else %} <h2>Currency</h2>
<p class="metric-value">{{ scenario_metrics.currency or '—' }}</p>
{% endif %} <span class="metric-caption">Financial reporting</span>
</article>
{% if scenario.end_date %} <article class="metric-card">
{{ scenario.end_date }} <h2>Primary Resource</h2>
{% else %} <p class="metric-value">{{ scenario_metrics.primary_resource or '—' }}</p>
<span class="metric-caption">Scenario focus</span>
{% endif %} </article>
</dd>
</div>
<div>
<dt>Discount Rate</dt>
<dd>{{ scenario.discount_rate or '—' }}</dd>
</div>
<div>
<dt>Currency</dt>
<dd>{{ scenario.currency or '—' }}</dd>
</div>
<div>
<dt>Primary Resource</dt>
<dd>{{ scenario.primary_resource.value.replace('_', ' ') | title if scenario.primary_resource else '—' }}</dd>
</div>
</dl>
</section> </section>
<section class="card"> <div class="scenario-layout">
<h2>Financial Inputs</h2> <section class="card">
{% if financial_inputs %} <h2>Scenario Details</h2>
<div class="table-responsive"> <dl class="definition-list">
<table class="table"> <div>
<thead> <dt>Description</dt>
<tr> <dd>{{ scenario.description or 'No description provided.' }}</dd>
<th>Name</th> </div>
<th>Category</th> <div>
<th>Amount</th> <dt>Timeline</dt>
<th>Currency</th> <dd>
</tr> {{ scenario.start_date or '—' }} → {{ scenario.end_date or '—' }}
</thead> </dd>
<tbody> </div>
{% for item in financial_inputs %} <div>
<tr> <dt>Discount Rate</dt>
<td>{{ item.name }}</td> <dd>{{ scenario.discount_rate or '—' }}</dd>
<td>{{ item.category.value.title() }}</td> </div>
<td>{{ '{:,.2f}'.format(item.amount) }}</td> <div>
<td>{{ item.currency or '—' }}</td> <dt>Last Updated</dt>
</tr> <dd>{{ scenario.updated_at.strftime('%Y-%m-%d %H:%M') if scenario.updated_at else '—' }}</dd>
{% endfor %} </div>
</tbody> </dl>
</table> </section>
</div>
{% else %}
<p>No financial inputs recorded.</p>
{% endif %}
</section>
<section class="card"> <section class="card">
<h2>Simulation Parameters</h2> <h2>Financial Inputs</h2>
{% if simulation_parameters %} {% if financial_inputs %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table"> <table class="table">
<thead> <thead>
<tr>
<th>Name</th>
<th>Distribution</th>
<th>Variable</th>
<th>Resource</th>
</tr>
</thead>
<tbody>
{% for param in simulation_parameters %}
<tr> <tr>
<td>{{ param.name }}</td> <th>Name</th>
<td>{{ param.distribution.value.title() }}</td> <th>Category</th>
<td>{{ param.variable.value.replace('_', ' ') | title if param.variable else '—' }}</td> <th>Amount</th>
<td>{{ param.resource_type.value.replace('_', ' ') | title if param.resource_type else '—' }}</td> <th>Currency</th>
</tr> </tr>
{% endfor %} </thead>
</tbody> <tbody>
</table> {% for item in financial_inputs %}
</div> <tr>
{% else %} <td>{{ item.name }}</td>
<p>No simulation parameters defined.</p> <td>{{ item.category.value.title() }}</td>
{% endif %} <td>{{ '{:,.2f}'.format(item.amount) }}</td>
</section> <td>{{ item.currency or '—' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No financial inputs recorded yet.</p>
{% endif %}
</section>
<section class="card">
<h2>Simulation Parameters</h2>
{% if simulation_parameters %}
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Distribution</th>
<th>Variable</th>
<th>Resource</th>
</tr>
</thead>
<tbody>
{% for param in simulation_parameters %}
<tr>
<td>{{ param.name }}</td>
<td>{{ param.distribution.value.title() }}</td>
<td>{{ param.variable.value.replace('_', ' ') | title if param.variable else '—' }}</td>
<td>{{ param.resource_type.value.replace('_', ' ') | title if param.resource_type else '—' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No simulation parameters defined.</p>
{% endif %}
</section>
</div>
{% endblock %} {% endblock %}

View File

@@ -21,13 +21,17 @@
<h1>{% if scenario %}Edit Scenario{% else %}Create Scenario{% endif %}</h1> <h1>{% if scenario %}Edit Scenario{% else %}Create Scenario{% endif %}</h1>
<p class="text-muted">Configure assumptions and metadata for this scenario.</p> <p class="text-muted">Configure assumptions and metadata for this scenario.</p>
</div> </div>
<div class="header-actions">
<a class="btn" href="{{ cancel_url }}">Cancel</a>
<button class="btn primary" type="submit">Save Scenario</button>
</div>
</header> </header>
{% if error %} {% if error %}
<div class="alert alert-error">{{ error }}</div> <div class="alert alert-error">{{ error }}</div>
{% endif %} {% endif %}
<form class="form" method="post" action="{{ form_action }}"> <form class="form scenario-form" method="post" action="{{ form_action }}">
<div class="form-grid"> <div class="form-grid">
<div class="form-group"> <div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
@@ -76,12 +80,12 @@
<div class="form-group"> <div class="form-group">
<label for="description">Description</label> <label for="description">Description</label>
<textarea id="description" name="description" rows="4">{{ scenario.description if scenario else '' }}</textarea> <textarea id="description" name="description" rows="5">{{ scenario.description if scenario else '' }}</textarea>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<a class="btn btn-secondary" href="{{ cancel_url }}">Cancel</a> <a class="btn" href="{{ cancel_url }}">Cancel</a>
<button class="btn btn-primary" type="submit">Save</button> <button class="btn primary" type="submit">Save Scenario</button>
</div> </div>
</form> </form>
{% endblock %} {% endblock %}