timekeeper/app/templates/attendance.html
2026-01-15 15:46:35 -05:00

106 lines
5.6 KiB
HTML

{% extends "layout.html" %}
{% block content %}
<style>
.att-toolbar { display:flex; gap:12px; align-items:center; flex-wrap:wrap; }
.att-grid { display:flex; flex-direction:column; gap:14px; }
.att-row { display:grid; grid-template-columns: 260px 1fr; gap:12px; align-items:flex-start; }
.att-name { font-weight:600; }
/* Bigger cells for easier on-screen viewing */
.cells { display:grid; grid-auto-flow: column; grid-auto-columns: 24px; gap:4px; overflow-x:auto; padding-bottom:6px; }
.cell { width:24px; height:24px; border-radius:4px; border:1px solid #e5e7eb; }
.c-worked { background:#22c55e33; border-color:#16a34a66; }
.c-off { background:#06b6d433; border-color:#0ea5a566; } /* Off PTO and skipped workdays */
.c-sick { background:#f43f5e33; border-color:#e11d4866; } /* Sick PTO (distinct color) */
.c-pto { background:#3b82f633; border-color:#2563eb66; } /* PTO only when type is 'PTO' */
.c-holiday { background:#f59e0b33; border-color:#d9770666; }
.c-other { background:#a855f733; border-color:#7e22ce66; } /* Was bereavement */
.c-weekend { background:#e5e7eb; border-color:#d1d5db; }
.c-nodata { background:#fafafa; border-color:#eeeeee; } /* No submissions in range */
.legend { display:flex; gap:12px; flex-wrap:wrap; margin-top:6px; }
.legend .item { display:flex; gap:6px; align-items:center; }
.legend .swatch { width:16px; height:16px; border-radius:3px; border:1px solid #e5e7eb; }
</style>
<div class="page-wide">
<div class="panel">
<div class="panel-title">Attendance Tracker</div>
<form class="panel att-toolbar" method="get" action="/attendance">
<label class="label">Start</label>
<input class="input" type="date" name="start" value="{{ start.isoformat() }}">
<label class="label">End</label>
<input class="input" type="date" name="end" value="{{ end.isoformat() }}">
<label class="label">Employee</label>
<select class="select" name="employee_id" id="employee_id" style="min-width:220px;">
<option value="all" {% if not selected_employee_id %}selected{% endif %}>All employees</option>
{% for e in employees %}
<option value="{{ e.id }}" {% if selected_employee_id and e.id == selected_employee_id %}selected{% endif %}>{{ e.name }}</option>
{% endfor %}
</select>
<label class="checkbox" style="display:flex; gap:6px; align-items:center;">
<input type="checkbox" name="include_weekends" value="1" {% if include_weekends %}checked{% endif %}>
Include weekends
</label>
<button class="btn primary" type="submit">Run</button>
<a class="btn" target="_blank"
href="/attendance/export.csv?start={{ start.isoformat() }}&end={{ end.isoformat() }}&employee_id={{ selected_employee_id if selected_employee_id else 'all' }}&include_weekends={{ 1 if include_weekends else 0 }}">
Export CSV
</a>
</form>
<div class="legend">
<div class="item"><span class="swatch" style="background:#22c55e33;border-color:#16a34a66;"></span> Worked</div>
<div class="item"><span class="swatch" style="background:#06b6d433;border-color:#0ea5a566;"></span> Off</div>
<div class="item"><span class="swatch" style="background:#f43f5e33;border-color:#e11d4866;"></span> Sick</div>
<div class="item"><span class="swatch" style="background:#3b82f633;border-color:#2563eb66;"></span> PTO</div>
<div class="item"><span class="swatch" style="background:#f59e0b33;border-color:#d9770666;"></span> Holiday</div>
<div class="item"><span class="swatch" style="background:#a855f733;border-color:#7e22ce66;"></span> Other</div>
<div class="item"><span class="swatch" style="background:#e5e7eb;border-color:#d1d5db;"></span> Weekend</div>
<div class="item"><span class="swatch" style="background:#fafafa;border-color:#eeeeee;"></span> No data (no submissions in range)</div>
</div>
</div>
<div class="panel">
<div class="panel-title">Period Overview</div>
<div class="att-grid">
{% for row in visual %}
<div class="att-row">
<div class="att-name">{{ row.employee.name }}</div>
<div class="cells" title="Scroll horizontally for full range">
{% for c in row.cells %}
<div class="cell c-{{ c.status }}" title="{{ c.date.isoformat() }} — {{ c.status }}"></div>
{% endfor %}
</div>
</div>
<div class="att-row" style="grid-template-columns:260px 1fr;">
<div></div>
<div style="display:flex; gap:16px; flex-wrap:wrap; margin-bottom:10px;">
<div><strong>Worked days:</strong> {{ row.totals.worked_days }} ({{ row.hours.worked|round(2) }} hrs)</div>
<div><strong>Off days:</strong> {{ row.totals.off_days }} ({{ row.hours.off|round(2) }} hrs)</div>
<div><strong>Sick days:</strong> {{ row.totals.sick_days }} ({{ row.hours.sick|round(2) }} hrs)</div>
<div><strong>PTO days:</strong> {{ row.totals.pto_days }} ({{ row.hours.pto|round(2) }} hrs)</div>
<div><strong>Holiday days:</strong> {{ row.totals.holiday_days }} ({{ row.hours.holiday|round(2) }} hrs)</div>
<div><strong>Other days:</strong> {{ row.totals.other_days }} ({{ row.hours.other|round(2) }} hrs)</div>
</div>
</div>
<hr style="border:none; border-top:1px solid #eee; margin:10px 0;">
{% endfor %}
</div>
</div>
</div>
<script>
// Auto-submit when employee dropdown changes
(function () {
var sel = document.getElementById('employee_id');
if (!sel) return;
sel.addEventListener('change', function () { sel.form.submit(); });
})();
</script>
{% endblock %}