feat(workforce): keep the hourly rate off the workers roster

The Rate / hr column put every worker's wage on a wide table that is
glanceable, projected and exported. The rate already lives on the worker
detail page, where it is a deliberate lookup.

Drop the column from the roster and the rate from its CSV export — the
export is the wider accidental leak, since that file gets emailed and
opened on shared machines. Site managers can still see a worker's rate by
opening the worker, which stays deliberate access.
This commit is contained in:
DEls 2026-10-26 01:10:08 +02:00
parent 34017d8630
commit 88a3a1e1be
3 changed files with 3 additions and 10 deletions

View file

@ -7,7 +7,7 @@ import { EmployeesView } from "./employees-view";
it("keeps worker assignments read-only until the edit sheet is opened", () => {
const html = renderToStaticMarkup(<EmployeesView rows={[{
id: "worker000000001", name: "Test worker", employeeNo: "01", workerType: "casual",
position: "Labourer", ratePerHour: 40, projectCode: "ONE", siteId: "site00000000001",
position: "Labourer", projectCode: "ONE", siteId: "site00000000001",
employerId: "broker000000001", employerName: "Labour broker",
}]} showSiteColumn={false} positions={[]} employers={[{ id: "broker000000001", name: "Labour broker", site: "site00000000001" }]} />);
expect(html).toContain("Labour broker");
@ -20,4 +20,6 @@ it("keeps worker assignments read-only until the edit sheet is opened", () => {
const roster = html.slice(html.indexOf("<tbody"), html.indexOf("</tbody>"));
expect(roster).not.toContain("<select");
expect(html).not.toContain("Save changes");
// The hourly rate lives on the worker detail page only — never in the roster.
expect(html).not.toContain("Rate / hr");
});

View file

@ -24,7 +24,6 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Pagination } from "@/components/ui/pagination";
import { formatCurrency } from "@/lib/utils";
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "@/components/ui/dropdown-menu";
import { AddWorkerDialog } from "@/components/add-worker-dialog";
import { WorkerQrBatchDialog } from "@/components/worker-qr-batch-dialog";
@ -47,7 +46,6 @@ export interface EmployeeRow {
weekActivity?: ActivityDay[];
position: string;
workerType: "permanent" | "casual";
ratePerHour: number;
projectCode: string;
}
@ -214,7 +212,6 @@ export function EmployeesView({
<TableHead>Position / type</TableHead>
{showSiteColumn && <TableHead>Project</TableHead>}
<TableHead>Employer/Broker</TableHead>
<TableHead className="text-right">Rate / hr</TableHead>
<TableHead className="w-10 px-1"><span className="sr-only">Worker actions</span></TableHead>
</TableRow>
</TableHeader>
@ -266,9 +263,6 @@ export function EmployeesView({
</TableCell>
)}
<TableCell className="text-sm">{r.employerName || "Unassigned"}</TableCell>
<TableCell className="text-right tabular-nums">
{formatCurrency(r.ratePerHour)}
</TableCell>
<TableCell className="w-10 px-1 text-right" onClick={event => event.stopPropagation()}>
<WorkerActions iconOnly worker={{ ...r, siteId: r.siteId ?? "" }} employers={employers}
history={assignments.filter(a => a.employee === r.id && a.site === r.siteId)} />
@ -348,7 +342,6 @@ function exportCsv(rows: EmployeeRow[]) {
"worker_type",
"position",
"project_code",
"rate_per_hour",
"employer_broker",
];
const lines = [headers.join(",")];
@ -360,7 +353,6 @@ function exportCsv(rows: EmployeeRow[]) {
r.workerType,
csvCell(r.position),
csvCell(r.projectCode),
r.ratePerHour.toFixed(2),
csvCell(r.employerName || "Unassigned"),
].join(","),
);

View file

@ -67,7 +67,6 @@ export default async function EmployeesPage({
duplicateBadge: (badgeCounts.get(String(e.employee_no||"").trim().toUpperCase())||0)>1,
position: e.position,
workerType: e.worker_type,
ratePerHour: e.rate_per_hour || 0,
projectCode: e.expand?.site?.project_code ?? siteById.get(e.site)?.project_code ?? "",
}));