Report: Chapter IV — Team × Project Activity pivot
Final chapter of the executive redesign. Renders the team_project_activity context as a pivot: rows=teams, columns=projects, cell=COUNT(DISTINCT work-log dates). Zero cells show em-dashes in muted grey (not '0') so non-zero cells stand out. Row totals, column totals, and grand total on the bottom row. Adds a tiny dictlookup template filter (format_tags.py) — Django templates can't index a dict by a dynamic variable key, and the pivot cell lookup is cells_by_project_id[col.id]. Defensive None + TypeError guards so a malformed context can't 500 the page. .table-total-row CSS: 2px top border + inset background for the footer row so totals visually separate from the data rows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
89c42d25a3
commit
fe85c9d7fd
@ -333,6 +333,53 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# === CHAPTER IV — Team × Project Activity === #}
|
||||
<h5 class="chapter-heading mb-3"><span class="chapter-num">IV</span>Team × Project Activity</h5>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 fw-bold"><i class="fas fa-th me-2" style="color: var(--accent);"></i>Distinct Work Days per Team × Project</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% if team_project_activity.rows %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0 report-numeric">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Team</th>
|
||||
{% for col in team_project_activity.columns %}
|
||||
<th class="text-end">{{ col.name }}</th>
|
||||
{% endfor %}
|
||||
<th class="text-end fw-bold">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in team_project_activity.rows %}
|
||||
<tr>
|
||||
<td class="fw-medium">{{ row.team_name }}</td>
|
||||
{% for col in team_project_activity.columns %}
|
||||
<td class="text-end">
|
||||
{% with days=row.cells_by_project_id|dictlookup:col.id %}
|
||||
{% if days %}{{ days }}{% else %}<span class="text-muted">—</span>{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td class="text-end fw-semibold">{{ row.row_total }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="table-total-row">
|
||||
<td class="fw-bold">Total</td>
|
||||
{% for col in team_project_activity.columns %}
|
||||
<td class="text-end fw-bold">{{ team_project_activity.col_totals|dictlookup:col.id }}</td>
|
||||
{% endfor %}
|
||||
<td class="text-end fw-bold">{{ team_project_activity.grand_total }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}<p class="text-muted text-center py-3 mb-0">No team × project activity in this period.</p>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Action Bar -->
|
||||
<div class="d-flex justify-content-between align-items-center d-print-none">
|
||||
<a href="{% url 'home' %}" class="btn btn-outline-secondary"><i class="fas fa-arrow-left me-1"></i>Back to Dashboard</a>
|
||||
|
||||
@ -25,3 +25,20 @@ def money(value):
|
||||
# Python's :, format gives comma separators — swap commas for spaces
|
||||
formatted = f"{num:,.2f}"
|
||||
return formatted.replace(",", " ")
|
||||
|
||||
|
||||
@register.filter
|
||||
def dictlookup(d, key):
|
||||
"""Look up a dict value by a dynamic key.
|
||||
|
||||
Plain-English: in Django templates, `{{ mydict.foo }}` only works when
|
||||
`foo` is a literal key. For `mydict[col.id]` with a variable key you need
|
||||
a filter — this one. Returns None if the key is missing or the input
|
||||
isn't a dict.
|
||||
"""
|
||||
if d is None:
|
||||
return None
|
||||
try:
|
||||
return d.get(key)
|
||||
except (AttributeError, TypeError):
|
||||
return None
|
||||
|
||||
@ -1721,3 +1721,15 @@ body, .card, .modal-content, .form-control, .form-select,
|
||||
.report-numeric th {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* === Pivot-table footer totals (Chapter IV) === */
|
||||
/*
|
||||
Bold, slightly-lifted row at the bottom of the Team × Project pivot
|
||||
that holds the column totals + grand total. The 2px top border
|
||||
visually separates totals from the data rows; the inset background
|
||||
is the same --bg-inset used by other "slightly raised" surfaces.
|
||||
*/
|
||||
.table-total-row td {
|
||||
border-top: 2px solid var(--border-default) !important;
|
||||
background: var(--bg-inset);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user