July 7 - Implement notification per contract

This commit is contained in:
2026-07-07 10:03:55 -04:00
parent 0d4a10d01d
commit e578fb6ca1
7 changed files with 634 additions and 6 deletions
+175
View File
@@ -165,5 +165,180 @@
</div>
{% endif %}
{# ── Additional Notification Recipients ── #}
{% if current_user.role == 'admin' %}
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<span class="fw-semibold">
<i class="bi bi-bell"></i> Additional Notification Recipients
</span>
<button class="btn btn-sm btn-primary" type="button"
data-bs-toggle="collapse" data-bs-target="#addNotifyRecipient">
<i class="bi bi-plus-lg"></i> Add Recipient
</button>
</div>
{# Add form (collapsed by default) #}
<div class="collapse border-bottom" id="addNotifyRecipient">
<form method="POST"
action="{{ url_for('projects.add_notify_recipient', project_id=project.id) }}"
class="p-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="row g-3 align-items-start">
{# Recipient type + target #}
<div class="col-md-5">
<label class="form-label small fw-semibold">Recipient</label>
<div class="btn-group btn-group-sm w-100 mb-2" role="group">
<input type="radio" class="btn-check" name="recipient_type"
id="rtypeUser" value="user" checked
onchange="toggleRecipientType()">
<label class="btn btn-outline-secondary" for="rtypeUser">Staff User</label>
<input type="radio" class="btn-check" name="recipient_type"
id="rtypeEmail" value="email"
onchange="toggleRecipientType()">
<label class="btn btn-outline-secondary" for="rtypeEmail">Email Address</label>
</div>
<div id="recipientUserWrap">
<select name="user_id" class="form-select form-select-sm">
<option value="">— Select a staff user —</option>
{% for u in staff_users %}
<option value="{{ u.id }}">{{ u.display_name }} ({{ u.role }})</option>
{% endfor %}
</select>
</div>
<div id="recipientEmailWrap" class="d-none">
<input type="email" name="email" class="form-control form-control-sm"
placeholder="name@example.com">
<div class="form-text">External address — receives email only.</div>
</div>
</div>
{# Event subscription #}
<div class="col-md-7">
<label class="form-label small fw-semibold d-flex justify-content-between">
<span>Notify for events</span>
<span>
<a href="#" class="small text-decoration-none"
onclick="setAllEvents(true);return false;">All</a> /
<a href="#" class="small text-decoration-none"
onclick="setAllEvents(false);return false;">None</a>
</span>
</label>
<div class="row row-cols-1 row-cols-lg-2 g-1"
style="max-height:180px;overflow-y:auto;">
{% for ekey, elabel in matrix_events.items() %}
<div class="col">
<div class="form-check">
<input class="form-check-input notify-event-cb" type="checkbox"
name="event_types" value="{{ ekey }}"
id="ev_{{ ekey }}">
<label class="form-check-label small" for="ev_{{ ekey }}">
{{ elabel }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="mt-3 text-end">
<button type="submit" class="btn btn-sm btn-success">
<i class="bi bi-check-lg"></i> Save Recipient
</button>
</div>
</form>
</div>
{# Existing recipients #}
<div class="card-body p-0">
{% if notify_recipients %}
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Recipient</th>
<th>Type</th>
<th>Notified Events</th>
<th width="80"></th>
</tr>
</thead>
<tbody>
{% for r in notify_recipients %}
<tr>
<td>
{% if r.user_id %}
<strong>{{ r.user.display_name if r.user else '—' }}</strong>
<div class="text-muted small">{{ r.user.email if r.user else '' }}</div>
{% else %}
<strong>{{ r.email }}</strong>
{% endif %}
</td>
<td>
{% if r.user_id %}
<span class="badge bg-primary">Staff · in-app + email</span>
{% else %}
<span class="badge bg-secondary">External · email</span>
{% endif %}
</td>
<td>
{% set ev = r.get_event_types() %}
{% if ev %}
<div class="d-flex flex-wrap gap-1">
{% for e in ev %}
<span class="badge bg-light text-dark border">
{{ matrix_events.get(e, e) }}
</span>
{% endfor %}
</div>
{% else %}
<span class="text-muted small">None</span>
{% endif %}
</td>
<td>
<form method="POST"
action="{{ url_for('projects.remove_notify_recipient', recipient_id=r.id) }}"
onsubmit="return confirm('Remove this notification recipient?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger"
title="Remove recipient">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="p-3 text-muted small">
No additional recipients. These are notified — for the events you pick —
on top of the global
<a href="{{ url_for('auth.notification_matrix') }}">Notification Matrix</a>.
</div>
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
{% if current_user.role == 'admin' %}
<script>
function toggleRecipientType() {
var isUser = document.getElementById('rtypeUser').checked;
document.getElementById('recipientUserWrap').classList.toggle('d-none', !isUser);
document.getElementById('recipientEmailWrap').classList.toggle('d-none', isUser);
}
function setAllEvents(state) {
document.querySelectorAll('.notify-event-cb').forEach(function (cb) { cb.checked = state; });
}
</script>
{% endif %}
{% endblock %}