fix: audit fix pass #1 — all 19 verified HIGH findings + critical dep cleanup
Fixes every CRITICAL/HIGH finding from the 2026-06-09 full-codebase audit
(REVIEW_FINDINGS.md); each fix went through independent spec + code-quality
review. Plan and per-task log: docs/superpowers/plans/2026-06-10-audit-high-fix-pass.md
- attendance: schemas accept the combined local datetimes the forms/service
use (new dateTimeString helpers in schemas/common.ts), breaks persist on
create, AttendanceCreate submit rebuilt — every submit 400'd since 519edce
- 2fa: backup codes wired to /totp/backup-verify (+ remember-me parity),
enrollment QR generated locally via qrcode (CSP-blocked external service
also leaked the secret), dashboard shows per-user enrollment, not policy
- invoices/orders: per-line VAT survives re-saves (numberOr 0-respecting
coercion in formatters.ts), billing_text persists on update, issued-order
status transitions update UI gates
- trips: real pagination on all 3 pages, GET /trips/stats server aggregate
(shared buildTripsWhere + legacy distance coalesce), vehicle_id applies on
PUT with both-vehicle odometer recompute, print rebuilt (sync window.open,
escaped template, server totals)
- orders api: attachment_data PDF blob excluded from all non-binary reads
- warehouse: unit field is a Select over UnitEnum, receipt attachments
downloadable via new authenticated GET route
- downloads: shared RFC 5987 contentDisposition helper — Czech filenames no
longer 500 (warehouse, received-invoices, orders endpoints)
- misc: block-env hook actually blocks (exit 2 + stderr), project create
works with empty dates, NaN filter guards on trips endpoints
- deps: remove unused concurrently (clears both critical advisories), pin
@hono/node-server >=1.19.13 via overrides (clears the 3 moderates without
the Prisma 6 downgrade), drop deprecated @types stubs
Gates: tsc -b clean - vitest 30 files / 342 tests (31 new) - eslint 0 errors
- build OK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,9 +12,11 @@ import { formatKm, todayLocalStr } from "../utils/formatters";
|
||||
import apiFetch from "../utils/api";
|
||||
import {
|
||||
tripListOptions,
|
||||
tripStatsOptions,
|
||||
tripVehiclesOptions,
|
||||
type BackendTrip,
|
||||
} from "../lib/queries/trips";
|
||||
import { usePaginatedQuery } from "../hooks/usePaginatedQuery";
|
||||
import { useApiMutation } from "../lib/queries/mutations";
|
||||
import {
|
||||
Button,
|
||||
@@ -158,12 +160,20 @@ export default function Trips() {
|
||||
const alert = useAlert();
|
||||
const { hasPermission } = useAuth();
|
||||
|
||||
const { data: tripsData, isPending: tripsLoading } = useQuery(
|
||||
tripListOptions({}),
|
||||
);
|
||||
const { data: vehiclesData } = useQuery(tripVehiclesOptions());
|
||||
// "Poslední jízdy" widget — only the 10 newest rows are shown, so fetch
|
||||
// exactly those; the stat cards come from the server-side stats endpoint
|
||||
// (computed over the whole filtered set, not one page).
|
||||
const { items: trips, isPending: tripsLoading } =
|
||||
usePaginatedQuery<BackendTrip>(tripListOptions({ perPage: 10 }));
|
||||
|
||||
const trips = tripsData ?? [];
|
||||
// Stat cards are a personal "this month" summary — the header subtitle
|
||||
// shows the current month, so the stats query is scoped to match it.
|
||||
const now = new Date();
|
||||
const { data: stats } = useQuery(
|
||||
tripStatsOptions({ month: now.getMonth() + 1, year: now.getFullYear() }),
|
||||
);
|
||||
|
||||
const { data: vehiclesData } = useQuery(tripVehiclesOptions());
|
||||
const vehicles = vehiclesData ?? [];
|
||||
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
@@ -321,19 +331,14 @@ export default function Trips() {
|
||||
return <LoadingState />;
|
||||
}
|
||||
|
||||
const totals = trips.reduce(
|
||||
(acc, t) => {
|
||||
const dist = t.distance ?? t.end_km - t.start_km;
|
||||
acc.count++;
|
||||
acc.total += dist;
|
||||
if (t.is_business) acc.business += dist;
|
||||
else acc.private += dist;
|
||||
return acc;
|
||||
},
|
||||
{ total: 0, business: 0, private: 0, count: 0 },
|
||||
);
|
||||
const totals = {
|
||||
count: stats?.count ?? 0,
|
||||
total: stats?.total_km ?? 0,
|
||||
business: stats?.business_km ?? 0,
|
||||
private: stats?.private_km ?? 0,
|
||||
};
|
||||
|
||||
const recentTrips = trips.slice(0, 10);
|
||||
const recentTrips = trips;
|
||||
|
||||
const columns: DataColumn<BackendTrip>[] = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user