You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Top layer of the billable-rates stack (on top of PR #933, which added billingRate to clients CRUD and totalAmount/per-entry amount to reports). Frontend only — this PR surfaces what the API already returns.
Clients page: a Billing Rate MUI TextField in the add/edit dialog plus a table column (rate > 0 renders toFixed(2), otherwise the same - Chip the other optional columns use). Form state keeps the rate as a string so the field can be empty, and submit converts it: billingRate: formData.billingRate === '' ? undefined : Number(formData.billingRate). The Number(...) matters — the backend Joi rule is .strict(), so a numeric string would 400.
Reports page: a fourth Total Billed summary card (the four cards drop to md: 3 to stay on one row) and an Amount column per entry. Both read through ?? 0 so a response without the new fields renders 0.00 instead of throwing on toFixed.
Types/api.ts: Client.billingRate is required, but WorkEntry.billable/amount are optional — the work-entries endpoints don't return them, only the report does.
Everything goes through the existing TanStack Query useQuery/useMutation calls in the two pages; no new data layer.
Stack: PR #932 (data model) → PR #933 (service/API) → this PR.
Testing
cd frontend && npm run lint and npm run build clean; cd backend && npm test still 179 green (backend untouched here). frontend/package.json has no test:coverage script, so nothing to run there. Verified end to end in the browser — see the runtime-test comment below (that run predates the hourlyRate → billingRate rename; the flow was re-checked after it).
Runtime test results — billable rates UI (tested at 74dfc23)
Tested end-to-end in the browser against a local backend (:3001) + frontend (:5173), watching the backend request log for 4xx/5xx. No 400s or 500s occurred during the entire run.
Amounts 500.00 / 200.00, Total Billed700.00, Total Hours unchanged at 3.50, and the four summary cards fit on one row with no wrapping.
PDF export — Total Amount line + Amount column (rendered, not mocked)
Total Amount: 700.00 is present and the Amount column text does not overlap Description with the new x-offsets.
Minor, pre-existing (not introduced here): the Date column contains raw epoch millis (1788220800000); because Date was narrowed 100→90 it now sits almost flush against the Hours value. Worth a follow-up to format the date in the exports.
Created with 125.50 → renders 125.50; created without a rate → renders - chip. Edited a rate to 200 → PUT /api/clients/1 returned 200 (no strict-Joi string rejection) and 200.00 survived a full reload with no "Failed to update client" alert.
CSV export contents
Header is exactly Date,Hours,Billable,Amount,Description,Created At with Yes and correct amounts.
Edge cases
Rate -5 and 12.345: blocked client-side, no request sent, no 400/500. Note the browser's native type=number validation fires before the new Hourly rate must be a positive number message, so that custom string is effectively unreachable by typed input.
Rate-0 client: Amount 0.00 / Total Billed 0.00, never NaN; empty-state row spans the full 5-column table.
billable = false amount rule: not verified at runtime — no UI/API path sets it and the DB is in-memory, so it remains unit-test-only coverage.
Runtime re-test after the hourly_rate → billing_rate rename (tested at fd29452)
Re-ran the wire-contract-critical paths end-to-end in the browser against a local backend (:3001) + frontend (:5173). No 4xx/5xx across all 26 backend requests. The rename did not break the API boundary.
Reports still resolve the rate from the renamed column (client rate 250, entries 4h + 2h):
Per-entry Amount 1000.00 / 500.00, Total Billed1500.00, Total Hours6.00, four cards on one row.
Created with 150 → renders 150.00 and survives reload. Re-opening the edit dialog on a freshly loaded page shows it prefilled with 150, which proves the GET response key matches the frontend form key. Edited to 250 → PUT /api/clients/1 returned 200, no error alert, and 250.00 survived a full reload — no silent field-name drop.
Exports still carry correct non-zero amounts
PDF: Total Amount: 1500.00, Amount column 1000.00 / 500.00, no overlap with Description.
CSV header is exactly Date,Hours,Billable,Amount,Description,Created At.
Still pre-existing (unrelated to the rename): the exported Date column is raw epoch millis (1788220800000), which sits almost flush against Hours in the PDF. Worth a follow-up to format dates in the exports.
Not re-covered in this focused run
Negative / >2-decimal input, zero-rate, and empty-state cases were verified at 74dfc23 and skipped here; nothing looked off. Note the renamed client-side message Billing rate must be a positive number is still effectively unreachable by typed input because Chrome's native type=number validation fires first.
billable = false amount rule: not verified at runtime (no UI/API path sets it; in-memory DB), so it remains unit-test-only coverage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Top layer of the billable-rates stack (on top of PR #933, which added
billingRateto clients CRUD andtotalAmount/per-entryamountto reports). Frontend only — this PR surfaces what the API already returns.Billing RateMUITextFieldin the add/edit dialog plus a table column (rate> 0renderstoFixed(2), otherwise the same-Chip the other optional columns use). Form state keeps the rate as a string so the field can be empty, and submit converts it:billingRate: formData.billingRate === '' ? undefined : Number(formData.billingRate). TheNumber(...)matters — the backend Joi rule is.strict(), so a numeric string would 400.Total Billedsummary card (the four cards drop tomd: 3to stay on one row) and anAmountcolumn per entry. Both read through?? 0so a response without the new fields renders0.00instead of throwing ontoFixed.api.ts:Client.billingRateis required, butWorkEntry.billable/amountare optional — the work-entries endpoints don't return them, only the report does.Everything goes through the existing TanStack Query
useQuery/useMutationcalls in the two pages; no new data layer.Stack: PR #932 (data model) → PR #933 (service/API) → this PR.
Testing
cd frontend && npm run lintandnpm run buildclean;cd backend && npm teststill 179 green (backend untouched here).frontend/package.jsonhas notest:coveragescript, so nothing to run there. Verified end to end in the browser — see the runtime-test comment below (that run predates thehourlyRate→billingRaterename; the flow was re-checked after it).Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/af51f484897545a8bc1df69511f47ed4
Open in Devin Desktop: https://partner-workshops.devinenterprise.com/desktop/session/af51f484897545a8bc1df69511f47ed4?variant=devin
Requested by: @sumitshatwara