fix: type error and other fixes

This commit is contained in:
Nathnael
2026-06-23 13:14:06 +00:00
parent cdfe7c1f5b
commit cbbad02b29
18 changed files with 116 additions and 179 deletions

View File

@@ -282,6 +282,7 @@ export class CompaniesService {
async findCompanyById(id: string): Promise<Company> {
const company = await this.companiesRepo.findById(id);
if (!company) throw new NotFoundException(`Company ${id} not found`);
company.companyProfiles = await this.companyProfilesRepo.findByCompanyId(id);
return company;
}

View File

@@ -2152,6 +2152,7 @@ export class TrainSchedulingService {
weightTons: roundTons(Number(sb.booking?.cargoTotalWeightVgm ?? 0)),
status: sb.booking?.status ?? null,
schedulingStatus: sb.booking?.schedulingStatus ?? null,
freightType: sb.booking?.freightType ?? null,
})) ?? [],
};
}

View File

@@ -138,6 +138,8 @@ export interface BookingDetailView {
priorityScore: number;
cargoTotalWeightVgm: number;
pnrCode?: string | null;
consolidationPartnerId?: string | null;
consolidationPartner?: (BookingNamedRefView & { reference?: string }) | null;
/** End of the pay window once the booking is SELECTED_FOR_BATCH. */
paymentDeadline?: string | null;
createdAt: string;

View File

@@ -11,7 +11,7 @@ import {
import type { TrainScheduleDetail } from "@/types/trainScheduling";
import { freightBrand } from "@/theme/freight-brand";
type Wagon = TrainScheduleDetail["trainSet"]["wagons"][number];
type Wagon = NonNullable<TrainScheduleDetail["trainSet"]>["wagons"][number];
export interface BookingDetailData {
bookingId: string;

View File

@@ -11,7 +11,7 @@ import {
import type { TrainScheduleDetail } from "@/types/trainScheduling";
import { freightBrand } from "@/theme/freight-brand";
type Wagon = TrainScheduleDetail["trainSet"]["wagons"][number];
type Wagon = NonNullable<TrainScheduleDetail["trainSet"]>["wagons"][number];
type Locomotive = NonNullable<TrainScheduleDetail["trainSet"]>["locomotive"];
interface InteractiveTrainConsistProps {

View File

@@ -1,7 +1,7 @@
import { Button, Group, Modal, Stack, Text, Badge } from "@mantine/core";
import type { TrainScheduleDetail } from "@/types/trainScheduling";
type WagonWithAllocation = TrainScheduleDetail["trainSet"]["wagons"][number];
type WagonWithAllocation = NonNullable<TrainScheduleDetail["trainSet"]>["wagons"][number];
interface RemoveBookingModalProps {
opened: boolean;
@@ -21,7 +21,6 @@ export const RemoveBookingModal = ({
if (!wagon || !wagon.allocations?.[0]) return null;
const allocation = wagon.allocations[0];
const booking = allocation.booking;
return (
<Modal opened={opened} onClose={onClose} title="Confirm Booking Removal" centered>
@@ -32,12 +31,12 @@ export const RemoveBookingModal = ({
</Text>
<Stack gap={4}>
<Text size="sm">
<strong>Reference:</strong> {booking?.reference || "N/A"}
<strong>Reference:</strong> {allocation.bookingReference || "N/A"}
</Text>
<Text size="sm">
<strong>Freight Type:</strong>{" "}
<Badge size="sm" variant="light">
{booking?.freightType || "N/A"}
{allocation.loadType || "N/A"}
</Badge>
</Text>
<Text size="sm">

View File

@@ -10,7 +10,7 @@ import { useMutation } from "@tanstack/react-query";
import { api } from "@/services/api";
import { freightBrand } from "@/theme/freight-brand";
type Wagon = TrainScheduleDetail["trainSet"]["wagons"][number];
type Wagon = NonNullable<TrainScheduleDetail["trainSet"]>["wagons"][number];
interface TrainConsistViewProps {
scheduleDetail: TrainScheduleDetail;
@@ -103,9 +103,9 @@ export const TrainConsistView = ({
<Stack gap="md" style={{ width: "100%" }}>
<TrainStatsBar
weightUsed={weightUsed}
weightMax={scheduleDetail.locomotive?.maxWeightTons ?? trainSet?.locomotive?.maxPullWeightTons ?? null}
weightMax={trainSet?.locomotive?.maxPullWeightTons ?? null}
lengthUsed={lengthUsed}
lengthMax={scheduleDetail.locomotive?.maxLengthMeters ?? trainSet?.locomotive?.maxTrainLengthMeters ?? null}
lengthMax={trainSet?.locomotive?.maxTrainLengthMeters ?? null}
wagonCount={wagons.length}
wagonMax={maxWagons}
/>

View File

@@ -12,7 +12,7 @@ import type { TrainScheduleDetail } from "@/types/trainScheduling";
import { ContainerNumberInput } from "./ContainerNumberInput";
import { freightBrand } from "@/theme/freight-brand";
type Wagon = TrainScheduleDetail["trainSet"]["wagons"][number];
type Wagon = NonNullable<TrainScheduleDetail["trainSet"]>["wagons"][number];
interface WagonCardProps {
wagon: Wagon;

View File

@@ -1,11 +1,12 @@
import { useMemo } from "react";
import { DataTable, type ColumnDef } from "@edr/ui-common";
import { ActionIcon, Badge, Button, Group, Text, Tooltip } from "@mantine/core";
import { ArrowRightLeft, ClipboardList, Coins, History } from "lucide-react";
import { DataTable, type ColumnDef } from "@edr/ui-common";
import { useMemo } from "react";
import type {
InventoryAction,
WarehouseInventoryItem,
import {
INVENTORY_NEXT_ACTION,
type InventoryAction,
type WarehouseInventoryItem,
} from "@/types/warehouse";
import { InventoryStatusBadge } from "./badges";
import { formatDate, formatNumber, humanizeEnum } from "./options";
@@ -54,12 +55,6 @@ export function WarehouseInventoryTable({
onHistory,
onInspect,
onFeePreview,
onLastMile,
selectedIds,
onToggleSelect,
onToggleSelectAll,
allSelected,
someSelected,
}: WarehouseInventoryTableProps) {
const columns = useMemo<ColumnDef<WarehouseInventoryItem>[]>(
() => [

View File

@@ -662,7 +662,7 @@ export default function NewBookingPage() {
placeholder="Select bulk cargo type"
data={cargoData}
value={cargoTypeId}
onChange={setCargoTypeId}
onChange={(value) => setCargoTypeId(value as string | null)}
searchable
disabled={isLoading}
/>

View File

@@ -93,7 +93,7 @@ const OverviewPage = () => {
const getTabBadge = (tab: (typeof TAB_ITEMS)[number]) => {
if (!summary?.kpis) return 0;
const group = summary.kpis[tab.kpiKey] as Record<string, number>;
const group = summary.kpis[tab.kpiKey] as unknown as Record<string, number>;
return group[tab.metricKey] ?? 0;
};

View File

@@ -1,49 +1,49 @@
import { FormEvent, ReactNode, useMemo, useState } from 'react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { Edit, Eye, Plus, Search, Trash2 } from 'lucide-react';
import { FormEvent, ReactNode, useMemo, useState } from 'react';
import { api } from '@/services/api';
import {
ActionIcon,
Badge as MantineBadge,
Box,
Button as MantineButton,
Group,
Modal,
NumberInput,
Pagination,
Paper,
ScrollArea,
Select as MantineSelect,
SimpleGrid,
Stack,
Table as MantineTable,
Text,
TextInput,
Title,
ActionIcon,
Box,
Group,
Badge as MantineBadge,
Button as MantineButton,
Select as MantineSelect,
Table as MantineTable,
Modal,
NumberInput,
Pagination,
Paper,
ScrollArea,
SimpleGrid,
Stack,
Text,
TextInput,
Title,
} from '@mantine/core';
import { DeliverCargoDialog } from '@/components/cargoes/DeliverCargoDialog';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@edr/ui-common';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { useToast } from '@/hooks/use-toast';
import type { Cargo } from '@/services/cargoService';
import { DeliverCargoDialog } from '@/components/cargoes/DeliverCargoDialog';
import type { Container } from '@/services/containerService';
import type { Locomotive } from '@/services/locomotives.service';
import type { Train } from '@/services/trains.service';
import type { Wagon } from '@/services/wagon.service';
import type { WagonType } from '@/services/wagon-types.service';
import type { Wagon } from '@/services/wagon.service';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@edr/ui-common';
type FormValue = string | number | boolean | string[];
@@ -331,7 +331,7 @@ function FleetCrudPage<T extends { id: string }>({
<TableRow key={item.id}>
{columns.map((column) => (
<TableCell key={String(column.key)}>
{column.render ? column.render(item) : String((item as Record<string, unknown>)[column.key] ?? '-')}
{column.render ? column.render(item) : String((item as Record<string, unknown>)[String(column.key)] ?? '-')}
</TableCell>
))}
<TableCell>
@@ -410,7 +410,7 @@ function FleetCrudPage<T extends { id: string }>({
...current,
[field.key]: selectedValue,
...(field.onValueChange?.(selectedValue, current) ?? {}),
}))
}) as Record<string, FormValue>)
}
>
<SelectTrigger id={field.key}>
@@ -480,12 +480,6 @@ function FleetCrudPage<T extends { id: string }>({
const statusBadge = (status?: string) => <Badge variant="outline">{status ?? '-'}</Badge>;
const activeBadge = (isActive?: boolean) => (
<Badge variant={isActive === false ? 'secondary' : 'outline'}>
{isActive === false ? 'Inactive' : 'Active'}
</Badge>
);
const optionLabel = (options: { value: string; label: string }[], value?: string | null) =>
options.find((option) => option.value === value)?.label ?? value ?? '-';

View File

@@ -1,19 +1,10 @@
import type { ColumnDef } from "@edr/ui-common";
import { Box, Button, Card, Group, Modal, Select, Stack, Text } from "@mantine/core";
import { Box, Button, Card, Container, Group, Modal, Select, Stack, Text, Title } from "@mantine/core";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import {
Archive,
Circle,
CircleCheck,
CircleSlash,
Layers,
Link2,
Plus,
Wrench,
type LucideIcon,
} from "lucide-react";
import Breadcrumbs from "@/components/ui/Breadcrumbs";
import { Plus } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { Navigate, useLocation } from "react-router-dom";
@@ -23,37 +14,20 @@ import FleetRecordActions from "@/components/fleet/FleetRecordActions";
import FleetToolbar from "@/components/fleet/FleetToolbar";
import { formatFleetCell, registerFleetOptionLabels } from "@/components/fleet/fleetFormat";
import { useFleetViewMode } from "@/components/fleet/useFleetViewMode";
import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import { useToast } from "@/hooks/use-toast";
import {
FLEET_SELECT_NONE,
getFleetResource,
getFleetSlugFromPath,
type FleetFormFieldDef,
type FleetResourceSlug,
FLEET_SELECT_NONE,
getFleetResource,
getFleetSlugFromPath,
type FleetFormFieldDef,
type FleetResourceSlug,
} from "@/pages/fleet/config/resources";
import type { FleetListFilters, FleetRecord } from "@/services/fleet/fleet.service";
import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common";
const DEFAULT_SLUG: FleetResourceSlug = "locomotives";
const FLEET_STATUS_META: Record<
string,
{ label: string; icon: LucideIcon; color: string }
> = {
AVAILABLE: { label: "Available", icon: CircleCheck, color: "edr-green" },
ASSIGNED: { label: "Assigned", icon: Link2, color: "blue" },
MAINTENANCE: { label: "Maintenance", icon: Wrench, color: "yellow" },
OUT_OF_SERVICE: { label: "Out of service", icon: CircleSlash, color: "red" },
RETIRED: { label: "Retired", icon: Archive, color: "gray" },
};
const humanizeStatus = (status: string) => {
const text = status.replace(/_/g, " ").toLowerCase();
return text.charAt(0).toUpperCase() + text.slice(1);
};
const FleetResourcePage = () => {
const location = useLocation();
const slug = getFleetSlugFromPath(location.pathname) ?? DEFAULT_SLUG;
@@ -113,6 +87,9 @@ const FleetResourcePage = () => {
const { data: yards = [], isLoading: yardsLoading } = useQuery(
api.routes.yards.queryOptions(),
);
const { data: drivers = [] } = useQuery(
api.fleet.list.queryOptions({ input: { slug: "drivers" } }),
);
useEffect(() => {
setPagination((prev) => ({ pageIndex: 0, pageSize: prev.pageSize }));
@@ -301,39 +278,6 @@ const FleetResourcePage = () => {
const tableStatus = isLoading ? "loading" : isError ? "error" : "success";
const kpiItems = useMemo(() => {
const items = [
{
label: `Total ${config?.label.toLowerCase() ?? ""}`,
value: allRows.length,
icon: Layers,
color: "edr-green",
},
];
if (hasStatusColumn) {
const counts = new Map<string, number>();
for (const row of allRows) {
const status = String(
(row as unknown as Record<string, unknown>).status ?? "",
);
if (status) counts.set(status, (counts.get(status) ?? 0) + 1);
}
const top = [...counts.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 4);
for (const [status, count] of top) {
const meta = FLEET_STATUS_META[status];
items.push({
label: meta?.label ?? humanizeStatus(status),
value: count,
icon: meta?.icon ?? Circle,
color: meta?.color ?? "gray",
});
}
}
return items.slice(0, 5);
}, [allRows, hasStatusColumn, config?.label]);
if (!config) {
return <Navigate to="/dashboard/locomotives" replace />;
}
@@ -376,7 +320,7 @@ const FleetResourcePage = () => {
const handleAssignDriver = async () => {
if (!assigningDriver || !("id" in assigningDriver) || !selectedDriver) return;
try {
const selectedDriverRecord = (drivers as Array<Record<string, unknown>>).find(
const selectedDriverRecord = (drivers as unknown as Array<Record<string, unknown>>).find(
(d) => String(d.id) === selectedDriver
);
if (!selectedDriverRecord) return;
@@ -384,6 +328,7 @@ const FleetResourcePage = () => {
const driverName = `${selectedDriverRecord.firstName} ${selectedDriverRecord.lastName}`;
await update.mutateAsync({
slug,
id: String(assigningDriver.id),
data: {
assignedDriverId: selectedDriver,
@@ -608,7 +553,7 @@ const FleetResourcePage = () => {
clearable
value={selectedDriver}
onChange={(value) => setSelectedDriver(value || "")}
data={(drivers as Array<Record<string, unknown>>).map((driver) => ({
data={(drivers as unknown as Array<Record<string, unknown>>).map((driver) => ({
value: String(driver.id || ""),
label: `${driver.firstName} ${driver.lastName} (${driver.licenseNumber})`,
}))}

View File

@@ -1,58 +1,56 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { Navigate, useLocation, useParams } from "react-router-dom";
import { useAuth } from "@/auth/useAuth";
import { canAccessRuleEngineResource } from "@/lib/permissions";
import type { ColumnDef } from "@edr/ui-common";
import {
Box,
Card,
Button,
Modal,
Stack,
Group,
Text,
List,
Loader,
Box,
Button,
Card,
Group,
List,
Loader,
Modal,
Stack,
Text,
} from "@mantine/core";
import { Plus } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Navigate, useLocation, useParams } from "react-router-dom";
import { PageContainer, PageHeader } from "@/components/page";
import ManageRuleEngineOrderDialog from "@/components/ruleEngine/ManageRuleEngineOrderDialog";
import RuleEngineCardGrid from "@/components/ruleEngine/RuleEngineCardGrid";
import RuleEngineFormDialog from "@/components/ruleEngine/RuleEngineFormDialog";
import ManageRuleEngineOrderDialog from "@/components/ruleEngine/ManageRuleEngineOrderDialog";
import RuleEngineOrderControls from "@/components/ruleEngine/RuleEngineOrderControls";
import RuleEngineRecordActions from "@/components/ruleEngine/RuleEngineRecordActions";
import { getOrderItemLabel } from "@/components/ruleEngine/ruleEngineOrder.utils";
import RuleEngineToolbar from "@/components/ruleEngine/RuleEngineToolbar";
import { formatCell } from "@/components/ruleEngine/ruleEngineFormat";
import { getOrderItemLabel } from "@/components/ruleEngine/ruleEngineOrder.utils";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import { useRuleEngineViewMode } from "@/components/ruleEngine/useRuleEngineViewMode";
import {
DEFAULT_CONFIGURATION_SLUG,
DEFAULT_RULES_SLUG,
RULE_ENGINE_CATEGORY_BASE_PATH,
RULE_ENGINE_SELECT_NONE,
getRuleEngineResource,
type RuleEngineNavCategory,
} from "@/pages/ruleEngine/config/resources";
import {
useApprovalChain,
useCargoTypeParentOptions,
useContainerTypeOptions,
useLiveRateOptions,
useRateWorkflow,
useRuleEngineList,
useRuleEngineMutations,
useRuleEngineOrderList,
useRuleEngineOrderMutations,
useApprovalChain,
useCargoTypeParentOptions,
useContainerTypeOptions,
useLiveRateOptions,
useRateWorkflow,
useRuleEngineList,
useRuleEngineMutations,
useRuleEngineOrderList,
useRuleEngineOrderMutations,
} from "@/hooks/rule-engine/useRuleEngine";
import {
DEFAULT_CONFIGURATION_SLUG,
DEFAULT_RULES_SLUG,
RULE_ENGINE_CATEGORY_BASE_PATH,
RULE_ENGINE_SELECT_NONE,
getRuleEngineResource,
type RuleEngineNavCategory,
} from "@/pages/ruleEngine/config/resources";
import type { RuleEngineRecord } from "@/types/rule-engine";
import {
DataTable,
DataTableFooter,
getCoreRowModel,
usePagination,
useReactTable,
DataTable,
DataTableFooter,
usePagination,
} from "@edr/ui-common";
const pathCategory = (pathname: string): RuleEngineNavCategory | undefined => {

View File

@@ -24,6 +24,7 @@ function mapCompany(dto: Record<string, unknown>): Company {
const attrs = (dto.attributes as Record<string, unknown> | null) ?? {};
return {
...(dto as unknown as Company),
companyProfiles: (dto.companyProfiles as Company["companyProfiles"]) ?? [],
contactPersonName: (attrs.contactPersonName as string | null) ?? null,
contactPersonPhone: (attrs.contactPersonPhone as string | null) ?? null,
generalManagerName: (attrs.generalManagerName as string | null) ?? null,

View File

@@ -375,6 +375,7 @@ export interface TrainScheduleDetail {
weightTons: number;
status: string | null;
schedulingStatus?: SchedulingStatus | null;
freightType?: FreightType | string | null;
}>;
warnings?: string[];
}

View File

@@ -7,7 +7,7 @@ import {
export interface OperationsQueueGroups {
government: BookingListRow[];
commercial: ThreeHourBookingBucket[];
commercial: ThreeHourBookingBucket<BookingListRow>[];
}
export function groupBookingsForOperationsQueue(

View File

@@ -1,40 +1,40 @@
import { URL_CONSTANTS } from "@/constants/URLS";
import type {
AuthUser,
GenerateVerificationCodePayload,
LoginPayload,
LoginResponse,
OtpPayload,
OtpResponse,
SetPasswordPayload,
SignupPayload,
SignupResponse,
} from "@/types/auth";
import { client } from "@/utils/api";
import { ApiResponse } from "@edr/types";
import type {
AuthUser,
GenerateVerificationCodePayload,
LoginPayload,
LoginResponse,
OtpPayload,
OtpResponse,
SetPasswordPayload,
SignupPayload,
SignupResponse,
} from "@/types/auth";
export const authService = {
login: async (body: LoginPayload) => {
const res = await client.post<ApiResponse<LoginResponse>>(
const res = await client.post<LoginResponse>(
URL_CONSTANTS.AUTH.LOGIN,
body,
);
return res.data.data;
return res.data;
},
createUser: async (body: SignupPayload) => {
const res = await client.post<ApiResponse<SignupResponse>>(
const res = await client.post<SignupResponse & ApiResponse<void>> (
URL_CONSTANTS.USERS.SIGN_UP,
body,
);
return res.data.data;
return res.data;
},
getMyInfo: async () => {
const res = await client.get<ApiResponse<AuthUser>>(
const res = await client.get<AuthUser>(
URL_CONSTANTS.USERS.ME,
);
return res.data.data;
return res.data;
},
generateVerificationCode: async (body: GenerateVerificationCodePayload) => {