04/25 modified the UI, projects -> contracts, button names

This commit is contained in:
2026-04-25 11:21:27 -04:00
parent 9d9880e20b
commit df9f245c57
15 changed files with 46 additions and 46 deletions
+4 -4
View File
@@ -347,7 +347,7 @@ def manage(customer_id):
aform = CustomerAssignmentForm() aform = CustomerAssignmentForm()
projects = Project.query.filter_by(active=True).order_by(Project.name).all() projects = Project.query.filter_by(active=True).order_by(Project.name).all()
aform.user_id.choices = [(customer.id, customer.username)] aform.user_id.choices = [(customer.id, customer.username)]
aform.facility_id.choices = [(0, '— All facilities in project —')] aform.facility_id.choices = [(0, '— All facilities in contract —')]
return render_template( return render_template(
'customers/manage.html', 'customers/manage.html',
@@ -374,7 +374,7 @@ def add_assignment(customer_id):
facility_id = request.form.get('facility_id', type=int) or None facility_id = request.form.get('facility_id', type=int) or None
if not project_id: if not project_id:
flash('Please select a project.', 'warning') flash('Please select a contract.', 'warning')
return redirect(url_for('customers.manage', customer_id=customer_id)) return redirect(url_for('customers.manage', customer_id=customer_id))
project = Project.query.get_or_404(project_id) project = Project.query.get_or_404(project_id)
@@ -691,7 +691,7 @@ def bulk_import():
if pname: if pname:
project = proj_by_name.get(pname.lower()) project = proj_by_name.get(pname.lower())
if project is None: if project is None:
row_errors.append(f'project "{pname}" not found or inactive') row_errors.append(f'contract "{pname}" not found or inactive')
else: else:
proj_id = project.id proj_id = project.id
if fname: if fname:
@@ -703,7 +703,7 @@ def bulk_import():
).first() ).first()
if facility is None: if facility is None:
row_errors.append( row_errors.append(
f'facility "{fname}" not found in project "{pname}"' f'facility "{fname}" not found in contract "{pname}"'
) )
else: else:
fac_id = facility.id fac_id = facility.id
+7 -7
View File
@@ -60,10 +60,10 @@ def create():
current_user.username, project.id, project.name) current_user.username, project.id, project.name)
log_action(ACTION_CREATE, 'Project', project.id, project.name, log_action(ACTION_CREATE, 'Project', project.id, project.name,
f'pm_id={pm_id}; active={project.active}') f'pm_id={pm_id}; active={project.active}')
flash(f'Project "{project.name}" created successfully.', 'success') flash(f'Contract "{project.name}" created successfully.', 'success')
return redirect(url_for('projects.view', project_id=project.id)) return redirect(url_for('projects.view', project_id=project.id))
return render_template('projects/form.html', form=form, title='Create Project') return render_template('projects/form.html', form=form, title='Create Contract')
# ── View ────────────────────────────────────────────────────────────────────── # ── View ──────────────────────────────────────────────────────────────────────
@@ -111,10 +111,10 @@ def edit(project_id):
current_user.username, project.id, project.name) current_user.username, project.id, project.name)
log_action(ACTION_UPDATE, 'Project', project.id, project.name, log_action(ACTION_UPDATE, 'Project', project.id, project.name,
f'pm_id={project.project_manager_id}; active={project.active}') f'pm_id={project.project_manager_id}; active={project.active}')
flash(f'Project "{project.name}" updated successfully.', 'success') flash(f'Contract "{project.name}" updated successfully.', 'success')
return redirect(url_for('projects.view', project_id=project.id)) return redirect(url_for('projects.view', project_id=project.id))
return render_template('projects/form.html', form=form, project=project, title='Edit Project') return render_template('projects/form.html', form=form, project=project, title='Edit Contract')
# ── Delete ──────────────────────────────────────────────────────────────────── # ── Delete ────────────────────────────────────────────────────────────────────
@@ -137,7 +137,7 @@ def delete(project_id):
logger.info('PROJECTS | delete | user=%s project_id=%s name=%s', logger.info('PROJECTS | delete | user=%s project_id=%s name=%s',
current_user.username, project_id_snap, project_name) current_user.username, project_id_snap, project_name)
log_action(ACTION_DELETE, 'Project', project_id_snap, project_name) log_action(ACTION_DELETE, 'Project', project_id_snap, project_name)
flash(f'Project "{project_name}" deleted successfully.', 'success') flash(f'Contract "{project_name}" deleted successfully.', 'success')
return redirect(url_for('projects.index')) return redirect(url_for('projects.index'))
@@ -156,7 +156,7 @@ def add_assignment(project_id):
# Facilities belonging to this project # Facilities belonging to this project
project_facilities = project.facilities.order_by(Facility.name).all() project_facilities = project.facilities.order_by(Facility.name).all()
form.facility_id.choices = [(0, '— All facilities in project —')] + \ form.facility_id.choices = [(0, '— All facilities in contract —')] + \
[(f.id, f.name) for f in project_facilities] [(f.id, f.name) for f in project_facilities]
if form.validate_on_submit(): if form.validate_on_submit():
@@ -188,7 +188,7 @@ def add_assignment(project_id):
log_action(ACTION_CREATE, 'CustomerAssignment', assignment.id, log_action(ACTION_CREATE, 'CustomerAssignment', assignment.id,
f'{user.username}{project.name}', f'{user.username}{project.name}',
f'scope={scope_label}') f'scope={scope_label}')
flash(f'Customer "{user.username}" assigned to project "{project.name}".', 'success') flash(f'Customer "{user.username}" assigned to contract "{project.name}".', 'success')
return redirect(url_for('projects.view', project_id=project_id)) return redirect(url_for('projects.view', project_id=project_id))
return render_template( return render_template(
+1 -1
View File
@@ -204,7 +204,7 @@
</p> </p>
<p class="mb-1" style="font-size:.8rem;"> <p class="mb-1" style="font-size:.8rem;">
<strong>Customer:</strong> Customer-portal users are notified only for facilities <strong>Customer:</strong> Customer-portal users are notified only for facilities
they are assigned to via their project/facility assignments. they are assigned to via their contract/facility assignments.
</p> </p>
<p class="mb-0" style="font-size:.8rem;"> <p class="mb-0" style="font-size:.8rem;">
<strong>Custom Recipients:</strong> Additional email addresses (e.g. external managers) <strong>Custom Recipients:</strong> Additional email addresses (e.g. external managers)
+1 -1
View File
@@ -103,7 +103,7 @@
</li> </li>
{% if current_user.role in ['admin', 'director', 'project_manager'] %} {% if current_user.role in ['admin', 'director', 'project_manager'] %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('projects.index') }}">Projects</a> <a class="nav-link" href="{{ url_for('projects.index') }}">Contracts</a>
</li> </li>
{% endif %} {% endif %}
<li class="nav-item"> <li class="nav-item">
+5 -5
View File
@@ -33,15 +33,15 @@
<div class="col-md-4"> <div class="col-md-4">
<strong>Optional columns</strong> <strong>Optional columns</strong>
<ul class="mb-0 mt-1 ps-3"> <ul class="mb-0 mt-1 ps-3">
<li><code>project_name</code> — exact project name</li> <li><code>project_name</code> — exact contract name</li>
<li><code>facility_name</code> — exact facility name within project (leave blank for all)</li> <li><code>facility_name</code> — exact facility name within contract (leave blank for all)</li>
</ul> </ul>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<strong>Tips</strong> <strong>Tips</strong>
<ul class="mb-0 mt-1 ps-3"> <ul class="mb-0 mt-1 ps-3">
<li>Repeat a username on multiple rows to assign them to multiple projects</li> <li>Repeat a username on multiple rows to assign them to multiple contracts</li>
<li>Leave <code>facility_name</code> blank to grant access to all facilities in the project</li> <li>Leave <code>facility_name</code> blank to grant access to all facilities in the contract</li>
<li>Existing usernames/emails will be flagged as errors before anything is saved</li> <li>Existing usernames/emails will be flagged as errors before anything is saved</li>
</ul> </ul>
</div> </div>
@@ -96,7 +96,7 @@
<th width="50">Row</th> <th width="50">Row</th>
<th>Username</th> <th>Username</th>
<th>Email</th> <th>Email</th>
<th>Project</th> <th>Contract</th>
<th>Facility Scope</th> <th>Facility Scope</th>
<th>Status</th> <th>Status</th>
</tr> </tr>
+1 -1
View File
@@ -28,7 +28,7 @@
<th>Full Name</th> <th>Full Name</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Assigned Projects</th> <th>Assigned Contracts</th>
<th>Accessible Facilities</th> <th>Accessible Facilities</th>
<th>Created</th> <th>Created</th>
<th width="160"></th> <th width="160"></th>
+7 -7
View File
@@ -123,14 +123,14 @@
{# ── Current assignments table ── #} {# ── Current assignments table ── #}
<div class="card shadow-sm mb-4"> <div class="card shadow-sm mb-4">
<div class="card-header bg-light fw-semibold d-flex justify-content-between align-items-center"> <div class="card-header bg-light fw-semibold d-flex justify-content-between align-items-center">
<span><i class="bi bi-diagram-3 me-1"></i> Project Assignments</span> <span><i class="bi bi-diagram-3 me-1"></i> Contract Assignments</span>
</div> </div>
{% if assignments %} {% if assignments %}
<div class="card-body p-0"> <div class="card-body p-0">
<table class="table table-hover table-sm mb-0"> <table class="table table-hover table-sm mb-0">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th>Project</th> <th>Contract</th>
<th>Facility Scope</th> <th>Facility Scope</th>
<th>Assigned</th> <th>Assigned</th>
<th width="60"></th> <th width="60"></th>
@@ -186,10 +186,10 @@
<div class="row g-3 align-items-end"> <div class="row g-3 align-items-end">
<div class="col-md-5"> <div class="col-md-5">
<label class="form-label small fw-semibold">Project</label> <label class="form-label small fw-semibold">Contract</label>
<select name="project_id" id="proj-select" class="form-select form-select-sm" <select name="project_id" id="proj-select" class="form-select form-select-sm"
required> required>
<option value="">— Select project —</option> <option value="">— Select contract —</option>
{% for p in projects %} {% for p in projects %}
<option value="{{ p.id }}">{{ p.name }}</option> <option value="{{ p.id }}">{{ p.name }}</option>
{% endfor %} {% endfor %}
@@ -198,10 +198,10 @@
<div class="col-md-5"> <div class="col-md-5">
<label class="form-label small fw-semibold">Facility Scope</label> <label class="form-label small fw-semibold">Facility Scope</label>
<select name="facility_id" id="fac-select" class="form-select form-select-sm"> <select name="facility_id" id="fac-select" class="form-select form-select-sm">
<option value="">— All facilities in project —</option> <option value="">— All facilities in contract —</option>
</select> </select>
<div class="form-text" style="font-size:.72rem;"> <div class="form-text" style="font-size:.72rem;">
Leave blank to grant access to all facilities in the project. Leave blank to grant access to all facilities in the contract.
</div> </div>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
@@ -228,7 +228,7 @@
projSelect.addEventListener('change', function () { projSelect.addEventListener('change', function () {
const projectId = this.value; const projectId = this.value;
facSelect.innerHTML = '<option value="">— All facilities in project —</option>'; facSelect.innerHTML = '<option value="">— All facilities in contract —</option>';
if (!projectId) return; if (!projectId) return;
+1 -1
View File
@@ -134,7 +134,7 @@
<tr> <tr>
<th>Facility</th> <th>Facility</th>
<th>Address</th> <th>Address</th>
<th>Project</th> <th>Contract</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
+1 -1
View File
@@ -37,7 +37,7 @@
<div class="mb-3"> <div class="mb-3">
{{ form.project_id.label(class="form-label") }} {{ form.project_id.label(class="form-label") }}
{{ form.project_id(class="form-select") }} {{ form.project_id(class="form-select") }}
<div class="form-text">Link this facility to a project for customer portal access.</div> <div class="form-text">Link this facility to a contract for customer portal access.</div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
+2 -2
View File
@@ -5,7 +5,7 @@
<h2><i class="bi bi-clipboard-data"></i> Inspections</h2> <h2><i class="bi bi-clipboard-data"></i> Inspections</h2>
{% if current_user.role != 'customer' %} {% if current_user.role != 'customer' %}
<a href="{{ url_for('inspections.start') }}" class="btn btn-primary"> <a href="{{ url_for('inspections.start') }}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Start Inspection <i class="bi bi-plus-circle"></i> New Inspection
</a> </a>
{% endif %} {% endif %}
</div> </div>
@@ -69,7 +69,7 @@
</td> </td>
<td> <td>
<span class="badge bg-{{ 'success' if ins.status == 'completed' else 'danger' if ins.status == 'flagged' else 'secondary' }}"> <span class="badge bg-{{ 'success' if ins.status == 'completed' else 'danger' if ins.status == 'flagged' else 'secondary' }}">
{{ ins.status|replace('_',' ')|title }} {{ 'Submitted' if ins.status == 'completed' else ins.status|replace('_',' ')|title }}
</span> </span>
{% if ins.follow_up_required and not ins.follow_ups.count() %} {% if ins.follow_up_required and not ins.follow_ups.count() %}
<span class="badge bg-danger ms-1" title="Follow-up re-inspection required"> <span class="badge bg-danger ms-1" title="Follow-up re-inspection required">
+3 -3
View File
@@ -9,7 +9,7 @@
<h4 class="mb-0"> <h4 class="mb-0">
<i class="bi bi-person-plus"></i> {{ title }} <i class="bi bi-person-plus"></i> {{ title }}
</h4> </h4>
<small class="text-white-50">Project: {{ project.name }}</small> <small class="text-white-50">Contract: {{ project.name }}</small>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
@@ -30,8 +30,8 @@
{{ form.facility_id.label(class="form-label") }} {{ form.facility_id.label(class="form-label") }}
{{ form.facility_id(class="form-select") }} {{ form.facility_id(class="form-select") }}
<div class="form-text"> <div class="form-text">
Select "All facilities in project" to grant access to every facility Select "All facilities in contract" to grant access to every facility
within this project, or choose a specific facility to restrict access. within this contract, or choose a specific facility to restrict access.
</div> </div>
</div> </div>
+1 -1
View File
@@ -42,7 +42,7 @@
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="bi bi-save"></i> Save Project <i class="bi bi-save"></i> Save Contract
</button> </button>
<a href="{{ url_for('projects.index') }}" class="btn btn-secondary"> <a href="{{ url_for('projects.index') }}" class="btn btn-secondary">
<i class="bi bi-x-circle"></i> Cancel <i class="bi bi-x-circle"></i> Cancel
+5 -5
View File
@@ -1,15 +1,15 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Projects{% endblock %} {% block title %}Contracts{% endblock %}
{% block content %} {% block content %}
<div class="row mb-4 align-items-center"> <div class="row mb-4 align-items-center">
<div class="col"> <div class="col">
<h2><i class="bi bi-folder2-open"></i> Projects</h2> <h2><i class="bi bi-folder2-open"></i> Contracts</h2>
</div> </div>
{% if current_user.role in ['admin', 'director'] %} {% if current_user.role in ['admin', 'director'] %}
<div class="col-auto"> <div class="col-auto">
<a href="{{ url_for('projects.create') }}" class="btn btn-primary"> <a href="{{ url_for('projects.create') }}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> New Project <i class="bi bi-plus-circle"></i> New Contract
</a> </a>
</div> </div>
{% endif %} {% endif %}
@@ -60,7 +60,7 @@
{% if current_user.role == 'admin' %} {% if current_user.role == 'admin' %}
<form method="POST" action="{{ url_for('projects.delete', project_id=project.id) }}" <form method="POST" action="{{ url_for('projects.delete', project_id=project.id) }}"
class="ms-auto d-inline" class="ms-auto d-inline"
onsubmit="return confirm('Delete project \'{{ project.name }}\'? This cannot be undone.');"> onsubmit="return confirm('Delete contract \'{{ project.name }}\'? This cannot be undone.');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger"> <button type="submit" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i> <i class="bi bi-trash"></i>
@@ -73,7 +73,7 @@
{% else %} {% else %}
<div class="col-12"> <div class="col-12">
<div class="alert alert-info"> <div class="alert alert-info">
<i class="bi bi-info-circle"></i> No projects have been created yet. <i class="bi bi-info-circle"></i> No contracts have been created yet.
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
+5 -5
View File
@@ -21,18 +21,18 @@
</a> </a>
{% endif %} {% endif %}
<a href="{{ url_for('projects.index') }}" class="btn btn-outline-primary"> <a href="{{ url_for('projects.index') }}" class="btn btn-outline-primary">
<i class="bi bi-arrow-left"></i> All Projects <i class="bi bi-arrow-left"></i> All Contracts
</a> </a>
</div> </div>
</div> </div>
<div class="row g-4"> <div class="row g-4">
{# ── Project Info ── #} {# ── Contract Info ── #}
<div class="col-md-4"> <div class="col-md-4">
<div class="card shadow-sm h-100"> <div class="card shadow-sm h-100">
<div class="card-header bg-light fw-semibold"> <div class="card-header bg-light fw-semibold">
<i class="bi bi-info-circle"></i> Project Details <i class="bi bi-info-circle"></i> Contract Details
</div> </div>
<div class="card-body"> <div class="card-body">
<dl class="row mb-0"> <dl class="row mb-0">
@@ -42,7 +42,7 @@
{{ 'Active' if project.active else 'Inactive' }} {{ 'Active' if project.active else 'Inactive' }}
</span> </span>
</dd> </dd>
<dt class="col-5 text-muted small">Project Manager</dt> <dt class="col-5 text-muted small">Contract Manager</dt>
<dd class="col-7 small"> <dd class="col-7 small">
{{ project.project_manager.username if project.project_manager else '—' }} {{ project.project_manager.username if project.project_manager else '—' }}
</dd> </dd>
@@ -95,7 +95,7 @@
</table> </table>
</div> </div>
{% else %} {% else %}
<div class="p-3 text-muted small">No facilities linked to this project yet.</div> <div class="p-3 text-muted small">No facilities linked to this contract yet.</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
+2 -2
View File
@@ -96,7 +96,7 @@ class FacilityForm(FlaskForm):
address = TextAreaField('Address', validators=[Optional()]) address = TextAreaField('Address', validators=[Optional()])
contact_person = StringField('Contact Person', validators=[Optional(), Length(max=100)]) contact_person = StringField('Contact Person', validators=[Optional(), Length(max=100)])
contact_phone = StringField('Contact Phone', validators=[Optional(), Length(max=20)]) contact_phone = StringField('Contact Phone', validators=[Optional(), Length(max=20)])
project_id = SelectField('Project', coerce=int, validators=[Optional()]) project_id = SelectField('Contract', coerce=int, validators=[Optional()])
active = BooleanField('Active', default=True) active = BooleanField('Active', default=True)
@@ -183,7 +183,7 @@ class IssueUpdateForm(FlaskForm):
# ── Projects ───────────────────────────────────────────────────────────────── # ── Projects ─────────────────────────────────────────────────────────────────
class ProjectForm(FlaskForm): class ProjectForm(FlaskForm):
name = StringField('Project Name', validators=[DataRequired(), Length(max=255)]) name = StringField('Contract Name', validators=[DataRequired(), Length(max=255)])
description = TextAreaField('Description', validators=[Optional()]) description = TextAreaField('Description', validators=[Optional()])
project_manager_id = SelectField('Project Manager', coerce=int, validators=[Optional()]) project_manager_id = SelectField('Project Manager', coerce=int, validators=[Optional()])
active = BooleanField('Active', default=True) active = BooleanField('Active', default=True)