enhance service filtering and dashboard functionality with company profile support

This commit is contained in:
Marshal
2026-06-23 07:54:36 +00:00
parent fd1fc1ca69
commit 1d77f377f3
19 changed files with 206 additions and 235 deletions

View File

@@ -148,15 +148,10 @@ export class BookingsController {
},
};
}
// Scope to the active operational profile (importer/exporter) when one
// resolves; otherwise fall back to company-level scoping.
const companyProfileId =
await this.bookingsService.resolveActiveCompanyProfileId(userId);
return this.bookingsService.findAll(
filter,
companyId,
companyProfileId ?? undefined,
);
// Company-wide by default; the optional filter.companyProfileId (per-page
// service filter) narrows within the company. The company guard always
// applies, so a customer can only ever see their own company's bookings.
return this.bookingsService.findAll(filter, companyId);
}
@Get('by-company/:companyId/customer-view')

View File

@@ -656,10 +656,11 @@ export class BookingsService {
assignedToSchedule: filter.assignedToSchedule,
// A forced company scope (portal/customer) overrides any caller-provided
// companyId so a customer can only ever see their own company's bookings.
// When an active profile resolves, scope to it; otherwise fall back to the
// company so nothing breaks for not-yet-onboarded customers.
companyId: forceCompanyProfileId ? undefined : forceCompanyId ?? filter.companyId,
companyProfileId: forceCompanyProfileId,
// The company guard always applies; the optional companyProfileId filter
// (from the per-page service filter) narrows WITHIN the company — the repo
// ANDs both, so cross-company access is impossible.
companyId: forceCompanyId ?? filter.companyId,
companyProfileId: forceCompanyProfileId ?? filter.companyProfileId,
contractType: filter.contractType,
serviceTypeId: filter.serviceTypeId,
cargoTypeId: filter.cargoTypeId,
@@ -694,18 +695,15 @@ export class BookingsService {
filter: FilterBookingDto,
): Promise<PaginatedBookings> {
const { company } = await this.companiesService.getCompanyInfoByUserId(userId);
// Scope to the active operational profile when one resolves; fall back to
// company-level so not-yet-onboarded customers still see their payables.
const companyProfileId =
await this.companiesService.resolveActiveCompanyProfileId(userId);
return this.bookingsRepository.findAllPaginated({
page: filter.page ?? 1,
pageSize: filter.pageSize ?? 20,
statuses: BookingsService.PAYABLE_STATUSES,
excludePaymentStatus: 'PAID',
companyId: companyProfileId ? undefined : company.id,
companyProfileId: companyProfileId ?? undefined,
// Company-wide: payables span all of the customer's services.
companyId: company.id,
companyProfileId: filter.companyProfileId,
sortBy: filter.sortBy,
sortOrder: filter.sortOrder,
});

View File

@@ -38,6 +38,15 @@ export class FilterBookingDto {
@IsUUID()
companyId?: string;
@ApiPropertyOptional({
format: 'uuid',
description:
'Narrow to a single operational profile (importer/exporter/freight_forwarder) within the company.',
})
@IsOptional()
@IsUUID()
companyProfileId?: string;
@ApiPropertyOptional()
@IsOptional()
contractType?: string;

View File

@@ -28,6 +28,7 @@ import { CreateCompanyProfileDto } from "./dto/create-company-profile.dto";
import { SetActiveModeDto } from "./dto/set-active-mode.dto";
import { SetOnboardingStepDto } from "./dto/set-onboarding-step.dto";
import { StartOnboardingDto } from "./dto/start-onboarding.dto";
import { DashboardQueryDto } from "./dto/dashboard-query.dto";
import {
ResponseCompanyDto,
ResponseCompanyProfileDto,
@@ -86,8 +87,12 @@ export class CompaniesController {
})
async getDashboard(
@CurrentUser() user: CurrentIamUser,
@Query() query: DashboardQueryDto,
): Promise<DashboardSummaryResponseDto> {
return this.companiesService.getDashboardSummary(user.id);
return this.companiesService.getDashboardSummary(
user.id,
query.companyProfileId,
);
}
@Post("fetch-etrade-info")

View File

@@ -7,7 +7,10 @@ import {
import { CompaniesRepository } from "./companies.repository";
import { CompanyProfileRepository } from "./company-profile.repository";
import { ExternalProfileRepository } from "./external-profile.repository";
import { CompanyDashboardRepository } from "./company-dashboard.repository";
import {
CompanyDashboardRepository,
DashboardScope,
} from "./company-dashboard.repository";
import { MinioService } from "../minio/minio.service";
import { ETradeService } from "./services/etrade.service";
import { normalizeE164 } from "../../common/validators/is-phone-number.validator";
@@ -320,6 +323,7 @@ export class CompaniesService {
*/
async getDashboardSummary(
userId: string,
companyProfileId?: string,
): Promise<DashboardSummaryResponseDto> {
// A user without a company profile has no bookings — return an empty summary
// rather than 404, so the portal home still renders.
@@ -327,17 +331,17 @@ export class CompaniesService {
const companyId = profile?.company?.id ?? profile?.companyId ?? null;
if (!companyId) return this.emptyDashboardSummary();
// Scope KPIs to the active operational profile (importer/exporter mode) when
// one resolves; otherwise aggregate across the whole company.
const companyProfileId = profile?.activeProfileType
? ((await this.companyProfilesRepo.findByType(
companyId,
profile.activeProfileType,
)) ?? null)
: null;
const scope = companyProfileId
? { companyProfileId: companyProfileId.id }
: { companyId };
// Company-wide by default (all services' data). An optional companyProfileId
// (from the per-page service filter) narrows to one operational profile —
// but only after we confirm it belongs to this user's company, since the
// dashboard scope has no company guard at the repository layer.
let scope: DashboardScope = { companyId };
if (companyProfileId) {
const owned = await this.companyProfilesRepo.findByCompanyId(companyId);
if (owned.some((p) => p.id === companyProfileId)) {
scope = { companyProfileId };
}
}
const now = new Date();
const yearStart = new Date(now.getFullYear(), 0, 1);

View File

@@ -0,0 +1,13 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import { IsOptional, IsUUID } from "class-validator";
export class DashboardQueryDto {
@ApiPropertyOptional({
format: "uuid",
description:
"Narrow dashboard KPIs to a single operational profile (importer/exporter/freight_forwarder) of the user's company. Omit for company-wide totals.",
})
@IsOptional()
@IsUUID()
companyProfileId?: string;
}

View File

@@ -228,14 +228,7 @@ const sidebarItems: SidebarItem[] = [
const App = () => {
const navigate = useNavigate();
const location = useLocation();
const {
user,
company,
activeProfileType,
companyType,
switchMode,
createProfileAndSwitch,
} = useAuth();
const { user, company, companyType, createProfileAndSwitch } = useAuth();
const displayName = user?.name?.en || user?.username || user?.email || "User";
const userEmail = user?.email;
@@ -278,8 +271,6 @@ const App = () => {
userEmail={userEmail}
companyProfiles={companyProfiles}
companyType={companyType}
activeProfileType={activeProfileType}
onSwitchMode={switchMode}
onCreateProfile={createProfileAndSwitch}
>
<OnboardingGate />

View File

@@ -19,9 +19,7 @@ import {
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import {
ArrowLeftRight,
Bell,
Check,
ChevronDown,
FileSignature,
LogOut,
@@ -61,13 +59,9 @@ export interface AppLayoutProps {
userEmail?: string;
/** Operational profiles for the company — surfaced as reference chips in the account menu. */
companyProfiles?: { type: string; reference: string; status?: string }[];
/** Company type (e.g. "customer", "forwarder") — gates the importer/exporter switch. */
/** Company type (e.g. "customer", "forwarder") — gates the "Add service" control. */
companyType?: string | null;
/** The active operational mode (importer/exporter/...). */
activeProfileType?: string | null;
/** Switch to an existing profile of the given type. */
onSwitchMode?: (type: ServiceType) => Promise<SwitchResult> | void;
/** Create the profile of the given type (with business license) then switch. */
/** Create a new service profile of the given type (with business license). */
onCreateProfile?: (
type: ServiceType,
licenseFiles: File[],
@@ -146,8 +140,6 @@ export function AppLayout({
userEmail,
companyProfiles = [],
companyType,
activeProfileType,
onSwitchMode,
onCreateProfile,
children,
}: AppLayoutProps) {
@@ -174,16 +166,16 @@ export function AppLayout({
const initials = getInitials(userName);
const activePage = getActivePage(sidebarItems, activePath);
// ── Service selection (customer companies only) ──
// A customer can operate as importer, exporter and/or freight forwarder,
// and switch between whichever service profiles their company has.
// ── Add a service (customer companies only) ──
// A customer can operate as importer, exporter and/or freight forwarder. The
// header lets them ADD a service they don't have yet (creating a profile with
// its business license). Data is no longer scoped by an "active" service —
// every page shows all the company's data, with an optional per-page filter.
const isCustomer = companyType === "customer";
const canSwitch =
isCustomer &&
CUSTOMER_SERVICES.includes(activeProfileType as ServiceType);
const profileExists = (type: ServiceType) =>
companyProfiles.some((p) => p.type === type);
const addableServices = CUSTOMER_SERVICES.filter((t) => !profileExists(t));
const canAddService = isCustomer && addableServices.length > 0;
const [switching, setSwitching] = useState(false);
const [createOpen, setCreateOpen] = useState(false);
@@ -191,22 +183,12 @@ export function AppLayout({
const [licenseFiles, setLicenseFiles] = useState<File[]>([]);
const [createError, setCreateError] = useState<string | null>(null);
const handleSelectService = async (type: ServiceType) => {
if (type === activeProfileType) return;
if (profileExists(type)) {
setSwitching(true);
try {
await onSwitchMode?.(type);
} finally {
setSwitching(false);
}
} else {
// No profile yet — collect a business license, then create + switch.
setCreateTarget(type);
setLicenseFiles([]);
setCreateError(null);
setCreateOpen(true);
}
const handleAddService = (type: ServiceType) => {
// Collect a business license, then create the profile.
setCreateTarget(type);
setLicenseFiles([]);
setCreateError(null);
setCreateOpen(true);
};
const handleCreateConfirm = async () => {
@@ -302,8 +284,8 @@ export function AppLayout({
{/* Right: switch + search + bell + avatar */}
<Group gap={10} wrap="nowrap" align="center">
{/* Service selector (customer companies only) */}
{canSwitch && (
{/* Add a service (customer companies that don't yet have all three) */}
{canAddService && (
<Menu
width={220}
position="bottom-end"
@@ -319,43 +301,25 @@ export function AppLayout({
color="edr-green"
radius={999}
size="sm"
leftSection={<ArrowLeftRight size={15} strokeWidth={1.8} />}
leftSection={<Plus size={15} strokeWidth={1.8} />}
rightSection={<ChevronDown size={14} strokeWidth={1.8} />}
styles={{ root: { height: 36 } }}
visibleFrom="xs"
>
{serviceLabel(activeProfileType as ServiceType)}
Add service
</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Select service</Menu.Label>
{CUSTOMER_SERVICES.map((type) => {
const isActive = type === activeProfileType;
const exists = profileExists(type);
return (
<Menu.Item
key={type}
onClick={() => handleSelectService(type)}
leftSection={
isActive ? (
<Check size={15} strokeWidth={2} />
) : exists ? (
<ArrowLeftRight size={15} strokeWidth={1.8} />
) : (
<Plus size={15} strokeWidth={1.8} />
)
}
disabled={isActive}
>
{serviceLabel(type)}
{!exists && (
<Text span size="xs" c="dimmed" ml={6}>
(set up)
</Text>
)}
</Menu.Item>
);
})}
<Menu.Label>Add a service</Menu.Label>
{addableServices.map((type) => (
<Menu.Item
key={type}
onClick={() => handleAddService(type)}
leftSection={<Plus size={15} strokeWidth={1.8} />}
>
{serviceLabel(type)}
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
)}
@@ -468,39 +432,25 @@ export function AppLayout({
<Divider />
<Box px="sm" py="xs">
<Stack gap={6}>
{companyProfiles.map((p) => {
const isActive = p.type === activeProfileType;
return (
<Group
key={p.reference}
justify="space-between"
gap="sm"
wrap="nowrap"
{companyProfiles.map((p) => (
<Group
key={p.reference}
justify="space-between"
gap="sm"
wrap="nowrap"
>
<Text
size="xs"
fw={600}
style={{ color: textColor }}
>
<Group gap={6} wrap="nowrap">
{isActive && (
<Check
size={13}
color={primaryDarkColor}
strokeWidth={2.5}
/>
)}
<Text
size="xs"
fw={isActive ? 700 : 600}
style={{
color: isActive ? primaryDarkColor : textColor,
}}
>
{PROFILE_TYPE_LABELS[p.type] ?? p.type}
</Text>
</Group>
<Text size="xs" ff="monospace" c="dimmed">
{p.reference}
</Text>
</Group>
);
})}
{PROFILE_TYPE_LABELS[p.type] ?? p.type}
</Text>
<Text size="xs" ff="monospace" c="dimmed">
{p.reference}
</Text>
</Group>
))}
</Stack>
</Box>
</>

View File

@@ -1,55 +0,0 @@
import { Badge, Tooltip } from "@mantine/core";
import { ArrowDownToLine, ArrowUpFromLine } from "lucide-react";
import useAuth from "@/hooks/useAuth";
import { modeDataDescription, modeDataLabel } from "@/constants/profileMode";
interface ModeIndicatorProps {
/** Mantine size token for the badge. */
size?: "sm" | "md" | "lg";
}
/**
* Small pill showing which operational mode's data is currently on screen
* (Import / Export). The data itself is scoped server-side by the active
* profile; this just makes the scope visible. Switching is done via the header
* button — this is read-only.
*
* Renders nothing for non-customer companies or when no import/export mode is
* active, so it never interferes with forwarders or not-yet-onboarded users.
*/
export function ModeIndicator({ size = "md" }: ModeIndicatorProps) {
const { companyType, activeProfileType } = useAuth();
if (companyType !== "customer") return null;
const label = modeDataLabel(activeProfileType);
if (!label) return null;
const isImport = activeProfileType === "importer";
return (
<Tooltip label={modeDataDescription(activeProfileType)} withArrow>
<Badge
size={size}
radius="sm"
variant="light"
color={isImport ? "edr-green" : "blue"}
leftSection={
isImport ? (
<ArrowDownToLine size={13} />
) : (
<ArrowUpFromLine size={13} />
)
}
styles={{
root: { textTransform: "none", letterSpacing: 0, fontWeight: 600 },
}}
>
Viewing: {label}
</Badge>
</Tooltip>
);
}
export default ModeIndicator;

View File

@@ -1,6 +1,6 @@
/**
* Operational-mode (importer/exporter/…) labels and helpers, shared by the app
* header and the per-page mode indicator so there is a single source of truth.
* Operational-service (importer/exporter/…) display labels, shared by the app
* header and the per-page service filters so there is a single source of truth.
*/
export const PROFILE_TYPE_LABELS: Record<string, string> = {
@@ -10,21 +10,3 @@ export const PROFILE_TYPE_LABELS: Record<string, string> = {
dj_freight_forwarder: "DJ Freight Forwarder",
transporter: "Transporter",
};
/** The data-scope label shown to the user (importer ⇒ "Import", exporter ⇒ "Export"). */
export function modeDataLabel(
activeProfileType?: string | null,
): string | null {
if (activeProfileType === "importer") return "Import";
if (activeProfileType === "exporter") return "Export";
return null;
}
/** Short helper sentence describing what the active mode scopes. */
export function modeDataDescription(
activeProfileType?: string | null,
): string {
const label = modeDataLabel(activeProfileType);
if (!label) return "";
return `Showing your ${label.toLowerCase()} data — switch in the header.`;
}

View File

@@ -1,7 +1,9 @@
import type { Currency } from "@/pages/billing/invoices.mock";
import { formatCurrency } from "@/pages/billing/invoices.mock";
import { Grid, Stack } from "@mantine/core";
import { Group, Grid, Select, Stack } from "@mantine/core";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { PROFILE_TYPE_LABELS } from "@/constants/profileMode";
import {
FreightVolumeSection,
HelloSection,
@@ -15,8 +17,12 @@ import { useMyPortalData } from "./hooks";
export default function MyPortalPage() {
const navigate = useNavigate();
const [selectedProfileId, setSelectedProfileId] = useState<string | null>(
null,
);
const {
customer,
companyProfiles,
bookingsQuery,
dashboardQuery,
allBookings,
@@ -30,7 +36,12 @@ export default function MyPortalPage() {
dashboard,
volumePoints,
maxVolume,
} = useMyPortalData();
} = useMyPortalData(selectedProfileId ?? undefined);
const serviceOptions = companyProfiles.map((p) => ({
value: p.id,
label: `${PROFILE_TYPE_LABELS[p.type] ?? p.type} · ${p.reference}`,
}));
const handleBookingClick = (id: string) => {
navigate(`/bookings/${id}`);
@@ -40,6 +51,22 @@ export default function MyPortalPage() {
<Stack gap="lg" p={{ base: 16, sm: 24, lg: 32 }}>
<HelloSection greeting={greeting} companyName={companyName} />
{serviceOptions.length > 1 && (
<Group justify="flex-end">
<Select
placeholder="All services"
data={serviceOptions}
value={selectedProfileId}
onChange={setSelectedProfileId}
clearable
radius="md"
comboboxProps={{ withinPortal: true }}
style={{ width: 220 }}
aria-label="Filter dashboard by service"
/>
</Group>
)}
<SetupPrompt show={!customer} />
<StatsSection

View File

@@ -2,7 +2,6 @@ import { Box, Group, Text } from "@mantine/core";
import { ArrowRight, Truck } from "lucide-react";
import { memo } from "react";
import { Link } from "react-router-dom";
import { ModeIndicator } from "@/components/ModeIndicator";
import { cv } from "../constants";
interface HelloSectionProps {
@@ -24,7 +23,6 @@ export const HelloSection = memo(function HelloSection({
<Text fz={26} fw={800} c="edr-text" className="tracking-tight">
{companyName} 👋
</Text>
<ModeIndicator />
</Group>
</Box>

View File

@@ -5,17 +5,25 @@ import { getMyInvoices } from "@/lib/currentCustomer";
import { api } from "@/services/api";
import { ACTIVE_STATUSES } from "./constants";
export function useMyPortalData() {
const { user, customer } = useAuth();
export function useMyPortalData(selectedProfileId?: string) {
const { user, customer, company } = useAuth();
const myInvoices = useMemo(() => getMyInvoices(), []);
const companyProfiles = company?.company?.companyProfiles ?? [];
const bookingsQuery = useQuery(
api.bookings.list.queryOptions({
input: { sortBy: "createdAt", sortOrder: "DESC" },
input: {
sortBy: "createdAt",
sortOrder: "DESC",
companyProfileId: selectedProfileId,
},
}),
);
const dashboardQuery = useQuery(api.companies.getDashboard.queryOptions());
const dashboardQuery = useQuery(
api.companies.getDashboard.queryOptions({ input: selectedProfileId }),
);
const allBookings = bookingsQuery.data?.items ?? [];
const activeBookings = allBookings.filter((b) =>
@@ -56,6 +64,7 @@ export function useMyPortalData() {
return {
user,
customer,
companyProfiles,
bookingsQuery,
dashboardQuery,
allBookings,

View File

@@ -34,7 +34,8 @@ import {
import { ShipmentTrackingModal } from "./tracking/ShipmentTrackingModal";
import { PayNowButton } from "./payments/PayNowButton";
import { ModeIndicator } from "@/components/ModeIndicator";
import useAuth from "@/hooks/useAuth";
import { PROFILE_TYPE_LABELS } from "@/constants/profileMode";
import {
BookingTypeBadge,
CargoModeCell,
@@ -256,10 +257,13 @@ function fmtDate(iso?: string | null): string {
// ── Main component ────────────────────────────────────────────────────────────
// Lightweight count query for a single lifecycle filter (reads only `total`).
function useStatusCount(statuses: string | undefined): number | undefined {
function useStatusCount(
statuses: string | undefined,
companyProfileId?: string,
): number | undefined {
const { data } = useQuery(
api.bookings.list.queryOptions({
input: { statuses, page: 1, pageSize: 1 },
input: { statuses, companyProfileId, page: 1, pageSize: 1 },
staleTime: 30_000,
}),
);
@@ -335,8 +339,18 @@ export default function MyBookings() {
const [query, setQuery] = useState("");
const [typeFilter, setTypeFilter] = useState<string | null>(null);
const [freightFilter, setFreightFilter] = useState<string | null>(null);
const [serviceFilter, setServiceFilter] = useState<string | null>(null);
const [createdFrom, setCreatedFrom] = useState<string>("");
const [createdTo, setCreatedTo] = useState<string>("");
// Operational-service options (importer / exporter / freight forwarder) for
// the per-page filter. Empty for non-customer companies.
const { company } = useAuth();
const companyProfiles = company?.company?.companyProfiles ?? [];
const serviceOptions = companyProfiles.map((p) => ({
value: p.id,
label: `${PROFILE_TYPE_LABELS[p.type] ?? p.type} · ${p.reference}`,
}));
const [trackingBooking, setTrackingBooking] = useState<Freight.IBooking | null>(
null,
);
@@ -352,10 +366,15 @@ export default function MyBookings() {
};
const hasExtraFilters =
!!typeFilter || !!freightFilter || !!createdFrom || !!createdTo;
!!typeFilter ||
!!freightFilter ||
!!serviceFilter ||
!!createdFrom ||
!!createdTo;
const clearExtraFilters = () => {
setTypeFilter(null);
setFreightFilter(null);
setServiceFilter(null);
setCreatedFrom("");
setCreatedTo("");
resetPage();
@@ -366,6 +385,7 @@ export default function MyBookings() {
statuses,
bookingType: typeFilter ?? undefined,
freightType: freightFilter ?? undefined,
companyProfileId: serviceFilter ?? undefined,
createdFrom: createdFrom || undefined,
// include the whole selected end day
createdTo: createdTo ? `${createdTo}T23:59:59.999Z` : undefined,
@@ -376,6 +396,7 @@ export default function MyBookings() {
statuses,
typeFilter,
freightFilter,
serviceFilter,
createdFrom,
createdTo,
pagination.pageIndex,
@@ -387,25 +408,33 @@ export default function MyBookings() {
api.bookings.list.queryOptions({ input: filter }),
);
// Per-card lifecycle counts (one cheap query each, total-only).
const allCount = useStatusCount(undefined);
// Per-card lifecycle counts (one cheap query each, total-only). Scoped to the
// selected service so the cards match the filtered table.
const svc = serviceFilter ?? undefined;
const allCount = useStatusCount(undefined, svc);
const activeCount = useStatusCount(
STATUS_FILTERS.find((f) => f.key === "active")!.statuses,
svc,
);
const paymentCount = useStatusCount(
STATUS_FILTERS.find((f) => f.key === "payment")!.statuses,
svc,
);
const draftCount = useStatusCount(
STATUS_FILTERS.find((f) => f.key === "draft")!.statuses,
svc,
);
const doneCount = useStatusCount(
STATUS_FILTERS.find((f) => f.key === "done")!.statuses,
svc,
);
const transitCount = useStatusCount(
STATUS_FILTERS.find((f) => f.key === "transit")!.statuses,
svc,
);
const closedCount = useStatusCount(
STATUS_FILTERS.find((f) => f.key === "closed")!.statuses,
svc,
);
const cardCounts: Record<StatusFilterKey, number | undefined> = {
all: allCount,
@@ -617,7 +646,6 @@ export default function MyBookings() {
<Title order={1} fw={800} fz={26} style={{ letterSpacing: "-0.01em" }}>
Bookings
</Title>
<ModeIndicator />
</Group>
<Text size="sm" c="edr-muted" mt={4}>
Track every cargo booking from draft to delivery.
@@ -723,6 +751,22 @@ export default function MyBookings() {
style={{ width: 150 }}
aria-label="Filter by cargo type"
/>
{serviceOptions.length > 1 && (
<Select
placeholder="All services"
data={serviceOptions}
value={serviceFilter}
onChange={(v) => {
setServiceFilter(v);
resetPage();
}}
clearable
radius="md"
comboboxProps={{ withinPortal: true }}
style={{ width: 200 }}
aria-label="Filter by service"
/>
)}
<TextInput
type="date"
value={createdFrom}

View File

@@ -25,7 +25,6 @@ import {
} from "lucide-react";
import { api } from "@/services/api";
import { ModeIndicator } from "@/components/ModeIndicator";
import { PayNowButton } from "../bookings/payments/PayNowButton";
import {
BORDER,
@@ -121,7 +120,6 @@ export default function ContractDetailPage() {
{contract.reference}
</Title>
<ContractStatusBadge status={contract.status} />
<ModeIndicator size="sm" />
</Group>
<Text size="sm" c="dimmed" mt={2}>
General contract · {isContainer ? "Containerised" : "Bulk"}

View File

@@ -25,7 +25,6 @@ import {
type ColumnDef,
usePagination,
} from "@edr/ui-common";
import { ModeIndicator } from "@/components/ModeIndicator";
import { CargoModeCell, PaymentBadge } from "../bookings/booking-display";
import { ContractStatusBadge, GREEN, INK, MUTED } from "./contract-ui";
@@ -177,7 +176,6 @@ export default function ContractsList() {
<Title order={1} fw={800} fz={26} style={{ letterSpacing: "-0.01em" }}>
General Contracts
</Title>
<ModeIndicator />
</Group>
<Text size="sm" c="edr-muted" mt={4}>
Reserve a quantity once, then place orders against it until the

View File

@@ -128,7 +128,7 @@ export const api = {
companiesService.updateProfile,
),
getDashboard: endpoint<void, DashboardSummary>(
getDashboard: endpoint<string | void, DashboardSummary>(
"companies",
"getDashboard",
companiesService.getDashboard,

View File

@@ -75,6 +75,8 @@ export interface BookingListFilter {
freightType?: string;
/** IMPORT / EXPORT / DOMESTIC. */
tradeDirection?: string;
/** Narrow to a single operational profile (importer/exporter/freight_forwarder). */
companyProfileId?: string;
/** Created-date range (ISO). */
createdFrom?: string;
createdTo?: string;

View File

@@ -162,9 +162,12 @@ export const companiesService = {
return unwrap(response.data);
},
getDashboard: async (): Promise<DashboardSummary> => {
getDashboard: async (
companyProfileId?: string,
): Promise<DashboardSummary> => {
const response = await client.get<ApiResponse<DashboardSummary>>(
URL_CONSTANTS.COMPANIES_API.DASHBOARD,
{ params: companyProfileId ? { companyProfileId } : undefined },
);
return unwrap(response.data);
},