Add Asset Summary table on Ratecard tab for visual validation

- New "Asset Summary" table at top of Ratecard tab showing:
  #, Client Asset name, Tier, Matched GMAL, Volume, Total Hours
- Grand total row with volume sum and hours sum
- Appears above the existing "Hours by Role" detail table
- Section labels ("Asset Summary" / "Hours by Role") for clarity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
DJP 2026-04-10 10:56:06 -04:00
parent 9eaa85dc37
commit d85ef96a06
2 changed files with 73 additions and 0 deletions

View file

@ -820,6 +820,15 @@ span.conf-none { background: var(--color-danger); }
flex: 1;
}
.rc-section-label {
font-size: 13px;
font-weight: 700;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
}
.text-right { text-align: right; }
.text-center { text-align: center; }

View file

@ -936,6 +936,70 @@ export default function ProjectView() {
</div>
</div>
{/* Asset Summary Table */}
<div className="rc-section-label">Asset Summary</div>
<div className="table-wrap" style={{ marginBottom: 20 }}>
<table className="rc-table">
<thead>
<tr>
<th>#</th>
<th>Client Asset</th>
<th>Tier</th>
<th>Matched GMAL</th>
<th>GMAL Name</th>
<th className="text-center">Volume</th>
<th className="text-right">Total Hours</th>
</tr>
</thead>
<tbody>
{(() => {
// Group ratecard lines by client asset to get unique assets + total hours
const assetMap: Record<number, { name: string; tier: string; gmal_id: string; gmal_name: string; volume: number; hours: number }> = {};
for (const l of ratecard.lines) {
if (!assetMap[l.client_asset_id]) {
const ca = assets.find(a => a.id === l.client_asset_id);
assetMap[l.client_asset_id] = {
name: l.client_asset_name || '',
tier: (ca as any)?.client_tier || '',
gmal_id: l.gmal_id || '',
gmal_name: '',
volume: l.volume,
hours: 0,
};
}
const effective = l.manual_override ?? l.total_hours ?? 0;
assetMap[l.client_asset_id].hours += effective;
}
const assetList = Object.entries(assetMap).sort(([a], [b]) => Number(a) - Number(b));
const grandTotal = assetList.reduce((sum, [, a]) => sum + a.hours, 0);
return (
<>
{assetList.map(([aid, a], idx) => (
<tr key={aid}>
<td className="td-discipline">{idx + 1}</td>
<td><strong>{a.name}</strong></td>
<td>{a.tier ? <span className="tier-tag" style={{ margin: 0 }}><strong>{a.tier}</strong></span> : <span style={{ color: 'var(--color-text-muted)' }}></span>}</td>
<td className="td-gmal">{a.gmal_id}</td>
<td className="td-discipline">{a.gmal_name}</td>
<td className="text-center"><strong>{a.volume}</strong></td>
<td className="text-right td-total">{a.hours.toFixed(2)}</td>
</tr>
))}
<tr>
<td colSpan={5}></td>
<td className="text-center"><strong>{assetList.reduce((s, [, a]) => s + a.volume, 0)}</strong></td>
<td className="text-right td-total" style={{ fontSize: 14 }}><strong>{grandTotal.toFixed(2)}</strong></td>
</tr>
</>
);
})()}
</tbody>
</table>
</div>
{/* Role Breakdown */}
<div className="rc-section-label">Hours by Role</div>
<div className="table-wrap">
<table className="rc-table">
<thead>