KQ-1775 · TEAM/CHARGEBACK
Team Chargeback
A cost-allocation view for a cloud account: the month's total is one long bar already divided into its teams, with what no rule claims hatched at its end and every billed month drawn below it. Pressing the total splits it: the segments part on springs.snap, then each slides down into its own row of a ranked list on springs.glide in one cascade while its share, trend and owner arrive beside it, and the unallocated remainder stays behind in the bar; pressing again folds the rows back up. Allocation is computed from tag rules and shared-cost splits, rows open with Enter to their spend by service and the rules that claimed it, arrow keys walk the rows and the month tabs, and a narrow container shortens each bar to its percentage.
Basinworks · cost by team
Chargeback for October · 5 teams
october · press the total to split it by team
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/team-chargeback
Props
- barStyle
- "solid" | "soft" | "outline"
- default "soft"
- How team bars are drawn: pigment fills, a tint with a lead edge, or outlines that fill on hover and focus.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Row height and bar thickness; roomy rows also show the owner's role.
- palette
- "basin" | "ember" | "spectrum" | "mono"
- default "basin"
- Team colours as pigments at fixed lightness: cool blues and teals, warm, spread hues, or ink steps.
- costs
- Record<string, ChargebackCost[]>
- default defaultChargebackCosts
- Costs per month id: { id, label (resource), service, amount, previous, tags }.
- months
- ChargebackMonth[]
- default defaultChargebackMonths
- The months as tabs, oldest first: { id, label, long }.
- month / defaultMonth
- string
- default — / "2026-10"
- The month shown, controlled or initial.
- onMonthChange
- (month: string) => void
- default —
- A month tab was chosen.
- teams
- ChargebackTeam[]
- default defaultChargebackTeams
- The teams costs are charged back to: { id, name, owner, role? }.
- rules / defaultRules
- ChargebackRule[]
- default — / defaultChargebackRules
- Allocation rules, controlled or initial: tag rules { kind: "tag", key, value, team, enabled } (key "resource" matches a cost's label) and split rules { kind: "split", key, value, method: "usage" | "even", enabled } for shared costs.
- onRuleChange
- (rule: ChargebackRule, rules: ChargebackRule[]) => void
- default —
- A rule was switched, re-methoded or added, with every rule.
- onAllocate
- (costId: string, teamId: string) => void
- default —
- An unallocated cost was assigned to a team; uncontrolled, a resource rule is added so the amount moves.
- onExport
- (csv: string, rows: ChargebackRow[]) => void
- default —
- Export was pressed: the month's allocation as CSV text and as rows.
- split / defaultSplit
- boolean
- default — / false
- Whether the total bar is split into team rows, controlled or initial. A host that flips it gets the same choreography as a press.
- onSplitChange
- (split: boolean) => void
- default —
- The total bar was pressed.
- format
- (amount: number) => string
- default US dollars, no cents
- Formats every amount.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the costs have arrived; a month without costs says so on its own.
- onRetry
- () => void
- default —
- Try again was pressed after the costs failed to load.
- account
- string
- default "Basinworks"
- The account, in the heading.
- height
- number
- default 540
- Height of the surface in px; its body scrolls inside.
- disabled
- boolean
- default false
- Nothing can be split, switched or assigned.
- className
- string
- default —
- Classes for the outer surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| barStyle | "solid" | "soft" | "outline" | "soft" | How team bars are drawn: pigment fills, a tint with a lead edge, or outlines that fill on hover and focus. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Row height and bar thickness; roomy rows also show the owner's role. |
| palette | "basin" | "ember" | "spectrum" | "mono" | "basin" | Team colours as pigments at fixed lightness: cool blues and teals, warm, spread hues, or ink steps. |
| costs | Record<string, ChargebackCost[]> | defaultChargebackCosts | Costs per month id: { id, label (resource), service, amount, previous, tags }. |
| months | ChargebackMonth[] | defaultChargebackMonths | The months as tabs, oldest first: { id, label, long }. |
| month / defaultMonth | string | — / "2026-10" | The month shown, controlled or initial. |
| onMonthChange | (month: string) => void | — | A month tab was chosen. |
| teams | ChargebackTeam[] | defaultChargebackTeams | The teams costs are charged back to: { id, name, owner, role? }. |
| rules / defaultRules | ChargebackRule[] | — / defaultChargebackRules | Allocation rules, controlled or initial: tag rules { kind: "tag", key, value, team, enabled } (key "resource" matches a cost's label) and split rules { kind: "split", key, value, method: "usage" | "even", enabled } for shared costs. |
| onRuleChange | (rule: ChargebackRule, rules: ChargebackRule[]) => void | — | A rule was switched, re-methoded or added, with every rule. |
| onAllocate | (costId: string, teamId: string) => void | — | An unallocated cost was assigned to a team; uncontrolled, a resource rule is added so the amount moves. |
| onExport | (csv: string, rows: ChargebackRow[]) => void | — | Export was pressed: the month's allocation as CSV text and as rows. |
| split / defaultSplit | boolean | — / false | Whether the total bar is split into team rows, controlled or initial. A host that flips it gets the same choreography as a press. |
| onSplitChange | (split: boolean) => void | — | The total bar was pressed. |
| format | (amount: number) => string | US dollars, no cents | Formats every amount. |
| status | "ready" | "loading" | "error" | "ready" | Whether the costs have arrived; a month without costs says so on its own. |
| onRetry | () => void | — | Try again was pressed after the costs failed to load. |
| account | string | "Basinworks" | The account, in the heading. |
| height | number | 540 | Height of the surface in px; its body scrolls inside. |
| disabled | boolean | false | Nothing can be split, switched or assigned. |
| className | string | — | Classes for the outer surface. |