feat(site-scope): scope the Plant pages, drop their project dropdowns
This commit is contained in:
parent
fe55a5d7df
commit
77ec2ec697
6 changed files with 156 additions and 114 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { pbAdmin } from "@/lib/pb";
|
||||
import { composeFilter, filterOpt, getSiteScope, siteFilter, type SearchParams } from "@/lib/site-scope";
|
||||
import {
|
||||
PlantView,
|
||||
type PlantRow,
|
||||
|
|
@ -9,7 +10,13 @@ import type { PlantSiteOption } from "@/components/add-plant-button";
|
|||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function PlantPage() {
|
||||
export default async function PlantPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<SearchParams>;
|
||||
}) {
|
||||
const scope = await getSiteScope(await searchParams);
|
||||
const siteF = siteFilter(scope);
|
||||
const pb = await pbAdmin();
|
||||
|
||||
// Plant bucket includes legacy empty-unit-type rows. The fuel page
|
||||
|
|
@ -18,16 +25,17 @@ export default async function PlantPage() {
|
|||
const [allVehicles, allFuelLogs, allSites] = await Promise.all([
|
||||
pb.collection("vehicles").getFullList({
|
||||
sort: "description",
|
||||
perPage: 5000,
|
||||
filter: `unit_type="plant" || unit_type="" || unit_type=null`,
|
||||
filter: composeFilter(siteF, `unit_type="plant" || unit_type="" || unit_type=null`),
|
||||
}),
|
||||
pb.collection("fuel_logs").getFullList({
|
||||
sort: "-date_logged",
|
||||
perPage: 5000,
|
||||
...filterOpt(siteF),
|
||||
}),
|
||||
// Sites is a lookup table for project_code display, not a data
|
||||
// read — left unfiltered so the Add-plant dialog can still show
|
||||
// every site even when this page is scoped.
|
||||
pb.collection("sites").getFullList({
|
||||
sort: "project_code",
|
||||
perPage: 5000,
|
||||
fields: "id,project_code,name",
|
||||
}),
|
||||
]);
|
||||
|
|
@ -145,7 +153,7 @@ export default async function PlantPage() {
|
|||
};
|
||||
});
|
||||
|
||||
const projectCodes = Array.from(new Set(rows.map((r) => r.projectCode))).sort();
|
||||
const projectCodes: string[] = []; // Project column visibility is controlled by showSiteColumn; the list is now derived server-side.
|
||||
|
||||
const sites: PlantSiteOption[] = allSites.map((s) => ({
|
||||
id: s.id,
|
||||
|
|
@ -179,6 +187,7 @@ export default async function PlantPage() {
|
|||
logs={logs}
|
||||
projectCodes={projectCodes}
|
||||
sites={sites}
|
||||
showSiteColumn={scope.mode === "all"}
|
||||
kpis={kpis}
|
||||
vehicleBasePath="/dashboard/plant/equipment"
|
||||
title="Equipment (L/h)"
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ export function PlantView({
|
|||
logs,
|
||||
projectCodes,
|
||||
sites,
|
||||
showSiteColumn,
|
||||
kpis,
|
||||
vehicleBasePath,
|
||||
title,
|
||||
|
|
@ -144,13 +145,13 @@ export function PlantView({
|
|||
logs: PlantLogRow[];
|
||||
projectCodes: string[];
|
||||
sites: PlantSiteOption[];
|
||||
showSiteColumn: boolean;
|
||||
kpis: PlantKpis;
|
||||
vehicleBasePath: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [projectFilter, setProjectFilter] = React.useState<string>("all");
|
||||
const [search, setSearch] = React.useState("");
|
||||
const [page, setPage] = React.useState(1);
|
||||
// Independent pagination for the bottom "Plant log entries" table.
|
||||
|
|
@ -184,15 +185,14 @@ export function PlantView({
|
|||
const q = search.trim().toLowerCase();
|
||||
return rows.filter((r) => {
|
||||
if (removedIds.has(r.id)) return false;
|
||||
if (projectFilter !== "all" && r.projectCode !== projectFilter) return false;
|
||||
if (q && !r.description.toLowerCase().includes(q)) return false;
|
||||
return true;
|
||||
});
|
||||
}, [rows, projectFilter, search, removedIds]);
|
||||
}, [rows, search, removedIds]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setPage(1);
|
||||
}, [projectFilter, search]);
|
||||
}, [search]);
|
||||
|
||||
const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
|
||||
const pageRows = React.useMemo(
|
||||
|
|
@ -249,7 +249,7 @@ export function PlantView({
|
|||
}
|
||||
}
|
||||
|
||||
const filtersActive = projectFilter !== "all" || search !== "";
|
||||
const filtersActive = search !== "";
|
||||
|
||||
// Inline-edit hook for the description column. The page-level toggle
|
||||
// controls this state via the EditModeToggle rendered in the Topbar.
|
||||
|
|
@ -392,31 +392,11 @@ export function PlantView({
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Filter className="h-3.5 w-3.5" /> Project
|
||||
</span>
|
||||
<Select value={projectFilter} onValueChange={setProjectFilter}>
|
||||
<SelectTrigger className="w-[160px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All projects</SelectItem>
|
||||
{projectCodes.map((c) => (
|
||||
<SelectItem key={c} value={c}>
|
||||
{c}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{filtersActive && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setProjectFilter("all");
|
||||
setSearch("");
|
||||
}}
|
||||
>
|
||||
|
|
@ -461,6 +441,7 @@ export function PlantView({
|
|||
}}
|
||||
onDelete={edit.editing ? () => void handleDelete(r) : null}
|
||||
deleting={deletingId === r.id}
|
||||
showSiteColumn={showSiteColumn}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -472,7 +453,7 @@ export function PlantView({
|
|||
<TableRow>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>QR code</TableHead>
|
||||
<TableHead>Project</TableHead>
|
||||
{showSiteColumn && <TableHead>Project</TableHead>}
|
||||
<TableHead className="text-right">Hours</TableHead>
|
||||
<TableHead className="text-right">Value</TableHead>
|
||||
<TableHead className="text-right">Logs</TableHead>
|
||||
|
|
@ -545,11 +526,13 @@ export function PlantView({
|
|||
<span className="text-xs text-muted-foreground">—</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{r.projectCode || "—"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
{showSiteColumn && (
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{r.projectCode || "—"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell className="text-right tabular-nums">
|
||||
{r.totalHours > 0
|
||||
? `${formatNumber(r.totalHours, { maximumFractionDigits: 1 })} h`
|
||||
|
|
@ -647,7 +630,7 @@ export function PlantView({
|
|||
<TableRow>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead>Vehicle</TableHead>
|
||||
<TableHead>Project</TableHead>
|
||||
{showSiteColumn && <TableHead>Project</TableHead>}
|
||||
<TableHead className="text-right">Hour meter</TableHead>
|
||||
<TableHead className="text-right">Hours</TableHead>
|
||||
<TableHead className="text-right">Fuel (L)</TableHead>
|
||||
|
|
@ -667,11 +650,13 @@ export function PlantView({
|
|||
<TableCell>
|
||||
<div className="font-medium">{l.vehicleDescription}</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{l.projectCode || "—"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
{showSiteColumn && (
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{l.projectCode || "—"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell className="text-right tabular-nums">
|
||||
{formatNumber(l.odoReading)}
|
||||
</TableCell>
|
||||
|
|
@ -834,6 +819,7 @@ function PlantMobileCard({
|
|||
edit,
|
||||
onDelete,
|
||||
deleting,
|
||||
showSiteColumn,
|
||||
}: {
|
||||
r: PlantRow;
|
||||
href: string;
|
||||
|
|
@ -847,6 +833,7 @@ function PlantMobileCard({
|
|||
};
|
||||
onDelete: (() => void) | null;
|
||||
deleting: boolean;
|
||||
showSiteColumn: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
|
|
@ -894,9 +881,11 @@ function PlantMobileCard({
|
|||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-1.5">
|
||||
<Badge variant="outline" className="font-mono text-[10px]">
|
||||
{r.projectCode || "—"}
|
||||
</Badge>
|
||||
{showSiteColumn && (
|
||||
<Badge variant="outline" className="font-mono text-[10px]">
|
||||
{r.projectCode || "—"}
|
||||
</Badge>
|
||||
)}
|
||||
{r.qrCodeId && (
|
||||
<span className="font-mono text-[10px]">QR {r.qrCodeId}</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,23 +3,38 @@ import { Topbar } from "@/components/layout/sidebar";
|
|||
import { ImportFuelLogsButton } from "@/components/import-fuel-logs-button";
|
||||
import { resolveFuelPrice } from "@/lib/metrics";
|
||||
import { loadFuelPrices } from "@/lib/fuel-prices";
|
||||
import { filterOpt, getSiteScope, siteFilter, siteFilterAllowingUnassigned, type SearchParams } from "@/lib/site-scope";
|
||||
import { FuelLogsView, type FuelLogRow } from "./fuel-logs-view";
|
||||
import { deriveProjectPeriodLabel } from "@/lib/project-calendar";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function FuelLogsPage() {
|
||||
export default async function FuelLogsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<SearchParams>;
|
||||
}) {
|
||||
const scope = await getSiteScope(await searchParams);
|
||||
const siteF = siteFilter(scope);
|
||||
const pb = await pbAdmin();
|
||||
|
||||
const [logs, vehicles, sites, documents] = await Promise.all([
|
||||
pb.collection("fuel_logs").getFullList({
|
||||
sort: "-date_logged",
|
||||
expand: "vehicle,fuel_sheet",
|
||||
...filterOpt(siteF),
|
||||
}),
|
||||
pb.collection("vehicles").getFullList(),
|
||||
// Sites is a lookup / Sites dropdown data — kept unfiltered.
|
||||
pb.collection("sites").getFullList({ sort: "name" }),
|
||||
pb.collection("fuel_sheets").getFullList({
|
||||
fields: "id,processing_status",
|
||||
...filterOpt(
|
||||
// A pump sheet legitimately spans sites and carries site="" by
|
||||
// design (src/lib/pb-types.ts:353-358). A plain siteFilter would
|
||||
// make every multi-site sheet vanish from upload status.
|
||||
siteFilterAllowingUnassigned(scope),
|
||||
),
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,19 +12,27 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { loadFuelPrices } from "@/lib/fuel-prices";
|
||||
import { resolveFuelPrice } from "@/lib/metrics";
|
||||
import { filterOpt, getSiteScope, composeFilter, siteFilter, type SearchParams, type SiteScope } from "@/lib/site-scope";
|
||||
import { formatCurrency, formatNumber } from "@/lib/utils";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
async function loadPlantSummary() {
|
||||
async function loadPlantSummary(scope: SiteScope) {
|
||||
const pb = await pbAdmin();
|
||||
const siteF = siteFilter(scope);
|
||||
const now = new Date();
|
||||
const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
|
||||
const monthStartIso = monthStart.toISOString().slice(0, 10);
|
||||
|
||||
const [allVehicles, allFuelLogs, fuelPrices] = await Promise.all([
|
||||
pb.collection("vehicles").getFullList({ sort: "description" }),
|
||||
pb.collection("fuel_logs").getFullList({ sort: "-date_logged" }),
|
||||
pb.collection("vehicles").getFullList({
|
||||
sort: "description",
|
||||
filter: composeFilter(siteF, `unit_type="fuel" || unit_type=null || unit_type=""`),
|
||||
}),
|
||||
pb.collection("fuel_logs").getFullList({
|
||||
sort: "-date_logged",
|
||||
...filterOpt(siteF),
|
||||
}),
|
||||
loadFuelPrices(),
|
||||
]);
|
||||
|
||||
|
|
@ -94,8 +102,13 @@ async function loadPlantSummary() {
|
|||
};
|
||||
}
|
||||
|
||||
export default async function PlantLanding() {
|
||||
const summary = await loadPlantSummary();
|
||||
export default async function PlantLanding({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<SearchParams>;
|
||||
}) {
|
||||
const scope = await getSiteScope(await searchParams);
|
||||
const summary = await loadPlantSummary(scope);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ const DISMISS_ERROR_COPY: Record<string, string> = {
|
|||
export function FuelView({
|
||||
rows,
|
||||
vehicles,
|
||||
projectCodes,
|
||||
showSiteColumn,
|
||||
kpis,
|
||||
vehicleBasePath,
|
||||
title,
|
||||
|
|
@ -203,7 +203,7 @@ export function FuelView({
|
|||
}: {
|
||||
rows: FuelRow[];
|
||||
vehicles: VehicleSummary[];
|
||||
projectCodes: string[];
|
||||
showSiteColumn: boolean;
|
||||
kpis: FuelKpis;
|
||||
vehicleBasePath: string;
|
||||
title: string;
|
||||
|
|
@ -214,7 +214,6 @@ export function FuelView({
|
|||
[vehicleBasePath],
|
||||
);
|
||||
const router = useRouter();
|
||||
const [projectFilter, setProjectFilter] = React.useState<string>("all");
|
||||
const [statusFilter, setStatusFilter] = React.useState<string>("all");
|
||||
const [search, setSearch] = React.useState("");
|
||||
const [page, setPage] = React.useState(1);
|
||||
|
|
@ -231,7 +230,6 @@ export function FuelView({
|
|||
const filtered = React.useMemo(() => {
|
||||
const q = search.trim().toLowerCase();
|
||||
return rows.filter((r) => {
|
||||
if (projectFilter !== "all" && r.projectCode !== projectFilter) return false;
|
||||
if (statusFilter !== "all") {
|
||||
if (statusFilter === "flagged" && r.status !== "warn" && r.status !== "alert") return false;
|
||||
if (statusFilter !== "flagged" && r.status !== statusFilter) return false;
|
||||
|
|
@ -239,17 +237,16 @@ export function FuelView({
|
|||
if (q && !r.vehicleDescription.toLowerCase().includes(q)) return false;
|
||||
return true;
|
||||
});
|
||||
}, [rows, projectFilter, statusFilter, search]);
|
||||
}, [rows, statusFilter, search]);
|
||||
|
||||
// Vehicles shown in the cards — same filters as the table apply
|
||||
const filteredVehicles = React.useMemo(() => {
|
||||
const q = search.trim().toLowerCase();
|
||||
return vehicles.filter((v) => {
|
||||
if (projectFilter !== "all" && v.projectCode !== projectFilter) return false;
|
||||
if (q && !v.description.toLowerCase().includes(q)) return false;
|
||||
return true;
|
||||
});
|
||||
}, [vehicles, projectFilter, search]);
|
||||
}, [vehicles, search]);
|
||||
|
||||
const vehiclesWithOverlay = React.useMemo(
|
||||
() =>
|
||||
|
|
@ -276,7 +273,7 @@ export function FuelView({
|
|||
React.useEffect(() => {
|
||||
setPage(1);
|
||||
setFleetPage(1);
|
||||
}, [projectFilter, statusFilter, search]);
|
||||
}, [statusFilter, search]);
|
||||
|
||||
const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
|
||||
const pageRows = React.useMemo(
|
||||
|
|
@ -297,8 +294,7 @@ export function FuelView({
|
|||
[filteredVehicles, fleetPage],
|
||||
);
|
||||
|
||||
const filtersActive =
|
||||
projectFilter !== "all" || statusFilter !== "all" || search !== "";
|
||||
const filtersActive = statusFilter !== "all" || search !== "";
|
||||
|
||||
// Inline-edit hook for vehicle descriptions. Toggle lives in the
|
||||
// Topbar's right slot (next to the Import button); banner shows
|
||||
|
|
@ -468,24 +464,7 @@ export function FuelView({
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
||||
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Filter className="h-3.5 w-3.5" /> Project
|
||||
</span>
|
||||
<Select value={projectFilter} onValueChange={setProjectFilter}>
|
||||
<SelectTrigger className="w-[160px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All projects</SelectItem>
|
||||
{projectCodes.map((c) => (
|
||||
<SelectItem key={c} value={c}>
|
||||
{c}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
||||
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
|
|
@ -512,7 +491,6 @@ export function FuelView({
|
|||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setProjectFilter("all");
|
||||
setStatusFilter("all");
|
||||
setSearch("");
|
||||
}}
|
||||
|
|
@ -565,6 +543,7 @@ export function FuelView({
|
|||
onDismiss={handleDismiss}
|
||||
busy={busyId === v.id}
|
||||
disabled={busyId !== null}
|
||||
showSiteColumn={showSiteColumn}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -579,6 +558,7 @@ export function FuelView({
|
|||
onDismiss={handleDismiss}
|
||||
busy={busyId === v.id}
|
||||
disabled={busyId !== null}
|
||||
showSiteColumn={showSiteColumn}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -622,6 +602,7 @@ export function FuelView({
|
|||
unitType: edit.valueOf(v.id, "unitType", v.unitType),
|
||||
onUnitTypeChange: (next) => edit.setValue(v.id, "unitType", next),
|
||||
}}
|
||||
showSiteColumn={showSiteColumn}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -633,7 +614,7 @@ export function FuelView({
|
|||
<TableRow>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>QR code</TableHead>
|
||||
<TableHead>Project</TableHead>
|
||||
{showSiteColumn && <TableHead>Project</TableHead>}
|
||||
<TableHead className="text-right">Litres</TableHead>
|
||||
<TableHead className="text-right">Spend</TableHead>
|
||||
<TableHead className="text-right">Txns</TableHead>
|
||||
|
|
@ -659,6 +640,7 @@ export function FuelView({
|
|||
edit.isDirty(v.id) &&
|
||||
edit.valueOf(v.id, "unitType", v.unitType) !== v.unitType,
|
||||
}}
|
||||
showSiteColumn={showSiteColumn}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
|
|
@ -702,7 +684,7 @@ export function FuelView({
|
|||
) : (
|
||||
pageRows.map((r) => (
|
||||
<li key={r.id}>
|
||||
<TransactionMobileCard r={r} href={vehicleHref(r.vehicleId)} />
|
||||
<TransactionMobileCard r={r} href={vehicleHref(r.vehicleId)} showSiteColumn={showSiteColumn} />
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
|
|
@ -713,7 +695,7 @@ export function FuelView({
|
|||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead>Project</TableHead>
|
||||
{showSiteColumn && <TableHead>Project</TableHead>}
|
||||
<TableHead>Vehicle</TableHead>
|
||||
<TableHead className="text-right">Odo / meter</TableHead>
|
||||
<TableHead className="text-right">Litres</TableHead>
|
||||
|
|
@ -743,11 +725,13 @@ export function FuelView({
|
|||
<TableCell className="text-xs tabular-nums">
|
||||
{r.date ? format(parseISO(r.date), "yyyy-MM-dd") : "—"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{r.projectCode || "—"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
{showSiteColumn && (
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{r.projectCode || "—"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>
|
||||
<Link
|
||||
href={vehicleHref(r.vehicleId)}
|
||||
|
|
@ -823,12 +807,14 @@ function AlertCard({
|
|||
onDismiss,
|
||||
busy,
|
||||
disabled,
|
||||
showSiteColumn,
|
||||
}: {
|
||||
v: VehicleSummary;
|
||||
href: string;
|
||||
onDismiss: (id: string) => void;
|
||||
busy: boolean;
|
||||
disabled: boolean;
|
||||
showSiteColumn: boolean;
|
||||
}) {
|
||||
const dominant = v.alert > 0 ? "alert" : "warn";
|
||||
const toneClasses =
|
||||
|
|
@ -860,9 +846,11 @@ function AlertCard({
|
|||
QR {v.qrCodeId}
|
||||
</p>
|
||||
)}
|
||||
<Badge variant="outline" className="mt-2 font-mono text-xs">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
{showSiteColumn && (
|
||||
<Badge variant="outline" className="mt-2 font-mono text-xs">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<ArrowRight
|
||||
className="mt-1 h-4 w-4 shrink-0 text-muted-foreground"
|
||||
|
|
@ -924,12 +912,14 @@ function AlertListRow({
|
|||
onDismiss,
|
||||
busy,
|
||||
disabled,
|
||||
showSiteColumn,
|
||||
}: {
|
||||
v: VehicleSummary;
|
||||
href: string;
|
||||
onDismiss: (id: string) => void;
|
||||
busy: boolean;
|
||||
disabled: boolean;
|
||||
showSiteColumn: boolean;
|
||||
}) {
|
||||
const dominant = v.alert > 0 ? "alert" : "warn";
|
||||
const stripeColor =
|
||||
|
|
@ -951,9 +941,11 @@ function AlertListRow({
|
|||
QR {v.qrCodeId}
|
||||
</span>
|
||||
)}
|
||||
<Badge variant="outline" className="shrink-0 font-mono text-[10px]">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
{showSiteColumn && (
|
||||
<Badge variant="outline" className="shrink-0 font-mono text-[10px]">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1.5">
|
||||
{v.alert > 0 && (
|
||||
|
|
@ -1010,6 +1002,7 @@ function FleetMobileCard({
|
|||
v,
|
||||
href,
|
||||
edit,
|
||||
showSiteColumn,
|
||||
}: {
|
||||
v: VehicleSummary;
|
||||
href: string;
|
||||
|
|
@ -1021,6 +1014,7 @@ function FleetMobileCard({
|
|||
unitType: "fuel" | "plant";
|
||||
onUnitTypeChange: (next: "fuel" | "plant") => void;
|
||||
};
|
||||
showSiteColumn: boolean;
|
||||
}) {
|
||||
return (
|
||||
edit.editing ? (
|
||||
|
|
@ -1049,9 +1043,11 @@ function FleetMobileCard({
|
|||
</div>
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<Badge variant="outline" className="font-mono text-[10px]">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
{showSiteColumn && (
|
||||
<Badge variant="outline" className="font-mono text-[10px]">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
)}
|
||||
{v.fuelLimits && (
|
||||
<span className="whitespace-nowrap font-medium tabular-nums" title={v.fuelLimits}>{v.fuelLimits}</span>
|
||||
)}
|
||||
|
|
@ -1098,9 +1094,11 @@ function FleetMobileCard({
|
|||
</div>
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<Badge variant="outline" className="font-mono text-[10px]">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
{showSiteColumn && (
|
||||
<Badge variant="outline" className="font-mono text-[10px]">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
)}
|
||||
<UnitTypePill value={v.unitType} />
|
||||
{v.fuelLimits && (
|
||||
<span className="whitespace-nowrap font-medium tabular-nums" title={v.fuelLimits}>{v.fuelLimits}</span>
|
||||
|
|
@ -1164,7 +1162,7 @@ function DescriptionInput({
|
|||
);
|
||||
}
|
||||
|
||||
function TransactionMobileCard({ r, href }: { r: FuelRow; href: string }) {
|
||||
function TransactionMobileCard({ r, href, showSiteColumn }: { r: FuelRow; href: string; showSiteColumn: boolean }) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
|
|
@ -1186,7 +1184,7 @@ function TransactionMobileCard({ r, href }: { r: FuelRow; href: string }) {
|
|||
<span className="tabular-nums">
|
||||
{r.date ? format(parseISO(r.date), "yyyy-MM-dd") : "—"}
|
||||
</span>
|
||||
{r.projectCode && (
|
||||
{showSiteColumn && r.projectCode && (
|
||||
<>
|
||||
<span aria-hidden>·</span>
|
||||
<Badge variant="outline" className="font-mono text-[10px]">
|
||||
|
|
@ -1224,6 +1222,7 @@ function FleetRow({
|
|||
v,
|
||||
href,
|
||||
edit,
|
||||
showSiteColumn,
|
||||
}: {
|
||||
v: VehicleSummary;
|
||||
href: string;
|
||||
|
|
@ -1236,6 +1235,7 @@ function FleetRow({
|
|||
onUnitTypeChange: (next: "fuel" | "plant") => void;
|
||||
dirtyUnitType: boolean;
|
||||
};
|
||||
showSiteColumn: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const handleNav = React.useCallback(() => router.push(href), [router, href]);
|
||||
|
|
@ -1286,11 +1286,13 @@ function FleetRow({
|
|||
<span className="text-xs text-muted-foreground">—</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
{showSiteColumn && (
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{v.projectCode || "—"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell className="text-right tabular-nums">
|
||||
{v.totalLitres > 0
|
||||
? `${formatNumber(v.totalLitres, { maximumFractionDigits: 1 })} L`
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import { pbAdmin } from "@/lib/pb";
|
||||
import { loadFuelPrices } from "@/lib/fuel-prices";
|
||||
import { parseFuelLimit } from "@/lib/fuel-variance";
|
||||
import {
|
||||
composeFilter,
|
||||
filterOpt,
|
||||
getSiteScope,
|
||||
siteFilter,
|
||||
type SearchParams,
|
||||
} from "@/lib/site-scope";
|
||||
import {
|
||||
computeFuelLogStatuses,
|
||||
resolveFuelPrice,
|
||||
|
|
@ -11,7 +18,13 @@ import { FuelView, type FuelRow, type VehicleSummary } from "./fuel-view";
|
|||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function FuelPage() {
|
||||
export default async function FuelPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<SearchParams>;
|
||||
}) {
|
||||
const scope = await getSiteScope(await searchParams);
|
||||
const siteF = siteFilter(scope);
|
||||
const pb = await pbAdmin();
|
||||
|
||||
const [vehicles, fuelLogs, fuelPrices, sites] = await Promise.all([
|
||||
|
|
@ -20,10 +33,13 @@ export default async function FuelPage() {
|
|||
// appear on the Fleet page where the user expects them)
|
||||
pb.collection("vehicles").getFullList({
|
||||
sort: "description",
|
||||
filter: `unit_type="fuel" || unit_type=null || unit_type=""`,
|
||||
filter: composeFilter(siteF, `unit_type="fuel" || unit_type=null || unit_type=""`),
|
||||
}),
|
||||
pb.collection("fuel_logs").getFullList({ expand: "vehicle" }),
|
||||
pb.collection("fuel_logs").getFullList({ expand: "vehicle", ...filterOpt(siteF) }),
|
||||
loadFuelPrices(),
|
||||
// Sites is a lookup table for project_code display, not a data
|
||||
// read — left unfiltered so project_code resolution still works
|
||||
// when this page is scoped to a single site.
|
||||
pb.collection("sites").getFullList({ fields: "id,project_code" }),
|
||||
]);
|
||||
|
||||
|
|
@ -164,13 +180,11 @@ export default async function FuelPage() {
|
|||
};
|
||||
});
|
||||
|
||||
const projectCodes = Array.from(new Set(rows.map((r) => r.projectCode))).sort();
|
||||
|
||||
return (
|
||||
<FuelView
|
||||
rows={rows}
|
||||
vehicles={vehicleSummaries}
|
||||
projectCodes={projectCodes}
|
||||
showSiteColumn={scope.mode === "all"}
|
||||
kpis={{
|
||||
vehicles: vehicles.length,
|
||||
totalLitres,
|
||||
|
|
|
|||
Loading…
Reference in a new issue