added on local at 2026-07-01 12:34:53
| 1 | <div class="page-header"> | |
| 2 | <div><h1>Money View</h1><p class="subtitle">Where time becomes dollars. <span class="text-muted" style="font-size:13px">· $range_label · $current_project_label</span></p></div> | |
| 3 | <div class="flex-end"> | |
| 4 | <form method="GET" action="/cost.cgi" style="display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap" id="costRangeForm"> | |
| 5 | <div class="field" style="margin:0"><label style="font-size:11px">Start</label><input class="input" type="date" name="start" value="$range_start" style="padding:6px 10px"></div> | |
| 6 | <div class="field" style="margin:0"><label style="font-size:11px">End</label><input class="input" type="date" name="end" value="$range_end" style="padding:6px 10px"></div> | |
| 7 | [if:$has_all_projects] | |
| 8 | <div class="field" style="margin:0"><label style="font-size:11px">Project</label> | |
| 9 | <select class="input" name="project" style="padding:6px 10px"> | |
| 10 | <option value="">All projects</option> | |
| 11 | [loop:@all_projects]<option value="$loop1.id" $loop1.is_selected>$loop1.label</option>[/loop] | |
| 12 | </select> | |
| 13 | </div> | |
| 14 | [/if] | |
| 15 | <button type="submit" class="btn btn-ghost btn-sm" style="height:36px">Apply</button> | |
| 16 | </form> | |
| 17 | </div> | |
| 18 | </div> | |
| 19 | ||
| 20 | [if:!$has_any_cost] | |
| 21 | <div class="card mb-3" style="border-left:3px solid var(--col-warn);padding:18px"> | |
| 22 | <strong>No cost data yet.</strong> Set your <a href="/company.cgi">company default hourly rate</a> and log some billable time to see the charts populate. Rates are snapshotted at log time, so changing them later won't rewrite history. | |
| 23 | </div> | |
| 24 | [/if] | |
| 25 | ||
| 26 | <div class="kpi-grid mb-3" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px"> | |
| 27 | <div class="kpi"><div class="kpi-label">Total cost (range)</div><div class="kpi-value">$kpi.total_cost_disp</div></div> | |
| 28 | <div class="kpi"><div class="kpi-label">Billable (range)</div><div class="kpi-value" style="color:#22c55e">$kpi.billable_cost_disp</div></div> | |
| 29 | <div class="kpi"><div class="kpi-label">Internal (range)</div><div class="kpi-value" style="color:#5aa9ff">$kpi.internal_cost_disp</div></div> | |
| 30 | <div class="kpi"><div class="kpi-label">Avg effective rate</div><div class="kpi-value">$kpi.avg_rate_disp<small>/hr</small></div></div> | |
| 31 | <div class="kpi"><div class="kpi-label">Hours logged (range)</div><div class="kpi-value">$kpi.total_hours_disp</div></div> | |
| 32 | <div class="kpi"><div class="kpi-label">Billable %</div><div class="kpi-value">$kpi.billable_pct%</div></div> | |
| 33 | </div> | |
| 34 | ||
| 35 | <div class="card mb-3"> | |
| 36 | <div class="card-header" style="display:flex;justify-content:space-between;align-items:center"> | |
| 37 | <h3 style="margin:0">Cost over time — billable vs internal</h3> | |
| 38 | <div style="display:flex;gap:12px;font-size:12px"> | |
| 39 | <span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:#22c55e;vertical-align:-1px"></span> Billable</span> | |
| 40 | <span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:#5aa9ff;vertical-align:-1px"></span> Internal</span> | |
| 41 | </div> | |
| 42 | </div> | |
| 43 | <svg viewBox="0 0 $chart_w $chart_h" style="width:100%;height:auto;display:block"> | |
| 44 | [loop:@y_labels] | |
| 45 | <line x1="50" x2="$chart_w" y1="$loop1.y" y2="$loop1.y" stroke="rgba(255,255,255,.06)" stroke-width="1"/> | |
| 46 | <text x="44" y="$loop1.y" dy="4" text-anchor="end" fill="var(--col-text-dim)" font-size="11">$loop1.text</text> | |
| 47 | [/loop] | |
| 48 | <path d="$internal_path" fill="#5aa9ff" fill-opacity="0.55" stroke="#5aa9ff" stroke-width="1.2"/> | |
| 49 | <path d="$billable_path" fill="#22c55e" fill-opacity="0.55" stroke="#22c55e" stroke-width="1.2"/> | |
| 50 | [loop:@x_labels] | |
| 51 | <text x="$loop1.x" y="$loop1.y" text-anchor="middle" fill="var(--col-text-dim)" font-size="11">$loop1.text</text> | |
| 52 | [/loop] | |
| 53 | </svg> | |
| 54 | </div> | |
| 55 | ||
| 56 | <div class="card mb-3"> | |
| 57 | <div class="card-header" style="display:flex;justify-content:space-between;align-items:center"> | |
| 58 | <h3 style="margin:0">Cost by department</h3> | |
| 59 | <div style="display:flex;gap:12px;font-size:12px;flex-wrap:wrap"> | |
| 60 | [loop:@dept_paths] | |
| 61 | <span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:$loop1.color;vertical-align:-1px"></span> $loop1.name</span> | |
| 62 | [/loop] | |
| 63 | </div> | |
| 64 | </div> | |
| 65 | [if:$has_dept_paths] | |
| 66 | <svg viewBox="0 0 $chart_w $chart_h" style="width:100%;height:auto;display:block"> | |
| 67 | [loop:@dept_y_labels] | |
| 68 | <line x1="50" x2="$chart_w" y1="$loop1.y" y2="$loop1.y" stroke="rgba(255,255,255,.06)" stroke-width="1"/> | |
| 69 | <text x="44" y="$loop1.y" dy="4" text-anchor="end" fill="var(--col-text-dim)" font-size="11">$loop1.text</text> | |
| 70 | [/loop] | |
| 71 | [loop:@dept_paths] | |
| 72 | <path d="$loop1.d" fill="$loop1.color" fill-opacity="0.55" stroke="$loop1.color" stroke-width="1"/> | |
| 73 | [/loop] | |
| 74 | [loop:@x_labels] | |
| 75 | <text x="$loop1.x" y="$loop1.y" text-anchor="middle" fill="var(--col-text-dim)" font-size="11">$loop1.text</text> | |
| 76 | [/loop] | |
| 77 | </svg> | |
| 78 | [/if] | |
| 79 | [if:!$has_dept_paths]<p class="text-muted" style="padding:18px">No department data in the last 12 weeks.</p>[/if] | |
| 80 | </div> | |
| 81 | ||
| 82 | <div class="card"> | |
| 83 | <div class="card-header"><h3 style="margin:0">Top projects by cost</h3><span class="text-muted" style="font-size:13px">$range_label · click a row to filter the charts to that project</span></div> | |
| 84 | [if:$has_projects] | |
| 85 | <table class="table"> | |
| 86 | <thead><tr><th>Project</th><th style="text-align:right">Hours</th><th style="text-align:right">Total cost</th><th style="text-align:right">Billable</th><th style="text-align:right">Billable %</th></tr></thead> | |
| 87 | <tbody> | |
| 88 | [loop:@projects] | |
| 89 | <tr class="clickable" onclick="document.location='/cost.cgi?start=$range_start&end=$range_end&project=$loop1.id'" style="cursor:pointer"> | |
| 90 | <td><span style="display:inline-block;width:10px;height:10px;border-radius:3px;background:$loop1.color;margin-right:8px;vertical-align:-1px"></span>$loop1.key_prefix · $loop1.name</td> | |
| 91 | <td style="text-align:right">$loop1.hours_disp</td> | |
| 92 | <td style="text-align:right;font-weight:600">$loop1.cost_disp</td> | |
| 93 | <td style="text-align:right;color:#22c55e">$loop1.billable_cost_disp</td> | |
| 94 | <td style="text-align:right">$loop1.billable_pct%</td> | |
| 95 | </tr> | |
| 96 | [/loop] | |
| 97 | </tbody> | |
| 98 | </table> | |
| 99 | [/if] | |
| 100 | [if:!$has_projects]<p class="text-muted" style="padding:18px">No time logged in this date range.</p>[/if] | |
| 101 | </div> |