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

218 lines
7.4 KiB
HTML

{% extends "layout.html" %}
{% block content %}
<div class="bundle">
{% for b in bundles %}
<section class="timesheet">
<div class="header">
<div class="brand">Timesheet</div>
<div class="row">
<div class="emp"><strong>Employee:</strong> {{ b.employee.name }}</div>
<div class="period"><strong>Period:</strong> {{ period_name }}</div>
</div>
<!-- Totals at the top (kept) -->
<div class="totals-top">
<span><strong>Regular:</strong> {{ b.grouped.totals.regular|fmt2 }}</span>
<span><strong>Overtime:</strong> {{ b.grouped.totals.overtime|fmt2 }}</span>
<span><strong>PTO:</strong> {{ b.grouped.totals.pto|fmt2 }}</span>
<span><strong>Holiday:</strong> {{ b.grouped.totals.holiday|fmt2 }}</span>
<span><strong>Other:</strong> {{ b.grouped.totals.bereavement|fmt2 }}</span>
<span><strong>Paid Total:</strong> {{ b.grouped.totals.paid_total|fmt2 }}</span>
</div>
</div>
<table class="grid">
<thead>
<tr>
<th>Date</th>
<th>Clock In</th>
<th>Clock Out</th>
<th class="num">Break</th>
<th class="num">Total</th>
<!-- PTO before PTO Type (kept) -->
<th class="num">PTO</th>
<th>PTO Type</th>
<th class="num">Holiday</th>
<th class="num">Other</th>
<th class="num">Paid Total</th>
</tr>
</thead>
<tbody>
{% for r in b.grouped.rows %}
<tr>
<td class="mono">{{ r.work_date }}</td>
<!-- Holiday takes precedence; then PTO type; else times -->
<td class="mono">
{% if r.holiday_hours and r.holiday_hours > 0 %}
Holiday
{% elif r.pto_type %}
{{ r.pto_type }}
{% else %}
{{ r.clock_in|fmt_excel_dt }}
{% endif %}
</td>
<td class="mono">
{% if r.holiday_hours and r.holiday_hours > 0 %}
Holiday
{% elif r.pto_type %}
{{ r.pto_type }}
{% else %}
{{ r.clock_out|fmt_excel_dt }}
{% endif %}
</td>
<td class="num mono">{{ r.break_hours|fmt2 }}</td>
<td class="num mono">{{ r.total_hours|fmt2 }}</td>
<td class="num mono">{{ r.pto_hours|fmt2 }}</td>
<td>{{ r.pto_type or "" }}</td>
<td class="num mono">{{ r.holiday_hours|fmt2 }}</td>
<td class="num mono">{{ r.bereavement_hours|fmt2 }}</td>
<td class="num mono">{{ r.hours_paid|fmt2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Footer underneath, per timesheet -->
<div class="footer-block">
<div class="comments">
<div class="comments-title">Comments</div>
<div class="comments-box">
{% if b.grouped.comments %}{{ b.grouped.comments }}{% endif %}
</div>
</div>
<div class="signatures">
<div class="sig-block">
<div class="signature-line"></div>
<div class="sig-label">Employee Signature</div>
</div>
<div class="sig-block">
<div class="signature-line"></div>
<div class="sig-label">Reviewer Signature</div>
</div>
<div class="sig-block">
<div class="signature-line"></div>
<div class="sig-label">Date</div>
</div>
</div>
</div>
</section>
{% endfor %}
</div>
<style>
header, nav, .navbar, .topbar, .site-header, .app-nav, .tk-header, .tk-nav, .tk-brand { display:none !important; }
@media print {
body { margin: 0 !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.tk-container { display:block !important; margin:0 !important; padding:0 !important; }
}
:root { --ink:#222; --grid:#cdd3da; --head:#f4f6f9; --margin: 0.30in; }
@page { size: Letter landscape; margin: var(--margin); }
.bundle {
width: calc(11in - (2 * var(--margin)));
margin: 0;
color: var(--ink);
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}
.timesheet { break-after: auto; page-break-after: auto; margin-bottom: 0.16in; }
.timesheet + .timesheet { break-before: page; page-break-before: always; }
.timesheet:last-child { break-after: avoid; page-break-after: avoid; }
.header { margin-bottom: 0.06in; }
.brand { font-size: 19.5pt; font-weight: 800; line-height: 1.1; margin-bottom: 0.03in; }
.row { display: grid; grid-template-columns: 1fr auto; align-items: end; column-gap: 0.16in; }
.emp, .period { font-size: 10.4pt; }
.totals-top {
display:flex; flex-wrap:wrap; gap:0.12in;
font-size: 11pt; margin-top: 0.04in; margin-bottom: 0.08in;
border: none; padding: 0;
}
/* Smaller body */
table.grid { width:100%; border-collapse: separate; border-spacing: 0; }
thead { display: table-header-group; }
table.grid th, table.grid td {
border: 1pt solid var(--grid);
padding: 0.042in 0.058in;
font-size: 9.8pt;
background: #fff;
}
table.grid thead th {
background: var(--head);
text-transform: uppercase;
font-size: 9.2pt;
letter-spacing: .02em;
color: #3a4856;
}
.num { text-align: right; }
.mono { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; }
table.grid tr { page-break-inside: avoid; }
.grid { margin-bottom: 0; padding-bottom: 0; }
/* Footer underneath each section */
.footer-block { margin-top: 0.14in; page-break-inside: avoid; }
.comments-title { font-size: 10.2pt; font-weight: 600; margin-bottom: 0.06in; }
.comments-box {
border: none;
min-height: 0.60in;
padding: 0.06in;
font-size: 9.8pt;
background: transparent;
}
.signatures {
display: grid;
grid-template-columns: 1fr 1fr 0.8fr;
column-gap: 0.16in;
align-items: end;
margin-top: 0.12in;
}
.signature-line { border-bottom: 1.3pt solid #333; height: 0.20in; }
.sig-label { font-size: 9.4pt; color:#555; text-align: center; margin-top: 0.04in; }
/* Tighten if needed */
.compact table.grid th, .compact table.grid td { padding: 0.038in 0.054in; font-size: 9.5pt; }
.compact .comments-box { min-height: 0.50in; }
.micro table.grid th, .micro table.grid td { padding: 0.034in 0.050in; font-size: 9.2pt; }
.micro .comments-box { min-height: 0.45in; }
</style>
<script>
(function(){
// Fit each section to a page by shrinking body/footers if necessary
const DPI = 96;
const pageHeightInches = 8.5;
const marginInches = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--margin')) || 0.30;
const availablePx = (pageHeightInches - (2 * marginInches)) * DPI;
function fitSection(sec) {
sec.classList.remove('compact','micro');
let h = sec.scrollHeight;
if (h > availablePx) {
sec.classList.add('compact');
h = sec.scrollHeight;
}
if (h > availablePx) {
sec.classList.add('micro');
}
}
function fitAll() {
document.querySelectorAll('.timesheet').forEach(fitSection);
}
window.addEventListener('load', function(){
fitAll();
setTimeout(function(){ window.focus(); window.print(); }, 60);
});
if (typeof window.onbeforeprint !== 'undefined') window.addEventListener('beforeprint', fitAll);
if (window.matchMedia) {
const mq = window.matchMedia('print');
if (mq && mq.addEventListener) mq.addEventListener('change', e => { if (e.matches) fitAll(); });
}
})();
</script>
{% endblock %}