refactor: migrate from most of the custom hooks into the api.ts

This commit is contained in:
Nathnael
2026-06-22 13:46:38 +00:00
parent c6bc636495
commit 2700a3edec
50 changed files with 1165 additions and 1249 deletions

View File

@@ -1,6 +1,9 @@
import { useState } from "react";
import { FileSignature, Loader2 } from "lucide-react";
import { useMutation, useQuery } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { api } from "@/services/api";
import { ContractSignaturePad } from "@/components/bookings/ContractSignaturePad";
import {
Card,
@@ -10,10 +13,6 @@ import {
CardTitle,
} from "@/components/ui/card";
import { useAuth } from "@/auth/useAuth";
import {
useMySignature,
useSaveSignature,
} from "@/hooks/useSavedSignature";
import {
Button,
Dialog,
@@ -33,8 +32,10 @@ import {
*/
export function MySignatureCard() {
const { user } = useAuth();
const { data: saved, isLoading } = useMySignature();
const saveMutation = useSaveSignature();
const { data: saved, isLoading } = useQuery(
api.signatures.mySignature.queryOptions({ staleTime: 60_000 }),
);
const saveMutation = useMutation(api.signatures.save.mutationOptions());
const [open, setOpen] = useState(false);
const [signerName, setSignerName] = useState("");
@@ -56,7 +57,13 @@ export function MySignatureCard() {
signerDisplayName: signerName.trim(),
signatureImageBase64: signatureData,
},
{ onSuccess: () => setOpen(false) },
{
onSuccess: () => {
toast.success("Signature saved");
setOpen(false);
},
onError: () => toast.error("Failed to save signature"),
},
);
};

View File

@@ -31,13 +31,8 @@ import {
Weight,
} from "lucide-react";
import {
useAvailableLocomotives,
useEligibleBookings,
useScheduleList,
useScheduleMutations,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { useRoutes } from "@/hooks/useRoutes";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import { trainSchedulingService } from "@/services/trainScheduling.service";
import type { BookingDetail } from "@/types/booking";
@@ -133,13 +128,27 @@ export function AllocateBookingWizard({
[originId, destinationId],
);
const eligibleQuery = useEligibleBookings(eligibleFilters, opened);
const schedulesQuery = useScheduleList();
const routesQuery = useRoutes();
const locomotivesQuery = useAvailableLocomotives(
scheduleMode === "new" && routeId ? routeId : undefined,
const eligibleQuery = useQuery(
api.trainScheduling.eligibleBookings.queryOptions({
input: { filters: eligibleFilters },
enabled: opened,
}),
);
const { create, preview, assign, finalize } = useScheduleMutations(selectedScheduleId ?? undefined);
const schedulesQuery = useQuery(
api.trainScheduling.scheduleList.queryOptions({ input: {} }),
);
const routesQuery = useQuery(api.routes.list.queryOptions());
const locomotivesQuery = useQuery(
api.trainScheduling.availableLocomotives.queryOptions({
input: {
routeId: scheduleMode === "new" && routeId ? routeId : undefined,
},
}),
);
const create = useMutation(api.trainScheduling.createSchedule.mutationOptions());
const preview = useMutation(api.trainScheduling.preview.mutationOptions());
const assign = useMutation(api.trainScheduling.assignBookings.mutationOptions());
const finalize = useMutation(api.trainScheduling.finalizeSchedule.mutationOptions());
useEffect(() => {
if (scheduleMode === "new") {

View File

@@ -13,11 +13,10 @@ import {
} from "@mantine/core";
import { CheckCircle2, Layers, Lock, LockOpen, PlayCircle, Repeat, XCircle } from "lucide-react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
import {
useBatchActions,
useBookableSchedules,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import type { TrainScheduleDetail } from "@/types/trainScheduling";
@@ -33,16 +32,31 @@ const windowColor: Record<string, string> = {
export function ScheduleBatchPanel({ schedule }: ScheduleBatchPanelProps) {
const { toast } = useToast();
const actions = useBatchActions(schedule.id);
const actions = {
runBatch: useMutation(api.trainScheduling.runBatch.mutationOptions()),
setWindow: useMutation(api.trainScheduling.setBookingWindow.mutationOptions()),
markPaid: useMutation(api.trainScheduling.markBookingPaid.mutationOptions()),
expire: useMutation(api.trainScheduling.expireBooking.mutationOptions()),
moveSchedule: useMutation(
api.trainScheduling.moveBookingSchedule.mutationOptions(),
),
};
const windowStatus = (schedule as { bookingWindowStatus?: string }).bookingWindowStatus ?? "OPEN";
const locked = schedule.status === "DISPATCHED" || schedule.status === "ARRIVED";
const [moveBookingId, setMoveBookingId] = useState<string | null>(null);
const [moveTarget, setMoveTarget] = useState<string | null>(null);
const { data: targets } = useBookableSchedules(
schedule.originStation?.id,
schedule.destinationStation?.id,
const { data: targets } = useQuery(
api.trainScheduling.bookableSchedules.queryOptions({
input: {
originYardId: schedule.originStation?.id,
destinationYardId: schedule.destinationStation?.id,
},
enabled: Boolean(
schedule.originStation?.id && schedule.destinationStation?.id,
),
}),
);
const moveOptions = useMemo(
() =>

View File

@@ -4,7 +4,8 @@ import { Building2, Package, TrainFront, Weight, X } from "lucide-react";
import type { TrainScheduleDetail } from "@/types/trainScheduling";
import type { BookingDetailData } from "./BookingDetailModal";
import { RemoveBookingConfirmModal, type RemovalTarget } from "./RemoveBookingConfirmModal";
import { useScheduleMutations } from "@/hooks/trainScheduling/useTrainScheduling";
import { useMutation } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import { freightBrand } from "@/theme/freight-brand";
@@ -22,7 +23,7 @@ export const AssignedBookingsPanel = ({
onSelect,
}: AssignedBookingsPanelProps) => {
const { toast } = useToast();
const unassign = useScheduleMutations(scheduleId).unassign;
const unassign = useMutation(api.trainScheduling.unassignBooking.mutationOptions());
const isDispatched = scheduleDetail.status === "DISPATCHED";
const [removalTarget, setRemovalTarget] = useState<RemovalTarget | null>(null);

View File

@@ -8,10 +8,8 @@ import { UnassignedBookingsPanel } from "./UnassignedBookingsPanel";
import { RemovalLogPanel } from "./RemovalLogPanel";
import { BatchBookingList } from "./BatchBookingList";
import { BookingDetailModal, type BookingDetailData } from "./BookingDetailModal";
import {
useCompositionRemovals,
useUnassignedBookings,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { freightBrand } from "@/theme/freight-brand";
interface CompositionBookingTabsProps {
@@ -47,8 +45,18 @@ export const CompositionBookingTabs = ({
const [detailBooking, setDetailBooking] = useState<BookingDetailData | null>(null);
const [tab, setTab] = useState<TabKey>("assigned");
const unassignedQuery = useUnassignedBookings(scheduleId);
const removalsQuery = useCompositionRemovals(scheduleId);
const unassignedQuery = useQuery(
api.trainScheduling.unassignedBookings.queryOptions({
input: { scheduleId },
enabled: Boolean(scheduleId),
}),
);
const removalsQuery = useQuery(
api.trainScheduling.compositionRemovals.queryOptions({
input: { scheduleId },
enabled: Boolean(scheduleId),
}),
);
const { assignedCount } = useMemo(() => {
const wagons = scheduleDetail.trainSet?.wagons ?? [];

View File

@@ -1,6 +1,7 @@
import { useState } from "react";
import { Group, TextInput, Text } from "@mantine/core";
import { useUpdateContainerItem } from "@/hooks/trainScheduling/useTrainScheduling";
import { useMutation } from "@tanstack/react-query";
import { api } from "@/services/api";
interface ContainerNumberInputProps {
value: string | null;
@@ -19,13 +20,16 @@ export const ContainerNumberInput = ({
const [inputValue, setInputValue] = useState(value ?? "");
const [error, setError] = useState<string | null>(null);
const updateMutation = useUpdateContainerItem(scheduleId);
const updateMutation = useMutation(
api.trainScheduling.updateContainerItem.mutationOptions(),
);
const isLoading = updateMutation.isPending;
const handleSave = async () => {
try {
setError(null);
await updateMutation.mutateAsync({
scheduleId,
itemId,
containerNumber: inputValue || null,
});

View File

@@ -1,13 +1,19 @@
import { Box, Card, Group, Stack, Text, ThemeIcon } from "@mantine/core";
import { History, PackageX } from "lucide-react";
import { useCompositionRemovals } from "@/hooks/trainScheduling/useTrainScheduling";
import { useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
interface RemovalLogPanelProps {
scheduleId: string;
}
export const RemovalLogPanel = ({ scheduleId }: RemovalLogPanelProps) => {
const removalQuery = useCompositionRemovals(scheduleId);
const removalQuery = useQuery(
api.trainScheduling.compositionRemovals.queryOptions({
input: { scheduleId },
enabled: Boolean(scheduleId),
}),
);
if (removalQuery.isLoading) {
return (

View File

@@ -6,7 +6,8 @@ import { TrainStatsBar } from "./TrainStatsBar";
import { WagonCard } from "./WagonCard";
import { InteractiveTrainConsist } from "./InteractiveTrainConsist";
import { RemoveBookingModal } from "./RemoveBookingModal";
import { useScheduleMutations, useRemoveWagonSlot } from "@/hooks/trainScheduling/useTrainScheduling";
import { useMutation } from "@tanstack/react-query";
import { api } from "@/services/api";
import { freightBrand } from "@/theme/freight-brand";
type Wagon = TrainScheduleDetail["trainSet"]["wagons"][number];
@@ -47,8 +48,12 @@ export const TrainConsistView = ({
const [selectedWagonId, setSelectedWagonId] = useState<string | null>(null);
const [removeModalOpen, setRemoveModalOpen] = useState(false);
const unassignMutation = useScheduleMutations(scheduleId).unassign;
const removeWagonMutation = useRemoveWagonSlot(scheduleId);
const unassignMutation = useMutation(
api.trainScheduling.unassignBooking.mutationOptions(),
);
const removeWagonMutation = useMutation(
api.trainScheduling.removeWagonSlot.mutationOptions(),
);
const trainSet = scheduleDetail.trainSet;
const wagons = trainSet?.wagons ?? [];
@@ -83,7 +88,7 @@ export const TrainConsistView = ({
const handleRemoveWagon = async (wagonId: string) => {
if (confirm("Are you sure you want to remove this wagon slot?")) {
await removeWagonMutation.mutateAsync(wagonId);
await removeWagonMutation.mutateAsync({ scheduleId, wagonId });
setSelectedWagonId(null);
}
};

View File

@@ -1,9 +1,7 @@
import { Badge, Box, Button, Card, Group, Stack, Text, ThemeIcon, Tooltip } from "@mantine/core";
import { AlertTriangle, Container as ContainerIcon, MapPin, Plus, TrainFront } from "lucide-react";
import {
useUnassignedBookings,
useScheduleMutations,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import type { FleetAvailabilityRow } from "@/types/trainScheduling";
import type { BookingDetailData } from "./BookingDetailModal";
@@ -73,8 +71,15 @@ export const UnassignedBookingsPanel = ({
onSelect,
}: UnassignedBookingsPanelProps) => {
const { toast } = useToast();
const unassignedQuery = useUnassignedBookings(scheduleId);
const assignMutation = useScheduleMutations(scheduleId).assignUnassigned;
const unassignedQuery = useQuery(
api.trainScheduling.unassignedBookings.queryOptions({
input: { scheduleId },
enabled: Boolean(scheduleId),
}),
);
const assignMutation = useMutation(
api.trainScheduling.assignUnassignedBooking.mutationOptions(),
);
const handleAssign = async (bookingId: string, reference: string | null) => {
try {

View File

@@ -4,17 +4,18 @@ import { Button, Group, Modal, NumberInput, Select, Stack, Text } from "@mantine
import { Freight } from "@edr/types";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import { useRouteYards } from "@/hooks/useRoutes";
import { useAssignWagonToTrain, useWagons } from "@/hooks/useWagons";
export function AssignWagonDialog({ trainId }: { trainId: string }) {
const [open, setOpen] = useState(false);
const [wagonId, setWagonId] = useState<string | null>(null);
const [sequence, setSequence] = useState<number | "">("");
const { data: wagons } = useWagons();
const { data: yards = [] } = useRouteYards();
const assign = useAssignWagonToTrain();
const { data: wagons } = useQuery(api.wagons.list.queryOptions({ input: {} }));
const { data: yards = [] } = useQuery(api.routes.yards.queryOptions());
const assign = useMutation(api.wagons.assignToTrain.mutationOptions());
const { toast } = useToast();
const available = (wagons ?? []).filter(

View File

@@ -3,15 +3,22 @@ import { Trash2 } from "lucide-react";
import type { ColumnDef } from "@edr/ui-common";
import { ActionIcon, Badge, Group, Text, Tooltip } from "@mantine/core";
import { useMutation, useQuery } from "@tanstack/react-query";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import { useUnassignWagon, useWagonsByTrain } from "@/hooks/useWagons";
import type { Wagon } from "@/services/wagon.service";
import { DataTable } from "@edr/ui-common";
export function WagonsTable({ trainId }: { trainId: string }) {
const { data: wagons = [], isLoading, refetch } = useWagonsByTrain(trainId);
const unassign = useUnassignWagon();
const { data: wagons = [], isLoading, refetch } = useQuery(
api.wagons.listByTrain.queryOptions({
input: { trainId },
enabled: !!trainId,
}),
);
const unassign = useMutation(api.wagons.unassign.mutationOptions());
const { toast } = useToast();
const columns = useMemo((): ColumnDef<Wagon>[] => {

View File

@@ -9,11 +9,10 @@ import {
TextInput,
} from '@mantine/core';
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { api } from '@/services/api';
import { useToast } from '@/hooks/use-toast';
import { useStations } from '@/hooks/useStations';
import type { SaveWarehousePayload, Warehouse, WarehouseType } from '@/types/warehouse';
import { extractErrorMessage, statusOptions, warehouseTypeOptions } from './options';
@@ -52,7 +51,9 @@ export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWareh
const { toast } = useToast();
const createMutation = useMutation(api.warehouses.create.mutationOptions());
const updateMutation = useMutation(api.warehouses.update.mutationOptions());
const { data: stations } = useStations();
const { data: stations } = useQuery(
api.stations.list.queryOptions({ staleTime: 5 * 60 * 1000 }),
);
const [form, setForm] = useState<FormState>(emptyForm());
const stationOptions = (stations ?? []).map((s) => ({ value: s.id, label: `${s.name} (${s.code})` }));

View File

@@ -2,7 +2,9 @@ import { useMemo } from 'react';
import { ActionIcon, Card, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { Building2, Eye, MapPin, Pencil } from 'lucide-react';
import { useStations } from '@/hooks/useStations';
import { useQuery } from '@tanstack/react-query';
import { api } from '@/services/api';
import type { Warehouse } from '@/types/warehouse';
import { WarehouseStatusBadge, WarehouseTypeBadge } from './badges';
import { formatCapacity } from './options';
@@ -14,7 +16,9 @@ interface WarehouseCardViewProps {
}
export function WarehouseCardView({ warehouses, onView, onEdit }: WarehouseCardViewProps) {
const { data: stations } = useStations();
const { data: stations } = useQuery(
api.stations.list.queryOptions({ staleTime: 5 * 60 * 1000 }),
);
const stationNameById = useMemo(
() => new Map((stations ?? []).map((s) => [s.id, s.name])),
[stations],

View File

@@ -3,7 +3,9 @@ import { ActionIcon, Group, Text } from '@mantine/core';
import { Eye, Pencil } from 'lucide-react';
import { DataTable, type ColumnDef } from '@edr/ui-common';
import { useStations } from '@/hooks/useStations';
import { useQuery } from '@tanstack/react-query';
import { api } from '@/services/api';
import type { Warehouse } from '@/types/warehouse';
import { WarehouseStatusBadge, WarehouseTypeBadge } from './badges';
import { formatCapacity } from './options';
@@ -15,7 +17,9 @@ interface WarehouseTableProps {
}
export function WarehouseTable({ warehouses, onView, onEdit }: WarehouseTableProps) {
const { data: stations } = useStations();
const { data: stations } = useQuery(
api.stations.list.queryOptions({ staleTime: 5 * 60 * 1000 }),
);
const stationNameById = useMemo(
() => new Map((stations ?? []).map((s) => [s.id, s.name])),
[stations],

View File

@@ -1,9 +1,9 @@
import type { BookingListFilter } from "@/services/bookings.service";
import type { CompanyListFilter } from "@/types/customer";
import type { RuleEngineListParams } from "@/services/ruleEngine/ruleEngine.service";
import type { TrainScheduleFilters } from "@/types/trainScheduling";
import type { FleetResourceSlug } from "@/pages/fleet/config/resources";
import type { BookingListFilter } from "@/services/bookings.service";
import type { RuleEngineListParams } from "@/services/ruleEngine/ruleEngine.service";
import type { CompanyListFilter } from "@/types/customer";
import type { RuleEngineResourceSlug } from "@/types/rule-engine";
import type { TrainScheduleFilters } from "@/types/trainScheduling";
export const QUERY_KEYS = {
USERS: {

View File

@@ -1,35 +0,0 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
import type { FleetListFilters, FleetResourceSlug } from "@/services/fleet/fleet.service";
import { fleetService } from "@/services/fleet/fleet.service";
export function useFleetList(slug: FleetResourceSlug, filters?: FleetListFilters) {
return useQuery({
queryKey: [...QUERY_KEYS.FLEET.list(slug), filters ?? {}],
queryFn: () => fleetService.list(slug, filters),
});
}
export function useFleetMutations(slug: FleetResourceSlug) {
const qc = useQueryClient();
const invalidate = () => qc.invalidateQueries({ queryKey: QUERY_KEYS.FLEET.list(slug) });
const create = useMutation({
mutationFn: (data: Record<string, unknown>) => fleetService.create(slug, data),
onSuccess: invalidate,
});
const update = useMutation({
mutationFn: ({ id, data }: { id: string; data: Record<string, unknown> }) =>
fleetService.update(slug, id, data),
onSuccess: invalidate,
});
const remove = useMutation({
mutationFn: (id: string) => fleetService.remove(slug, id),
onSuccess: invalidate,
});
return { create, update, remove };
}

View File

@@ -1,325 +0,0 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
import { trainSchedulingService } from "@/services/trainScheduling.service";
import type {
AssignBookingsPayload,
CreateTrainSchedulePayload,
FreightType,
PinWagonsPayload,
RecordCheckpointPayload,
TrainScheduleFilters,
TrainSchedulePreviewPayload,
} from "@/types/trainScheduling";
export const useScheduleList = (freightType?: FreightType) =>
useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.schedules(),
queryFn: () => trainSchedulingService.listSchedules(freightType),
});
export const useBatchBoard = () =>
useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.batchBoard(),
queryFn: () => trainSchedulingService.getBatchBoard(),
refetchInterval: 30_000,
});
export const useBatchBoardDetail = (scheduleId: string | undefined) =>
useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.batchBoardDetail(scheduleId ?? ""),
queryFn: () => trainSchedulingService.getBatchBoardDetail(scheduleId!),
enabled: Boolean(scheduleId),
refetchInterval: 30_000,
});
export const useRunAllocation = (scheduleId: string) => {
const qc = useQueryClient();
return useMutation({
mutationFn: () => trainSchedulingService.runAllocation(scheduleId),
onSuccess: () => {
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.batchBoardDetail(scheduleId),
});
void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.batchBoard() });
},
});
};
export const useScheduleDetail = (id: string | undefined, freightType?: FreightType) =>
useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.scheduleById(id ?? ""),
queryFn: () => trainSchedulingService.getScheduleById(id!, freightType),
enabled: Boolean(id),
});
export const useEligibleBookings = (
filters?: TrainScheduleFilters,
enabled = true,
freightType?: FreightType,
) =>
useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.eligible(freightType, filters),
queryFn: () => trainSchedulingService.getEligibleBookings(filters, freightType),
enabled,
});
export const useAvailableLocomotives = (routeId?: string) =>
useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.locomotives(routeId),
queryFn: () => trainSchedulingService.getAvailableLocomotives(routeId),
enabled: routeId ? Boolean(routeId) : true,
});
export const useBatchActions = (scheduleId?: string) => {
const qc = useQueryClient();
const invalidate = () => {
void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.ROOT });
void qc.invalidateQueries({ queryKey: QUERY_KEYS.BOOKINGS.ROOT });
void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.batchBoard() });
if (scheduleId) {
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.scheduleById(scheduleId),
});
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.batchBoardDetail(scheduleId),
});
}
};
const runBatch = useMutation({
mutationFn: (id: string) => trainSchedulingService.runBatch(id),
onSuccess: invalidate,
});
const setWindow = useMutation({
mutationFn: ({ id, status }: { id: string; status: "OPEN" | "CLOSED" }) =>
trainSchedulingService.setBookingWindow(id, status),
onSuccess: invalidate,
});
const markPaid = useMutation({
mutationFn: (bookingId: string) => trainSchedulingService.markBookingPaid(bookingId),
onSuccess: invalidate,
});
const expire = useMutation({
mutationFn: (bookingId: string) => trainSchedulingService.expireBooking(bookingId),
onSuccess: invalidate,
});
const moveSchedule = useMutation({
mutationFn: ({ bookingId, trainScheduleId }: { bookingId: string; trainScheduleId: string }) =>
trainSchedulingService.moveBookingSchedule(bookingId, trainScheduleId),
onSuccess: invalidate,
});
return { runBatch, setWindow, markPaid, expire, moveSchedule, invalidate };
};
export const useBookableSchedules = (
originYardId?: string | null,
destinationYardId?: string | null,
) =>
useQuery({
queryKey: [
...QUERY_KEYS.TRAIN_SCHEDULING.ROOT,
"bookable",
originYardId ?? "",
destinationYardId ?? "",
],
queryFn: () =>
trainSchedulingService.getBookableSchedules(
originYardId ?? undefined,
destinationYardId ?? undefined,
),
enabled: Boolean(originYardId && destinationYardId),
});
/**
* Day-level pool: which days have an OPEN departure on the route. Staff pick a
* day (not a train) when creating a booking; the engine assigns the train.
*/
export const useAvailableDays = (
originYardId?: string | null,
destinationYardId?: string | null,
) =>
useQuery({
queryKey: [
...QUERY_KEYS.TRAIN_SCHEDULING.ROOT,
"available-days",
originYardId ?? "",
destinationYardId ?? "",
],
queryFn: () =>
trainSchedulingService.getAvailableDays(
originYardId ?? undefined,
destinationYardId ?? undefined,
),
enabled: Boolean(originYardId && destinationYardId),
});
export const useTrainTrack = (id: string | undefined) =>
useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.track(id ?? ""),
queryFn: () => trainSchedulingService.getTrack(id!),
enabled: Boolean(id),
});
export const useScheduleMutations = (scheduleId?: string) => {
const qc = useQueryClient();
const invalidate = () => {
void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.ROOT });
void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.schedules() });
void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.locomotives() });
if (scheduleId) {
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.scheduleById(scheduleId),
});
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.track(scheduleId),
});
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.unassignedBookings(scheduleId),
});
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.compositionRemovals(scheduleId),
});
}
void qc.invalidateQueries({ queryKey: QUERY_KEYS.BOOKINGS.ROOT });
};
const create = useMutation({
mutationFn: ({
freightType,
payload,
}: {
freightType?: FreightType;
payload: CreateTrainSchedulePayload;
}) => trainSchedulingService.createSchedule(payload, freightType),
onSuccess: invalidate,
});
const preview = useMutation({
mutationFn: ({
freightType,
payload,
}: {
freightType?: FreightType;
payload: TrainSchedulePreviewPayload;
}) => trainSchedulingService.preview(payload, freightType),
});
const assign = useMutation({
mutationFn: ({
id,
freightType,
payload,
}: {
id: string;
freightType?: FreightType;
payload: AssignBookingsPayload;
}) => trainSchedulingService.assignBookings(id, payload, freightType),
onSuccess: invalidate,
});
const assignUnassigned = useMutation({
mutationFn: ({ id, bookingId }: { id: string; bookingId: string }) =>
trainSchedulingService.assignUnassignedBooking(id, bookingId),
onSuccess: invalidate,
});
const unassign = useMutation({
mutationFn: ({ id, bookingId }: { id: string; bookingId: string }) =>
trainSchedulingService.unassignBooking(id, bookingId),
onSuccess: invalidate,
});
const pin = useMutation({
mutationFn: ({ id, payload }: { id: string; payload: PinWagonsPayload }) =>
trainSchedulingService.pinWagons(id, payload),
onSuccess: invalidate,
});
const finalize = useMutation({
mutationFn: (id: string) => trainSchedulingService.finalizeSchedule(id),
onSuccess: invalidate,
});
const dispatch = useMutation({
mutationFn: (id: string) => trainSchedulingService.dispatchSchedule(id),
onSuccess: invalidate,
});
const cancel = useMutation({
mutationFn: ({ id, freightType }: { id: string; freightType?: FreightType }) =>
trainSchedulingService.cancelSchedule(id, freightType ?? "CONTAINER"),
onSuccess: invalidate,
});
const recordCheckpoint = useMutation({
mutationFn: ({ id, payload }: { id: string; payload: RecordCheckpointPayload }) =>
trainSchedulingService.recordCheckpoint(id, payload),
onSuccess: invalidate,
});
const arrive = useMutation({
mutationFn: (id: string) => trainSchedulingService.arriveSchedule(id),
onSuccess: invalidate,
});
return {
create,
preview,
assign,
assignUnassigned,
unassign,
pin,
finalize,
dispatch,
cancel,
recordCheckpoint,
arrive,
invalidate,
};
};
export const useUnassignedBookings = (scheduleId: string | undefined) =>
useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.unassignedBookings(scheduleId ?? ""),
queryFn: () => trainSchedulingService.getUnassignedBookings(scheduleId!),
enabled: Boolean(scheduleId),
});
export const useCompositionRemovals = (scheduleId: string | undefined) =>
useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.compositionRemovals(scheduleId ?? ""),
queryFn: () => trainSchedulingService.getCompositionRemovals(scheduleId!),
enabled: Boolean(scheduleId),
});
export const useRemoveWagonSlot = (scheduleId: string) => {
const qc = useQueryClient();
return useMutation({
mutationFn: (wagonId: string) =>
trainSchedulingService.removeWagonSlot(scheduleId, wagonId),
onSuccess: () => {
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.scheduleById(scheduleId),
});
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.batchBoardDetail(scheduleId),
});
},
});
};
export const useUpdateContainerItem = (scheduleId: string) => {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ itemId, containerNumber }: { itemId: string; containerNumber: string | null }) =>
trainSchedulingService.updateContainerItem(scheduleId, itemId, { containerNumber }),
onSuccess: () => {
void qc.invalidateQueries({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.scheduleById(scheduleId),
});
},
});
};

View File

@@ -1,12 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { cargoTypesService } from '@/services/cargo-types.service';
export const CARGO_TYPES_QUERY_KEY = ['cargo-types'];
export function useCargoTypes() {
return useQuery({
queryKey: CARGO_TYPES_QUERY_KEY,
queryFn: () => cargoTypesService.getCargoTypes(),
staleTime: Infinity,
});
}

View File

@@ -1,12 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { containerTypesService } from '@/services/container-types.service';
export const CONTAINER_TYPES_QUERY_KEY = ['container-types'];
export function useContainerTypes() {
return useQuery({
queryKey: CONTAINER_TYPES_QUERY_KEY,
queryFn: () => containerTypesService.getContainerTypes(),
staleTime: Infinity,
});
}

View File

@@ -1,36 +0,0 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { wagonTypesService } from '@/services/wagon-types.service';
export const WAGON_TYPES_QUERY_KEY = ['wagon-types'];
export function useWagonTypes() {
return useQuery({
queryKey: WAGON_TYPES_QUERY_KEY,
queryFn: () => wagonTypesService.getWagonTypes(),
});
}
export function useCreateWagonType() {
const qc = useQueryClient();
return useMutation({
mutationFn: wagonTypesService.create,
onSuccess: () => qc.invalidateQueries({ queryKey: WAGON_TYPES_QUERY_KEY }),
});
}
export function useUpdateWagonType() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Record<string, unknown> }) =>
wagonTypesService.update(id, data),
onSuccess: () => qc.invalidateQueries({ queryKey: WAGON_TYPES_QUERY_KEY }),
});
}
export function useDeleteWagonType() {
const qc = useQueryClient();
return useMutation({
mutationFn: wagonTypesService.delete,
onSuccess: () => qc.invalidateQueries({ queryKey: WAGON_TYPES_QUERY_KEY }),
});
}

View File

@@ -1,59 +0,0 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { containerService } from '@/services/containerService';
export const containerKeys = {
all: ['containers'] as const,
byWagon: (wagonId: string) => [...containerKeys.all, 'wagon', wagonId] as const,
details: () => [...containerKeys.all, 'detail'] as const,
detail: (id: string) => [...containerKeys.details(), id] as const,
};
export function useContainers() {
return useQuery({ queryKey: containerKeys.all, queryFn: () => containerService.getAll().then(res => res.data) });
}
export const useGetContainers = useContainers;
export function useContainersByWagon(wagonId: string) {
return useQuery({ queryKey: containerKeys.byWagon(wagonId), queryFn: () => containerService.getByWagon(wagonId).then(res => res.data), enabled: !!wagonId });
}
export function useContainer(id: string) {
return useQuery({ queryKey: containerKeys.detail(id), queryFn: () => containerService.getById(id).then(res => res.data), enabled: !!id });
}
export const useGetContainer = useContainer;
export function useCreateContainer() {
const qc = useQueryClient();
return useMutation({ mutationFn: containerService.create, onSuccess: () => qc.invalidateQueries({ queryKey: containerKeys.all }) });
}
export function useUpdateContainer() {
const qc = useQueryClient();
return useMutation({ mutationFn: ({ id, data }: any) => containerService.update(id, data), onSuccess: (_, { id }) => {
qc.invalidateQueries({ queryKey: containerKeys.all });
qc.invalidateQueries({ queryKey: containerKeys.detail(id) });
} });
}
export function useDeleteContainer() {
const qc = useQueryClient();
return useMutation({ mutationFn: containerService.delete, onSuccess: () => qc.invalidateQueries({ queryKey: containerKeys.all }) });
}
export function useAssignContainerToWagon() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ containerId, wagonId, position }: any) => containerService.assignToWagon(containerId, wagonId, position),
onSuccess: (_, { wagonId }) => qc.invalidateQueries({ queryKey: containerKeys.byWagon(wagonId) })
});
}
export function useUnassignContainer() {
const qc = useQueryClient();
return useMutation({
mutationFn: containerService.unassign,
onSuccess: () => qc.invalidateQueries({ queryKey: containerKeys.all })
});
}

View File

@@ -1,112 +0,0 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "@/services/api";
import type {
CreateDropdownOptionDto,
CreateDropdownSettingDto,
UpdateDropdownOptionDto,
UpdateDropdownSettingDto,
} from "@/types/dropdownSettings";
/* ----------------------------- Mutations ----------------------------- */
export const useCreateDropdownSetting = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: (dto: CreateDropdownSettingDto) =>
api.dropdownSettings.create.call(dto),
onSuccess: () =>
qc.invalidateQueries({ queryKey: api.dropdownSettings.list.queryKey() }),
});
};
export const useUpdateDropdownSetting = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
id,
dto,
}: {
id: string;
dto: UpdateDropdownSettingDto;
}) => api.dropdownSettings.update.call({ id, dto }),
onSuccess: (_data, { id }) => {
qc.invalidateQueries({ queryKey: api.dropdownSettings.list.queryKey() });
qc.invalidateQueries({
queryKey: api.dropdownSettings.getById.queryKey({ id }),
});
},
});
};
export const useDeleteDropdownSetting = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) => api.dropdownSettings.remove.call({ id }),
onSuccess: () =>
qc.invalidateQueries({ queryKey: api.dropdownSettings.list.queryKey() }),
});
};
export const useReplaceDropdownOptions = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
settingId,
options,
}: {
settingId: string;
options: CreateDropdownOptionDto[];
}) => api.dropdownSettings.replaceOptions.call({ id: settingId, options }),
onSuccess: (_data, { settingId }) => {
qc.invalidateQueries({ queryKey: api.dropdownSettings.list.queryKey() });
qc.invalidateQueries({
queryKey: api.dropdownSettings.getById.queryKey({ id: settingId }),
});
},
});
};
export const useAddDropdownOption = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
settingId,
dto,
}: {
settingId: string;
dto: CreateDropdownOptionDto;
}) => api.dropdownSettings.addOption.call({ id: settingId, dto }),
onSuccess: (_data, { settingId }) => {
qc.invalidateQueries({ queryKey: api.dropdownSettings.list.queryKey() });
qc.invalidateQueries({
queryKey: api.dropdownSettings.getById.queryKey({ id: settingId }),
});
},
});
};
export const useUpdateDropdownOption = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
optionId,
dto,
}: {
optionId: string;
dto: UpdateDropdownOptionDto;
}) => api.dropdownSettings.updateOption.call({ optionId, dto }),
onSuccess: () =>
qc.invalidateQueries({ queryKey: api.dropdownSettings.list.queryKey() }),
});
};
export const useRemoveDropdownOption = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: (optionId: string) =>
api.dropdownSettings.removeOption.call({ optionId }),
onSuccess: () =>
qc.invalidateQueries({ queryKey: api.dropdownSettings.list.queryKey() }),
});
};

View File

@@ -1,16 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { facilityService } from '@/services/facility.service';
export const facilityKeys = {
all: ['facilities'] as const,
list: () => ['facilities', 'list'] as const,
detail: (id: string) => ['facilities', 'detail', id] as const,
};
export function useFacilities() {
return useQuery({
queryKey: facilityKeys.list(),
queryFn: () => facilityService.list().then((r) => r.data),
});
}

View File

@@ -1,126 +0,0 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "@/services/api";
import type {
CreateFileUploadFieldDto,
CreateFileUploadSettingDto,
UpdateFileUploadFieldDto,
UpdateFileUploadSettingDto,
} from "@/types/fileUploadSettings";
/* ----------------------------- Mutations ----------------------------- */
export const useCreateFileUploadSetting = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: (dto: CreateFileUploadSettingDto) =>
api.fileUploadSettings.create.call(dto),
onSuccess: () =>
qc.invalidateQueries({
queryKey: api.fileUploadSettings.list.queryKey(),
}),
});
};
export const useUpdateFileUploadSetting = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
id,
dto,
}: {
id: string;
dto: UpdateFileUploadSettingDto;
}) => api.fileUploadSettings.update.call({ id, dto }),
onSuccess: (_data, { id }) => {
qc.invalidateQueries({
queryKey: api.fileUploadSettings.list.queryKey(),
});
qc.invalidateQueries({
queryKey: api.fileUploadSettings.getById.queryKey({ id }),
});
},
});
};
export const useDeleteFileUploadSetting = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) => api.fileUploadSettings.remove.call({ id }),
onSuccess: () =>
qc.invalidateQueries({
queryKey: api.fileUploadSettings.list.queryKey(),
}),
});
};
export const useReplaceFileUploadFields = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
settingId,
fields,
}: {
settingId: string;
fields: CreateFileUploadFieldDto[];
}) => api.fileUploadSettings.replaceFields.call({ id: settingId, fields }),
onSuccess: (_data, { settingId }) => {
qc.invalidateQueries({
queryKey: api.fileUploadSettings.list.queryKey(),
});
qc.invalidateQueries({
queryKey: api.fileUploadSettings.getById.queryKey({ id: settingId }),
});
},
});
};
export const useAddFileUploadField = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
settingId,
dto,
}: {
settingId: string;
dto: CreateFileUploadFieldDto;
}) => api.fileUploadSettings.addField.call({ settingId, dto }),
onSuccess: (_data, { settingId }) => {
qc.invalidateQueries({
queryKey: api.fileUploadSettings.list.queryKey(),
});
qc.invalidateQueries({
queryKey: api.fileUploadSettings.getById.queryKey({ id: settingId }),
});
},
});
};
export const useUpdateFileUploadField = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
fieldId,
dto,
}: {
fieldId: string;
dto: UpdateFileUploadFieldDto;
}) => api.fileUploadSettings.updateField.call({ fieldId, dto }),
onSuccess: () =>
qc.invalidateQueries({
queryKey: api.fileUploadSettings.list.queryKey(),
}),
});
};
export const useRemoveFileUploadField = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: (fieldId: string) =>
api.fileUploadSettings.removeField.call({ fieldId }),
onSuccess: () =>
qc.invalidateQueries({
queryKey: api.fileUploadSettings.list.queryKey(),
}),
});
};

View File

@@ -1,47 +0,0 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { locomotivesService } from '@/services/locomotives.service';
export const locomotiveKeys = {
all: ['locomotives'] as const,
details: () => [...locomotiveKeys.all, 'detail'] as const,
detail: (id: string) => [...locomotiveKeys.details(), id] as const,
};
export function useLocomotives() {
return useQuery({
queryKey: locomotiveKeys.all,
queryFn: () => locomotivesService.getAll().then((response) => response.data),
});
}
export function useCreateLocomotive() {
const qc = useQueryClient();
return useMutation({
mutationFn: locomotivesService.create,
onSuccess: () => qc.invalidateQueries({ queryKey: locomotiveKeys.all }),
});
}
export function useUpdateLocomotive() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Record<string, unknown> }) =>
locomotivesService.update(id, data),
onSuccess: (_, { id }) => {
qc.invalidateQueries({ queryKey: locomotiveKeys.all });
qc.invalidateQueries({ queryKey: locomotiveKeys.detail(id) });
},
});
}
export function useDecommissionLocomotive() {
const qc = useQueryClient();
return useMutation({
mutationFn: locomotivesService.decommission,
onSuccess: (_, id) => {
qc.invalidateQueries({ queryKey: locomotiveKeys.all });
qc.invalidateQueries({ queryKey: locomotiveKeys.detail(id) });
},
});
}

View File

@@ -1,23 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import {
paymentsService,
type PaymentListFilter,
} from "@/services/payments.service";
export function usePaymentList(filter?: PaymentListFilter, enabled = true) {
return useQuery({
queryKey: ["payments", "list", filter ?? {}],
queryFn: () => paymentsService.list(filter),
enabled,
});
}
export function usePaymentSummary(enabled = true) {
return useQuery({
queryKey: ["payments", "summary"],
queryFn: () => paymentsService.getSummary(),
staleTime: 30_000,
enabled,
});
}

View File

@@ -1,55 +0,0 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { routesService } from '@/services/routes.service';
export const routeKeys = {
all: ['routes'] as const,
yards: ['routes', 'yards'] as const,
details: () => [...routeKeys.all, 'detail'] as const,
detail: (id: string) => [...routeKeys.details(), id] as const,
};
export function useRoutes() {
return useQuery({
queryKey: routeKeys.all,
queryFn: () => routesService.getAll().then((response) => response.data),
});
}
export function useRouteYards() {
return useQuery({
queryKey: routeKeys.yards,
queryFn: () => routesService.getYards().then((response) => response.data.data),
});
}
export function useCreateRoute() {
const qc = useQueryClient();
return useMutation({
mutationFn: routesService.create,
onSuccess: () => qc.invalidateQueries({ queryKey: routeKeys.all }),
});
}
export function useUpdateRoute() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Record<string, unknown> }) =>
routesService.update(id, data),
onSuccess: (_, { id }) => {
qc.invalidateQueries({ queryKey: routeKeys.all });
qc.invalidateQueries({ queryKey: routeKeys.detail(id) });
},
});
}
export function useDeactivateRoute() {
const qc = useQueryClient();
return useMutation({
mutationFn: routesService.deactivate,
onSuccess: (_, id) => {
qc.invalidateQueries({ queryKey: routeKeys.all });
qc.invalidateQueries({ queryKey: routeKeys.detail(id) });
},
});
}

View File

@@ -1,30 +0,0 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import toast from "react-hot-toast";
import {
signaturesService,
type SaveSignaturePayload,
} from "@/services/signatures.service";
const SAVED_SIGNATURE_KEY = ["me", "signature"] as const;
export function useMySignature() {
return useQuery({
queryKey: SAVED_SIGNATURE_KEY,
queryFn: () => signaturesService.getMySignature(),
staleTime: 60_000,
});
}
export function useSaveSignature() {
const qc = useQueryClient();
return useMutation({
mutationFn: (payload: SaveSignaturePayload) =>
signaturesService.saveMySignature(payload),
onSuccess: () => {
toast.success("Signature saved");
void qc.invalidateQueries({ queryKey: SAVED_SIGNATURE_KEY });
},
onError: () => toast.error("Failed to save signature"),
});
}

View File

@@ -1,16 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { trainSchedulingService } from '@/services/trainScheduling.service';
import { QUERY_KEYS } from '@/constants/QUERY_KEYS';
/**
* The 21 network stations / yards, sourced from the existing booking
* reference-data API. Reused as the parent "Facility / Port" for warehouses.
*/
export function useStations() {
return useQuery({
queryKey: QUERY_KEYS.TRAIN_SCHEDULING.stations(),
queryFn: () => trainSchedulingService.getStations(),
staleTime: 5 * 60 * 1000,
});
}

View File

@@ -1,39 +0,0 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { trainService } from '@/services/trains.service';
export const trainKeys = {
all: ['trains'] as const,
lists: () => [...trainKeys.all, 'list'] as const,
details: () => [...trainKeys.all, 'detail'] as const,
detail: (id: string) => [...trainKeys.details(), id] as const,
};
export function useTrains() {
return useQuery({ queryKey: trainKeys.lists(), queryFn: () => trainService.getAll().then(res => res.data) });
}
export const useGetTrains = useTrains;
export function useTrain(id: string) {
return useQuery({ queryKey: trainKeys.detail(id), queryFn: () => trainService.getById(id).then(res => res.data), enabled: !!id });
}
export const useGetTrain = useTrain;
export function useCreateTrain() {
const qc = useQueryClient();
return useMutation({ mutationFn: trainService.create, onSuccess: () => qc.invalidateQueries({ queryKey: trainKeys.lists() }) });
}
export function useUpdateTrain() {
const qc = useQueryClient();
return useMutation({ mutationFn: ({ id, data }: any) => trainService.update(id, data), onSuccess: (_, { id }) => {
qc.invalidateQueries({ queryKey: trainKeys.lists() });
qc.invalidateQueries({ queryKey: trainKeys.detail(id) });
} });
}
export function useDeleteTrain() {
const qc = useQueryClient();
return useMutation({ mutationFn: trainService.delete, onSuccess: () => qc.invalidateQueries({ queryKey: trainKeys.lists() }) });
}

View File

@@ -1,64 +0,0 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { wagonService } from '@/services/wagon.service';
export type WagonListFilters = import('@/services/wagon.service').WagonListFilters;
export const wagonKeys = {
all: ['wagons'] as const,
list: (filters?: WagonListFilters) => [...wagonKeys.all, 'list', filters ?? {}] as const,
byTrain: (trainId: string) => [...wagonKeys.all, 'train', trainId] as const,
details: () => [...wagonKeys.all, 'detail'] as const,
detail: (id: string) => [...wagonKeys.details(), id] as const,
};
export function useWagons(filters?: WagonListFilters) {
return useQuery({
queryKey: wagonKeys.list(filters),
queryFn: () => wagonService.getAll(filters ?? {}).then((res) => res.data),
});
}
export const useGetWagons = useWagons;
export function useWagonsByTrain(trainId: string) {
return useQuery({ queryKey: wagonKeys.byTrain(trainId), queryFn: () => wagonService.getByTrain(trainId).then(res => res.data), enabled: !!trainId });
}
export function useWagon(id: string) {
return useQuery({ queryKey: wagonKeys.detail(id), queryFn: () => wagonService.getById(id).then(res => res.data), enabled: !!id });
}
export const useGetWagon = useWagon;
export function useAssignWagonToTrain() {
const qc = useQueryClient();
return useMutation({ mutationFn: ({ wagonId, trainId, sequenceNumber }: any) => wagonService.assignToTrain(wagonId, trainId, sequenceNumber), onSuccess: (_, { trainId }) => qc.invalidateQueries({ queryKey: wagonKeys.byTrain(trainId) }) });
}
export function useUnassignWagon() {
const qc = useQueryClient();
return useMutation({ mutationFn: wagonService.unassign, onSuccess: () => qc.invalidateQueries({ queryKey: wagonKeys.all }) });
}
export function useReorderWagons() {
const qc = useQueryClient();
return useMutation({ mutationFn: ({ trainId, wagonIds }: any) => wagonService.reorder(trainId, wagonIds), onSuccess: (_, { trainId }) => qc.invalidateQueries({ queryKey: wagonKeys.byTrain(trainId) }) });
}
export function useCreateWagon() {
const qc = useQueryClient();
return useMutation({ mutationFn: wagonService.create, onSuccess: () => qc.invalidateQueries({ queryKey: wagonKeys.all }) });
}
export function useUpdateWagon() {
const qc = useQueryClient();
return useMutation({ mutationFn: ({ id, data }: any) => wagonService.update(id, data), onSuccess: (_, { id }) => {
qc.invalidateQueries({ queryKey: wagonKeys.all });
qc.invalidateQueries({ queryKey: wagonKeys.detail(id) });
} });
}
export function useDeleteWagon() {
const qc = useQueryClient();
return useMutation({ mutationFn: wagonService.delete, onSuccess: () => qc.invalidateQueries({ queryKey: wagonKeys.all }) });
}

View File

@@ -1,7 +1,3 @@
import { useEffect, useMemo, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { isAxiosError } from "axios";
import {
ActionIcon,
Badge,
@@ -23,6 +19,8 @@ import {
ThemeIcon,
Tooltip,
} from "@mantine/core";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { isAxiosError } from "axios";
import {
AlertTriangle,
ArrowLeft,
@@ -40,14 +38,16 @@ import {
Trash2,
Weight,
} from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
import { useNavigate } from "react-router-dom";
import Breadcrumbs from "@/components/ui/Breadcrumbs";
import { bookingsService } from "@/services/bookings.service";
import { useAvailableDays } from "@/hooks/trainScheduling/useTrainScheduling";
import { api } from "@/auth/http";
import { unwrap } from "@/utils/endpoint";
import Breadcrumbs from "@/components/ui/Breadcrumbs";
import { URL_CONSTANTS } from "@/constants/URLS";
import { api as appApi } from "@/services/api";
import { bookingsService } from "@/services/bookings.service";
import { unwrap } from "@/utils/endpoint";
interface CompanyOption {
id: string;
@@ -234,9 +234,11 @@ export default function NewBookingPage() {
// Day-level pool: fetch only the days that have a departure on the route (no
// train, no capacity). The batch engine assigns the train after booking.
const { data: availableDays, isLoading: daysLoading } = useAvailableDays(
originYardId,
destinationYardId,
const { data: availableDays, isLoading: daysLoading } = useQuery(
appApi.trainScheduling.availableDays.queryOptions({
input: { originYardId, destinationYardId },
enabled: Boolean(originYardId && destinationYardId),
}),
);
const dayOptions = (availableDays ?? []).map((day) => ({
value: day,
@@ -392,7 +394,7 @@ export default function NewBookingPage() {
</Button>
</Group>
<Grid gutter="lg" mt="lg">
<Grid gap="lg" mt="lg">
{/* LEFT — form */}
<Grid.Col span={{ base: 12, lg: 8 }}>
<Stack gap="lg">
@@ -453,7 +455,6 @@ export default function NewBookingPage() {
value={originYardId}
onChange={(v) => {
setOriginYardId(v);
setTrainScheduleId(null);
}}
searchable
disabled={isLoading}

View File

@@ -15,7 +15,8 @@ import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { FileUploadEntity } from "@edr/types/freight";
import { useCreateFileUploadSetting, useUpdateFileUploadSetting } from "@/hooks/useFileUploadSettings";
import { useMutation } from "@tanstack/react-query";
import { api } from "@/services/api";
// import type {
// FileUploadEntity,
@@ -61,8 +62,8 @@ export default function EditFileUploadSettingDialog({
const [description, setDescription] = useState(setting?.description ?? "");
const [error, setError] = useState<string | null>(null);
const createMutation = useCreateFileUploadSetting();
const updateMutation = useUpdateFileUploadSetting();
const createMutation = useMutation(api.fileUploadSettings.create.mutationOptions());
const updateMutation = useMutation(api.fileUploadSettings.update.mutationOptions());
const pending = createMutation.isPending || updateMutation.isPending;
const reset = () => {

View File

@@ -25,12 +25,11 @@ import {
Trash2,
X,
} from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import { api } from "@/services/api";
import { useDeleteFileUploadSetting } from "@/hooks/useFileUploadSettings";
import { getMinFiles, type FileUploadSetting } from "@/types/fileUploadSettings";
import { DataTable, type ColumnDef } from "@edr/ui-common";
@@ -44,7 +43,7 @@ export default function FileUploadSettingsPage() {
const { data, isLoading, isError, error, refetch } = useQuery(
api.fileUploadSettings.list.queryOptions(),
);
const deleteMutation = useDeleteFileUploadSetting();
const deleteMutation = useMutation(api.fileUploadSettings.remove.mutationOptions());
const fileUploadSettings = useMemo(
() => (Array.isArray(data) ? data : []),
@@ -202,7 +201,7 @@ export default function FileUploadSettingsPage() {
<DeleteFileUploadSettingDialog
settingLabel={setting.label}
settingCode={setting.code}
onConfirm={() => deleteMutation.mutate(setting.id)}
onConfirm={() => deleteMutation.mutate({ id: setting.id })}
>
<ActionIcon
variant="default"

View File

@@ -19,7 +19,8 @@ import type {
FileUploadSetting,
} from "@/types/fileUploadSettings";
import { getMinFiles } from "@/types/fileUploadSettings";
import { useReplaceFileUploadFields } from "@/hooks/useFileUploadSettings";
import { useMutation } from "@tanstack/react-query";
import { api } from "@/services/api";
export interface ManageFileUploadFieldsDialogProps {
setting: FileUploadSetting;
@@ -77,7 +78,9 @@ export default function ManageFileUploadFieldsDialog({
const [fields, setFields] = useState<DraftField[]>(seed);
const replaceMutation = useReplaceFileUploadFields();
const replaceMutation = useMutation(
api.fileUploadSettings.replaceFields.mutationOptions(),
);
const update = (i: number, patch: Partial<DraftField>) =>
setFields((prev) =>
@@ -145,7 +148,7 @@ export default function ManageFileUploadFieldsDialog({
}));
replaceMutation.mutate(
{ settingId: setting.id, fields: payload },
{ id: setting.id, fields: payload },
{
onSuccess: () => setOpen(false),
onError: (err) =>

View File

@@ -31,9 +31,8 @@ import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
import EditDropdownSettingDialog from "./EditDropdownSettingDialog";
import ManageDropdownOptionsDialog from "./ManageDropdownOptionsDialog";
import DeleteDropdownSettingDialog from "./DeleteDropdownSettingDialog";
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useDeleteDropdownSetting } from "@/hooks/useDropdownSettings";
import type { DropdownSetting } from "@/types/dropdownSettings";
import {
DataTable,
@@ -63,7 +62,7 @@ export default function DropdownSettingsPage() {
const { data, isLoading, isError, error } = useQuery(
api.dropdownSettings.list.queryOptions(),
);
const deleteMutation = useDeleteDropdownSetting();
const deleteMutation = useMutation(api.dropdownSettings.remove.mutationOptions());
const dropdownSettings = useMemo<DropdownSetting[]>(
() => (Array.isArray(data) ? data : []),
@@ -353,7 +352,7 @@ export default function DropdownSettingsPage() {
key={`delete-${activeSetting.id}`}
settingLabel={activeSetting.label}
settingCode={activeSetting.code}
onConfirm={() => deleteMutation.mutate(activeSetting.id)}
onConfirm={() => deleteMutation.mutate({ id: activeSetting.id })}
open={activeDialog === "delete"}
onOpenChange={(next) => (next ? null : closeDialog())}
/>

View File

@@ -20,10 +20,9 @@ import type {
DropdownSetting,
UpdateDropdownSettingDto,
} from "@/types/dropdownSettings";
import {
useCreateDropdownSetting,
useUpdateDropdownSetting,
} from "@/hooks/useDropdownSettings";
import { useMutation } from "@tanstack/react-query";
import { api } from "@/services/api";
export interface EditDropdownSettingDialogProps {
mode?: "create" | "edit";
@@ -76,8 +75,8 @@ export default function EditDropdownSettingDialog({
);
const [error, setError] = useState<string | null>(null);
const createMutation = useCreateDropdownSetting();
const updateMutation = useUpdateDropdownSetting();
const createMutation = useMutation(api.dropdownSettings.create.mutationOptions());
const updateMutation = useMutation(api.dropdownSettings.update.mutationOptions());
const pending = createMutation.isPending || updateMutation.isPending;
const reset = () => {

View File

@@ -18,7 +18,8 @@ import type {
CreateDropdownOptionDto,
DropdownSetting,
} from "@/types/dropdownSettings";
import { useReplaceDropdownOptions } from "@/hooks/useDropdownSettings";
import { useMutation } from "@tanstack/react-query";
import { api } from "@/services/api";
export interface ManageDropdownOptionsDialogProps {
setting: DropdownSetting;
@@ -84,7 +85,9 @@ export default function ManageDropdownOptionsDialog({
const [options, setOptions] = useState<DraftOption[]>(seed);
const replaceMutation = useReplaceDropdownOptions();
const replaceMutation = useMutation(
api.dropdownSettings.replaceOptions.mutationOptions(),
);
const update = (i: number, patch: Partial<DraftOption>) =>
setOptions((prev) =>
@@ -147,7 +150,7 @@ export default function ManageDropdownOptionsDialog({
});
replaceMutation.mutate(
{ settingId: setting.id, options: payload },
{ id: setting.id, options: payload },
{
onSuccess: () => setOpen(false),
onError: (err) =>

View File

@@ -36,30 +36,7 @@ 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 { useCargoTypes } from '@/hooks/use-cargo-types';
import { useContainerTypes } from '@/hooks/use-container-types';
import {
useCreateWagonType,
useDeleteWagonType,
useUpdateWagonType,
useWagonTypes,
} from '@/hooks/use-wagon-types';
import { useToast } from '@/hooks/use-toast';
import {
useContainers,
useCreateContainer,
useDeleteContainer,
useUpdateContainer,
} from '@/hooks/useContainers';
import { useCreateTrain, useDeleteTrain, useTrains, useUpdateTrain } from '@/hooks/useTrains';
import { useRouteYards } from '@/hooks/useRoutes';
import { useCreateWagon, useDeleteWagon, useUpdateWagon, useWagons } from '@/hooks/useWagons';
import {
useCreateLocomotive,
useDecommissionLocomotive,
useLocomotives,
useUpdateLocomotive,
} from '@/hooks/useLocomotives';
import type { Cargo } from '@/services/cargoService';
import { DeliverCargoDialog } from '@/components/cargoes/DeliverCargoDialog';
import type { Container } from '@/services/containerService';
@@ -513,7 +490,7 @@ const optionLabel = (options: { value: string; label: string }[], value?: string
options.find((option) => option.value === value)?.label ?? value ?? '-';
export function TrainMasterDataPage() {
const query = useTrains();
const query = useQuery(api.trains.list.queryOptions());
return (
<FleetCrudPage<Train>
title="Trains"
@@ -521,9 +498,9 @@ export function TrainMasterDataPage() {
addLabel="Add Train"
data={query.data}
isLoading={query.isLoading}
create={useCreateTrain()}
update={useUpdateTrain()}
remove={useDeleteTrain()}
create={useMutation(api.trains.create.mutationOptions())}
update={useMutation(api.trains.update.mutationOptions())}
remove={useMutation(api.trains.remove.mutationOptions())}
searchText={(train) => [train.code, train.trainNumber, train.trainName, train.status].join(' ')}
columns={[
{ key: 'code', label: 'Code' },
@@ -548,10 +525,10 @@ export function TrainMasterDataPage() {
}
export function WagonTypesCrudPage() {
const query = useWagonTypes();
const create = useCreateWagonType();
const update = useUpdateWagonType();
const remove = useDeleteWagonType();
const query = useQuery(api.wagonTypes.list.queryOptions());
const create = useMutation(api.wagonTypes.create.mutationOptions());
const update = useMutation(api.wagonTypes.update.mutationOptions());
const remove = useMutation(api.wagonTypes.remove.mutationOptions());
const { toast } = useToast();
const [search, setSearch] = useState('');
const [page, setPage] = useState(1);
@@ -898,9 +875,9 @@ export function WagonTypesCrudPage() {
}
export function WagonsCrudPage() {
const query = useWagons();
const { data: wagonTypes = [] } = useWagonTypes();
const { data: yards = [] } = useRouteYards();
const query = useQuery(api.wagons.list.queryOptions({ input: {} }));
const { data: wagonTypes = [] } = useQuery(api.wagonTypes.list.queryOptions());
const { data: yards = [] } = useQuery(api.routes.yards.queryOptions());
const wagonTypeOptions = wagonTypes.map((type: any) => ({
value: type.id,
label: `${type.code} - ${type.name}`,
@@ -916,9 +893,9 @@ export function WagonsCrudPage() {
addLabel="Add Wagon"
data={query.data}
isLoading={query.isLoading}
create={useCreateWagon()}
update={useUpdateWagon()}
remove={useDeleteWagon()}
create={useMutation(api.wagons.create.mutationOptions())}
update={useMutation(api.wagons.update.mutationOptions())}
remove={useMutation(api.wagons.remove.mutationOptions())}
searchText={(wagon) => [
wagon.wagonNumber,
wagon.wagonTypeId,
@@ -986,9 +963,11 @@ export function WagonsCrudPage() {
}
export function ContainersCrudPage() {
const query = useContainers();
const { data: containerTypes = [] } = useContainerTypes();
const { data: wagons = [] } = useWagons();
const query = useQuery(api.containers.list.queryOptions());
const { data: containerTypes = [] } = useQuery(
api.containerTypes.list.queryOptions({ staleTime: Infinity }),
);
const { data: wagons = [] } = useQuery(api.wagons.list.queryOptions({ input: {} }));
const containerTypeOptions = containerTypes.map((type: any) => ({
value: type.id,
label: type.label ?? type.name ?? type.code,
@@ -1004,9 +983,9 @@ export function ContainersCrudPage() {
addLabel="Add Container"
data={query.data}
isLoading={query.isLoading}
create={useCreateContainer()}
update={useUpdateContainer()}
remove={useDeleteContainer()}
create={useMutation(api.containers.create.mutationOptions())}
update={useMutation(api.containers.update.mutationOptions())}
remove={useMutation(api.containers.remove.mutationOptions())}
searchText={(container) => [container.containerNumber, container.containerTypeId, container.wagonId, container.status].join(' ')}
columns={[
{ key: 'containerNumber', label: 'Number' },
@@ -1044,8 +1023,10 @@ export function ContainersCrudPage() {
export function CargoesCrudPage() {
const query = useQuery(api.cargoes.list.queryOptions());
const { data: cargoTypes = [] } = useCargoTypes();
const { data: containers = [] } = useContainers();
const { data: cargoTypes = [] } = useQuery(
api.cargoTypes.list.queryOptions({ staleTime: Infinity }),
);
const { data: containers = [] } = useQuery(api.containers.list.queryOptions());
const cargoTypeOptions = cargoTypes.map((type: any) => ({
value: type.id,
label: type.cargoTypeName ?? type.cargo_type_name ?? type.name ?? type.code,
@@ -1112,7 +1093,7 @@ export function CargoesCrudPage() {
}
export function LocomotivesCrudPage() {
const query = useLocomotives();
const query = useQuery(api.locomotives.list.queryOptions());
return (
<FleetCrudPage<Locomotive>
@@ -1122,9 +1103,9 @@ export function LocomotivesCrudPage() {
addLabel="Add Locomotive"
data={query.data}
isLoading={query.isLoading}
create={useCreateLocomotive()}
update={useUpdateLocomotive()}
remove={useDecommissionLocomotive()}
create={useMutation(api.locomotives.create.mutationOptions())}
update={useMutation(api.locomotives.update.mutationOptions())}
remove={useMutation(api.locomotives.decommission.mutationOptions())}
removeActionLabel="Decommission"
removeConfirmMessage="Decommission this locomotive?"
removeSuccessMessage="Locomotive decommissioned"

View File

@@ -1,5 +1,8 @@
import type { ColumnDef } from "@edr/ui-common";
import { Box, Button, Card, Group, Modal, Select, Stack, Text } from "@mantine/core";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import {
Archive,
Circle,
@@ -22,14 +25,7 @@ import { formatFleetCell, registerFleetOptionLabels } from "@/components/fleet/f
import { useFleetViewMode } from "@/components/fleet/useFleetViewMode";
import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import { useFleetList, useFleetMutations } from "@/hooks/fleet/useFleet";
import { useCargoTypes } from "@/hooks/use-cargo-types";
import { useContainerTypes } from "@/hooks/use-container-types";
import { useToast } from "@/hooks/use-toast";
import { useWagonTypes } from "@/hooks/use-wagon-types";
import { useContainers } from "@/hooks/useContainers";
import { useRouteYards } from "@/hooks/useRoutes";
import { useWagons } from "@/hooks/useWagons";
import {
FLEET_SELECT_NONE,
getFleetResource,
@@ -90,15 +86,31 @@ const FleetResourcePage = () => {
return filters;
}, [slug, listFilterValues, search]);
const { data: allRows = [], isLoading, isError, error } = useFleetList(slug, serverListFilters);
const { create, update, remove } = useFleetMutations(slug);
const { data: allRows = [], isLoading, isError, error } = useQuery(
api.fleet.list.queryOptions({ input: { slug, filters: serverListFilters } }),
);
const create = useMutation(api.fleet.create.mutationOptions());
const update = useMutation(api.fleet.update.mutationOptions());
const remove = useMutation(api.fleet.remove.mutationOptions());
const { data: wagonTypes = [], isLoading: wagonTypesLoading } = useWagonTypes();
const { data: containerTypes = [], isLoading: containerTypesLoading } = useContainerTypes();
const { data: cargoTypes = [], isLoading: cargoTypesLoading } = useCargoTypes();
const { data: wagons = [], isLoading: wagonsLoading } = useWagons();
const { data: containers = [], isLoading: containersLoading } = useContainers();
const { data: yards = [], isLoading: yardsLoading } = useRouteYards();
const { data: wagonTypes = [], isLoading: wagonTypesLoading } = useQuery(
api.wagonTypes.list.queryOptions(),
);
const { data: containerTypes = [], isLoading: containerTypesLoading } = useQuery(
api.containerTypes.list.queryOptions({ staleTime: Infinity }),
);
const { data: cargoTypes = [], isLoading: cargoTypesLoading } = useQuery(
api.cargoTypes.list.queryOptions({ staleTime: Infinity }),
);
const { data: wagons = [], isLoading: wagonsLoading } = useQuery(
api.wagons.list.queryOptions({ input: {} }),
);
const { data: containers = [], isLoading: containersLoading } = useQuery(
api.containers.list.queryOptions(),
);
const { data: yards = [], isLoading: yardsLoading } = useQuery(
api.routes.yards.queryOptions(),
);
useEffect(() => {
setPagination((prev) => ({ pageIndex: 0, pageSize: prev.pageSize }));
@@ -316,10 +328,10 @@ const FleetResourcePage = () => {
const handleFormSubmit = async (values: Record<string, unknown>) => {
try {
if (editing && "id" in editing) {
await update.mutateAsync({ id: String(editing.id), data: values });
await update.mutateAsync({ slug, id: String(editing.id), data: values });
toast({ title: `${config.entityLabel} updated` });
} else {
await create.mutateAsync(values);
await create.mutateAsync({ slug, data: values });
toast({ title: `${config.entityLabel} created` });
}
setFormOpen(false);
@@ -335,7 +347,7 @@ const FleetResourcePage = () => {
const handleRemove = async () => {
if (!removeTarget || !("id" in removeTarget)) return;
try {
await remove.mutateAsync(String(removeTarget.id));
await remove.mutateAsync({ slug, id: String(removeTarget.id) });
toast({
title: config.removeSuccessMessage ?? `${config.entityLabel} removed`,
});

View File

@@ -17,18 +17,14 @@ import {
Tooltip,
} from "@mantine/core";
import { useMutation, useQuery } from "@tanstack/react-query";
import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
import FleetToolbar from "@/components/fleet/FleetToolbar";
import { useFleetViewMode } from "@/components/fleet/useFleetViewMode";
import RuleEngineListFooter from "@/components/ruleEngine/RuleEngineListFooter";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import {
useCreateRoute,
useDeactivateRoute,
useRouteYards,
useRoutes,
useUpdateRoute,
} from "@/hooks/useRoutes";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import type { RouteRecord, YardRef } from "@/services/routes.service";
import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common";
@@ -71,11 +67,11 @@ export default function RoutesPage() {
const { pagination, setPagination } = usePagination({ pageSize: 10 });
const { toast } = useToast();
const routesQuery = useRoutes();
const yardsQuery = useRouteYards();
const createMutation = useCreateRoute();
const updateMutation = useUpdateRoute();
const deactivateMutation = useDeactivateRoute();
const routesQuery = useQuery(api.routes.list.queryOptions());
const yardsQuery = useQuery(api.routes.yards.queryOptions());
const createMutation = useMutation(api.routes.create.mutationOptions());
const updateMutation = useMutation(api.routes.update.mutationOptions());
const deactivateMutation = useMutation(api.routes.deactivate.mutationOptions());
const filteredRoutes = useMemo(() => {
const query = search.trim().toLowerCase();

View File

@@ -22,8 +22,10 @@ import {
} from "lucide-react";
import { useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
import { usePaymentList, usePaymentSummary } from "@/hooks/usePayments";
import { api } from "@/services/api";
import type { PaymentMethod, PaymentRow } from "@/services/payments.service";
import {
Badge,
@@ -106,8 +108,12 @@ export default function PaymentsPage() {
[query, statuses, method, pagination.pageIndex, pagination.pageSize],
);
const { data, isLoading, isError } = usePaymentList(filter);
const { data: summary, isLoading: summaryLoading } = usePaymentSummary();
const { data, isLoading, isError } = useQuery(
api.payments.list.queryOptions({ input: { filter } }),
);
const { data: summary, isLoading: summaryLoading } = useQuery(
api.payments.summary.queryOptions({ staleTime: 30_000 }),
);
const rows = data?.items ?? [];
const total = data?.total ?? 0;

View File

@@ -46,7 +46,8 @@ import {
import { RouteCorridor } from "@/components/trainScheduling/scheduleVisuals";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import { FREIGHT_BRAND, FREIGHT_BRAND_DARK } from "@/theme/freight-brand";
import { useBatchBoard } from "@/hooks/trainScheduling/useTrainScheduling";
import { useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import type { BatchBoardSchedule } from "@/types/trainScheduling";
const fmtTons = (n: number) =>
@@ -368,7 +369,9 @@ function CardSkeleton() {
export default function BatchBoardPage() {
const navigate = useNavigate();
const { data, isLoading, isError, isFetching, refetch } = useBatchBoard();
const { data, isLoading, isError, isFetching, refetch } = useQuery(
api.trainScheduling.batchBoard.queryOptions({ refetchInterval: 30_000 }),
);
const { viewMode, setViewMode } = useFleetViewMode("batch-board");
const { pagination, setPagination } = usePagination({ pageSize: 10 });
const [search, setSearch] = useState("");

View File

@@ -52,11 +52,8 @@ import {
WindowStatusPill,
} from "@/components/trainScheduling/batchVisuals";
import { RouteCorridor } from "@/components/trainScheduling/scheduleVisuals";
import {
useBatchBoardDetail,
useRunAllocation,
useScheduleDetail,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import type {
BatchBoardBookingDetail,
@@ -429,8 +426,16 @@ export default function BatchScheduleDetailPage() {
const { scheduleId } = useParams<{ scheduleId: string }>();
const navigate = useNavigate();
const { toast } = useToast();
const { data, isLoading, isFetching, refetch } = useBatchBoardDetail(scheduleId);
const runAllocation = useRunAllocation(scheduleId ?? "");
const { data, isLoading, isFetching, refetch } = useQuery(
api.trainScheduling.batchBoardDetail.queryOptions({
input: { scheduleId: scheduleId ?? "" },
enabled: Boolean(scheduleId),
refetchInterval: 30_000,
}),
);
const runAllocation = useMutation(
api.trainScheduling.runAllocation.mutationOptions(),
);
const hasAssignedWagons = useMemo(
() =>
@@ -443,7 +448,12 @@ export default function BatchScheduleDetailPage() {
[data],
);
const scheduleDetailQuery = useScheduleDetail(scheduleId, "CONTAINER");
const scheduleDetailQuery = useQuery(
api.trainScheduling.scheduleDetail.queryOptions({
input: { id: scheduleId ?? "", freightType: "CONTAINER" },
enabled: Boolean(scheduleId),
}),
);
// Batch bookings by state for the composition side panel (payment / expired lists).
const batchBookings = useMemo(() => {
@@ -550,7 +560,7 @@ export default function BatchScheduleDetailPage() {
const handleRunAllocation = () => {
runAllocation
.mutateAsync()
.mutateAsync({ scheduleId: scheduleId ?? "" })
.then((result) => {
const failed = result.issues.filter((i) => i.status === "FAILED").length;
const deferred = result.deferred.length;

View File

@@ -28,7 +28,8 @@ import { PageContainer } from "@/components/page";
import { RouteCorridorTrack } from "@/components/trainScheduling/RouteCorridorTrack";
import { RouteCorridor, StatusPill } from "@/components/trainScheduling/scheduleVisuals";
import { freightBrand } from "@/theme/freight-brand";
import { useTrainTrack, useScheduleMutations } from "@/hooks/trainScheduling/useTrainScheduling";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
const parseError = (error: unknown, fallback: string) => {
@@ -81,8 +82,15 @@ function MetaStat({
export default function TrainScheduleTrackPage() {
const { scheduleId } = useParams<{ scheduleId: string }>();
const { toast } = useToast();
const trackQuery = useTrainTrack(scheduleId);
const { recordCheckpoint } = useScheduleMutations(scheduleId);
const trackQuery = useQuery(
api.trainScheduling.trainTrack.queryOptions({
input: { id: scheduleId ?? "" },
enabled: Boolean(scheduleId),
}),
);
const recordCheckpoint = useMutation(
api.trainScheduling.recordCheckpoint.mutationOptions(),
);
if (trackQuery.isLoading) {
return (

View File

@@ -56,11 +56,8 @@ import { shouldShowContainerPlacementStep } from "@/components/trainScheduling/s
import { TrainCompositionDiagram } from "@/components/trainScheduling/TrainCompositionDiagram";
import { WagonPlanGrid } from "@/components/trainScheduling/WagonPlanGrid";
import { WorkflowRail, WorkflowStep } from "@/components/trainScheduling/WorkflowStep";
import {
useEligibleBookings,
useScheduleDetail,
useScheduleMutations,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import type {
ContainerPlacement,
@@ -91,7 +88,12 @@ export default function TrainScheduleV2DetailPage() {
const [maintenanceOpen, setMaintenanceOpen] = useState(false);
const autoPreviewedRef = useRef(false);
const detailQuery = useScheduleDetail(scheduleId);
const detailQuery = useQuery(
api.trainScheduling.scheduleDetail.queryOptions({
input: { id: scheduleId ?? "" },
enabled: Boolean(scheduleId),
}),
);
const schedule = detailQuery.data;
const freightType: FreightType | undefined = schedule?.freightType as FreightType | undefined;
@@ -111,12 +113,17 @@ export default function TrainScheduleV2DetailPage() {
const eligibleFreightType =
freightType === "CONTAINER" || freightType === "BULK" ? freightType : undefined;
const eligibleQuery = useEligibleBookings(
eligibleFilters,
Boolean(schedule),
eligibleFreightType,
const eligibleQuery = useQuery(
api.trainScheduling.eligibleBookings.queryOptions({
input: { filters: eligibleFilters, freightType: eligibleFreightType },
enabled: Boolean(schedule),
}),
);
const { preview, assign, unassign, finalize, dispatch } = useScheduleMutations(scheduleId);
const preview = useMutation(api.trainScheduling.preview.mutationOptions());
const assign = useMutation(api.trainScheduling.assignBookings.mutationOptions());
const unassign = useMutation(api.trainScheduling.unassignBooking.mutationOptions());
const finalize = useMutation(api.trainScheduling.finalizeSchedule.mutationOptions());
const dispatch = useMutation(api.trainScheduling.dispatchSchedule.mutationOptions());
const assignedIds = useMemo(
() => (schedule?.bookings ?? []).map((b) => b.id),

View File

@@ -39,13 +39,9 @@ import {
RouteCorridor,
StatusPill,
} from "@/components/trainScheduling/scheduleVisuals";
import {
useAvailableLocomotives,
useScheduleList,
useScheduleMutations,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useToast } from "@/hooks/use-toast";
import { useRoutes } from "@/hooks/useRoutes";
import type { TrainScheduleListItem } from "@/types/trainScheduling";
import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common";
@@ -88,10 +84,17 @@ export default function TrainScheduleV2ListPage() {
const [scheduleDate, setScheduleDate] = useState("");
const [locomotiveId, setLocomotiveId] = useState("");
const schedulesQuery = useScheduleList();
const routesQuery = useRoutes();
const locomotivesQuery = useAvailableLocomotives(routeId || undefined);
const { create, cancel } = useScheduleMutations();
const schedulesQuery = useQuery(
api.trainScheduling.scheduleList.queryOptions({ input: {} }),
);
const routesQuery = useQuery(api.routes.list.queryOptions());
const locomotivesQuery = useQuery(
api.trainScheduling.availableLocomotives.queryOptions({
input: { routeId: routeId || undefined },
}),
);
const create = useMutation(api.trainScheduling.createSchedule.mutationOptions());
const cancel = useMutation(api.trainScheduling.cancelSchedule.mutationOptions());
const activeRoutes = useMemo(
() => (routesQuery.data ?? []).filter((r) => r.isActive),

View File

@@ -2,13 +2,17 @@ import { useParams, Link } from "react-router-dom";
import { ArrowLeft } from "lucide-react";
import { Badge, Button, Card, Group, Loader, SimpleGrid, Stack, Text } from "@mantine/core";
import { useQuery } from "@tanstack/react-query";
import { AssignWagonDialog } from "@/components/wagons/AssignWagonDialog";
import { WagonsTable } from "@/components/wagons/WagonsTable";
import { useTrain } from "@/hooks/useTrains";
import { api } from "@/services/api";
export default function TrainDetailPage() {
const { id } = useParams<{ id: string }>();
const { data: train, isLoading } = useTrain(id!);
const { data: train, isLoading } = useQuery(
api.trains.getById.queryOptions({ input: { id: id ?? "" }, enabled: !!id }),
);
if (isLoading) {
return (

View File

@@ -42,7 +42,68 @@ import {
type Cargo,
type DeliverCargoPayload,
} from "./cargoService";
import { containerService, type Container } from "./containerService";
import { containerTypesService } from "./container-types.service";
import {
wagonService,
type Wagon,
type WagonListFilters,
} from "./wagon.service";
import { wagonTypesService, type WagonType } from "./wagon-types.service";
import { trainService, type Train } from "./trains.service";
import {
locomotivesService,
type Locomotive,
type SaveLocomotivePayload,
} from "./locomotives.service";
import { cargoTypesService } from "./cargo-types.service";
import {
fleetService,
type FleetListFilters,
type FleetRecord,
} from "./fleet/fleet.service";
import type { FleetResourceSlug } from "@/pages/fleet/config/resources";
import {
paymentsService,
type PaginatedPayments,
type PaymentListFilter,
type PaymentSummary,
} from "./payments.service";
import {
signaturesService,
type SavedSignature,
type SaveSignaturePayload,
} from "./signatures.service";
import { warehouseService } from "./warehouse.service";
import { trainSchedulingService } from "./trainScheduling.service";
import {
routesService,
type RouteRecord,
type SaveRoutePayload,
type YardRef,
} from "./routes.service";
import type {
AssignBookingsPayload,
BatchBoardSchedule,
BatchBoardScheduleDetail,
BookableSchedule,
CompositionRemovalEntry,
CreateTrainSchedulePayload,
EligibleContainerBookingsResponse,
FreightType,
LocomotiveRecord,
PinWagonsPayload,
RecordCheckpointPayload,
TrainScheduleDetail,
TrainScheduleFilters,
TrainScheduleListItem,
TrainSchedulePreviewPayload,
TrainSchedulePreviewResponse,
TrainTrackResponse,
UnassignedBookingsResponse,
WagonAllocationAttemptResult,
YardOption,
} from "@/types/trainScheduling";
import type {
AllocationCriteria,
AllocationPreviewResult,
@@ -104,7 +165,333 @@ const INVENTORY_INVALIDATIONS: ReadonlyArray<readonly unknown[]> = [
["warehouses"],
];
/**
* Train-scheduling mutations broadly affect the schedule board and bookings.
* The grouped hooks invalidated TRAIN_SCHEDULING.ROOT + BOOKINGS.ROOT; since
* every train-scheduling key is prefixed with `"train-scheduling"`, the two
* roots below cover all of them via React Query's prefix matching.
*/
const TRAIN_SCHEDULING_INVALIDATIONS: ReadonlyArray<readonly unknown[]> = [
QUERY_KEYS.TRAIN_SCHEDULING.ROOT,
QUERY_KEYS.BOOKINGS.ROOT,
];
export const api = {
trainScheduling: {
// ── Queries ────────────────────────────────────────────────────────────
scheduleList: endpoint<{ freightType?: FreightType }, TrainScheduleListItem[]>(
"train-scheduling",
"schedules",
({ freightType }) => trainSchedulingService.listSchedules(freightType),
() => QUERY_KEYS.TRAIN_SCHEDULING.schedules(),
),
batchBoard: endpoint<void, BatchBoardSchedule[]>(
"train-scheduling",
"batch-board",
() => trainSchedulingService.getBatchBoard(),
() => QUERY_KEYS.TRAIN_SCHEDULING.batchBoard(),
),
batchBoardDetail: endpoint<{ scheduleId: string }, BatchBoardScheduleDetail>(
"train-scheduling",
"batch-board-detail",
({ scheduleId }) => trainSchedulingService.getBatchBoardDetail(scheduleId),
({ scheduleId }) => QUERY_KEYS.TRAIN_SCHEDULING.batchBoardDetail(scheduleId),
),
scheduleDetail: endpoint<
{ id: string; freightType?: FreightType },
TrainScheduleDetail
>(
"train-scheduling",
"schedule-detail",
({ id, freightType }) =>
trainSchedulingService.getScheduleById(id, freightType),
({ id }) => QUERY_KEYS.TRAIN_SCHEDULING.scheduleById(id),
),
eligibleBookings: endpoint<
{ filters?: TrainScheduleFilters; freightType?: FreightType },
EligibleContainerBookingsResponse
>(
"train-scheduling",
"eligible-bookings",
({ filters, freightType }) =>
trainSchedulingService.getEligibleBookings(filters, freightType),
({ filters, freightType }) =>
QUERY_KEYS.TRAIN_SCHEDULING.eligible(freightType, filters),
),
availableLocomotives: endpoint<{ routeId?: string }, LocomotiveRecord[]>(
"train-scheduling",
"locomotives",
({ routeId }) => trainSchedulingService.getAvailableLocomotives(routeId),
({ routeId }) => QUERY_KEYS.TRAIN_SCHEDULING.locomotives(routeId),
),
bookableSchedules: endpoint<
{ originYardId?: string | null; destinationYardId?: string | null },
BookableSchedule[]
>(
"train-scheduling",
"bookable",
({ originYardId, destinationYardId }) =>
trainSchedulingService.getBookableSchedules(
originYardId ?? undefined,
destinationYardId ?? undefined,
),
({ originYardId, destinationYardId }) => [
...QUERY_KEYS.TRAIN_SCHEDULING.ROOT,
"bookable",
originYardId ?? "",
destinationYardId ?? "",
],
),
availableDays: endpoint<
{ originYardId?: string | null; destinationYardId?: string | null },
string[]
>(
"train-scheduling",
"available-days",
({ originYardId, destinationYardId }) =>
trainSchedulingService.getAvailableDays(
originYardId ?? undefined,
destinationYardId ?? undefined,
),
({ originYardId, destinationYardId }) => [
...QUERY_KEYS.TRAIN_SCHEDULING.ROOT,
"available-days",
originYardId ?? "",
destinationYardId ?? "",
],
),
trainTrack: endpoint<{ id: string }, TrainTrackResponse>(
"train-scheduling",
"track",
({ id }) => trainSchedulingService.getTrack(id),
({ id }) => QUERY_KEYS.TRAIN_SCHEDULING.track(id),
),
unassignedBookings: endpoint<
{ scheduleId: string },
UnassignedBookingsResponse
>(
"train-scheduling",
"unassigned-bookings",
({ scheduleId }) =>
trainSchedulingService.getUnassignedBookings(scheduleId),
({ scheduleId }) =>
QUERY_KEYS.TRAIN_SCHEDULING.unassignedBookings(scheduleId),
),
compositionRemovals: endpoint<
{ scheduleId: string },
CompositionRemovalEntry[]
>(
"train-scheduling",
"composition-removals",
({ scheduleId }) =>
trainSchedulingService.getCompositionRemovals(scheduleId),
({ scheduleId }) =>
QUERY_KEYS.TRAIN_SCHEDULING.compositionRemovals(scheduleId),
),
// ── Mutations ──────────────────────────────────────────────────────────
runAllocation: endpoint<{ scheduleId: string }, WagonAllocationAttemptResult>(
"train-scheduling",
"run-allocation",
({ scheduleId }) => trainSchedulingService.runAllocation(scheduleId),
undefined,
() => [QUERY_KEYS.TRAIN_SCHEDULING.ROOT],
),
runBatch: endpoint<string, BatchBoardScheduleDetail>(
"train-scheduling",
"run-batch",
(id) => trainSchedulingService.runBatch(id),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
setBookingWindow: endpoint<
{ id: string; status: "OPEN" | "CLOSED" },
TrainScheduleDetail
>(
"train-scheduling",
"set-booking-window",
({ id, status }) => trainSchedulingService.setBookingWindow(id, status),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
markBookingPaid: endpoint<string, void>(
"train-scheduling",
"mark-booking-paid",
(bookingId) => trainSchedulingService.markBookingPaid(bookingId),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
expireBooking: endpoint<string, void>(
"train-scheduling",
"expire-booking",
(bookingId) => trainSchedulingService.expireBooking(bookingId),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
moveBookingSchedule: endpoint<
{ bookingId: string; trainScheduleId: string },
void
>(
"train-scheduling",
"move-booking-schedule",
({ bookingId, trainScheduleId }) =>
trainSchedulingService.moveBookingSchedule(bookingId, trainScheduleId),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
createSchedule: endpoint<
{ freightType?: FreightType; payload: CreateTrainSchedulePayload },
TrainScheduleDetail
>(
"train-scheduling",
"create-schedule",
({ freightType, payload }) =>
trainSchedulingService.createSchedule(payload, freightType),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
preview: endpoint<
{ freightType?: FreightType; payload: TrainSchedulePreviewPayload },
TrainSchedulePreviewResponse
>("train-scheduling", "preview", ({ freightType, payload }) =>
trainSchedulingService.preview(payload, freightType),
),
assignBookings: endpoint<
{ id: string; freightType?: FreightType; payload: AssignBookingsPayload },
TrainScheduleDetail
>(
"train-scheduling",
"assign-bookings",
({ id, freightType, payload }) =>
trainSchedulingService.assignBookings(id, payload, freightType),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
assignUnassignedBooking: endpoint<
{ id: string; bookingId: string },
TrainScheduleDetail
>(
"train-scheduling",
"assign-unassigned-booking",
({ id, bookingId }) =>
trainSchedulingService.assignUnassignedBooking(id, bookingId),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
unassignBooking: endpoint<
{ id: string; bookingId: string },
TrainScheduleDetail
>(
"train-scheduling",
"unassign-booking",
({ id, bookingId }) =>
trainSchedulingService.unassignBooking(id, bookingId),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
pinWagons: endpoint<{ id: string; payload: PinWagonsPayload }, TrainScheduleDetail>(
"train-scheduling",
"pin-wagons",
({ id, payload }) => trainSchedulingService.pinWagons(id, payload),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
finalizeSchedule: endpoint<string, TrainScheduleDetail>(
"train-scheduling",
"finalize-schedule",
(id) => trainSchedulingService.finalizeSchedule(id),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
dispatchSchedule: endpoint<string, TrainScheduleDetail>(
"train-scheduling",
"dispatch-schedule",
(id) => trainSchedulingService.dispatchSchedule(id),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
cancelSchedule: endpoint<
{ id: string; freightType?: FreightType },
TrainScheduleDetail
>(
"train-scheduling",
"cancel-schedule",
({ id, freightType }) =>
trainSchedulingService.cancelSchedule(id, freightType ?? "CONTAINER"),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
recordCheckpoint: endpoint<
{ id: string; payload: RecordCheckpointPayload },
TrainTrackResponse
>(
"train-scheduling",
"record-checkpoint",
({ id, payload }) => trainSchedulingService.recordCheckpoint(id, payload),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
arriveSchedule: endpoint<string, TrainScheduleDetail>(
"train-scheduling",
"arrive-schedule",
(id) => trainSchedulingService.arriveSchedule(id),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
removeWagonSlot: endpoint<
{ scheduleId: string; wagonId: string },
TrainScheduleDetail
>(
"train-scheduling",
"remove-wagon-slot",
({ scheduleId, wagonId }) =>
trainSchedulingService.removeWagonSlot(scheduleId, wagonId),
undefined,
() => [QUERY_KEYS.TRAIN_SCHEDULING.ROOT],
),
updateContainerItem: endpoint<
{ scheduleId: string; itemId: string; containerNumber: string | null },
{ id: string; containerNumber: string | null }
>(
"train-scheduling",
"update-container-item",
({ scheduleId, itemId, containerNumber }) =>
trainSchedulingService.updateContainerItem(scheduleId, itemId, {
containerNumber,
}),
undefined,
() => [QUERY_KEYS.TRAIN_SCHEDULING.ROOT],
),
},
warehouses: {
// ── Warehouses ─────────────────────────────────────────────────────────
list: endpoint<{ filter?: WarehouseFilter }, Warehouse[]>(
@@ -685,6 +1072,388 @@ export const api = {
),
},
routes: {
list: endpoint<void, RouteRecord[]>("routes", "list", () =>
routesService.getAll().then((r) => r.data),
),
yards: endpoint<void, YardRef[]>(
"routes",
"yards",
() => routesService.getYards().then((r) => r.data.data),
() => ["routes", "yards"],
),
create: endpoint<SaveRoutePayload, RouteRecord>(
"routes",
"create",
(payload) => routesService.create(payload).then((r) => r.data),
undefined,
() => [["routes"]],
),
update: endpoint<{ id: string; data: Partial<SaveRoutePayload> }, RouteRecord>(
"routes",
"update",
({ id, data }) => routesService.update(id, data).then((r) => r.data),
undefined,
() => [["routes"]],
),
deactivate: endpoint<string, void>(
"routes",
"deactivate",
(id) => routesService.deactivate(id).then(() => undefined),
undefined,
() => [["routes"]],
),
},
stations: {
list: endpoint<void, YardOption[]>(
"train-scheduling",
"stations",
() => trainSchedulingService.getStations(),
() => QUERY_KEYS.TRAIN_SCHEDULING.stations(),
),
},
containers: {
list: endpoint<void, Container[]>("containers", "list", () =>
containerService.getAll().then((r) => r.data),
),
listByWagon: endpoint<{ wagonId: string }, Container[]>(
"containers",
"listByWagon",
({ wagonId }) => containerService.getByWagon(wagonId).then((r) => r.data),
({ wagonId }) => ["containers", "wagon", wagonId],
),
getById: endpoint<{ id: string }, Container>(
"containers",
"getById",
({ id }) => containerService.getById(id).then((r) => r.data),
),
create: endpoint<Partial<Container>, Container>(
"containers",
"create",
(payload) => containerService.create(payload).then((r) => r.data),
undefined,
() => [["containers"]],
),
update: endpoint<{ id: string; data: Partial<Container> }, Container>(
"containers",
"update",
({ id, data }) => containerService.update(id, data).then((r) => r.data),
undefined,
() => [["containers"]],
),
remove: endpoint<string, void>(
"containers",
"remove",
(id) => containerService.delete(id).then(() => undefined),
undefined,
() => [["containers"]],
),
assignToWagon: endpoint<
{ containerId: string; wagonId: string; position?: number },
Container
>(
"containers",
"assignToWagon",
({ containerId, wagonId, position }) =>
containerService
.assignToWagon(containerId, wagonId, position)
.then((r) => r.data),
undefined,
() => [["containers"]],
),
unassign: endpoint<string, void>(
"containers",
"unassign",
(containerId) =>
containerService.unassign(containerId).then(() => undefined),
undefined,
() => [["containers"]],
),
},
containerTypes: {
list: endpoint<void, unknown[]>("container-types", "list", () =>
containerTypesService.getContainerTypes(),
),
},
wagons: {
list: endpoint<{ filters?: WagonListFilters }, Wagon[]>(
"wagons",
"list",
({ filters }) => wagonService.getAll(filters ?? {}).then((r) => r.data),
({ filters }) => ["wagons", "list", filters ?? {}],
),
listByTrain: endpoint<{ trainId: string }, Wagon[]>(
"wagons",
"listByTrain",
({ trainId }) => wagonService.getByTrain(trainId).then((r) => r.data),
({ trainId }) => ["wagons", "train", trainId],
),
getById: endpoint<{ id: string }, Wagon>(
"wagons",
"getById",
({ id }) => wagonService.getById(id).then((r) => r.data),
),
assignToTrain: endpoint<
{ wagonId: string; trainId: string; sequenceNumber?: number },
Wagon
>(
"wagons",
"assignToTrain",
({ wagonId, trainId, sequenceNumber }) =>
wagonService
.assignToTrain(wagonId, trainId, sequenceNumber)
.then((r) => r.data),
undefined,
() => [["wagons"]],
),
unassign: endpoint<string, void>(
"wagons",
"unassign",
(wagonId) => wagonService.unassign(wagonId).then(() => undefined),
undefined,
() => [["wagons"]],
),
reorder: endpoint<{ trainId: string; wagonIds: string[] }, Wagon[]>(
"wagons",
"reorder",
({ trainId, wagonIds }) =>
wagonService.reorder(trainId, wagonIds).then((r) => r.data),
undefined,
() => [["wagons"]],
),
create: endpoint<Partial<Wagon>, Wagon>(
"wagons",
"create",
(payload) => wagonService.create(payload).then((r) => r.data),
undefined,
() => [["wagons"]],
),
update: endpoint<{ id: string; data: Partial<Wagon> }, Wagon>(
"wagons",
"update",
({ id, data }) => wagonService.update(id, data).then((r) => r.data),
undefined,
() => [["wagons"]],
),
remove: endpoint<string, void>(
"wagons",
"remove",
(id) => wagonService.delete(id).then(() => undefined),
undefined,
() => [["wagons"]],
),
},
trains: {
list: endpoint<void, Train[]>(
"trains",
"list",
() => trainService.getAll().then((r) => r.data),
() => ["trains", "list"],
),
getById: endpoint<{ id: string }, Train>(
"trains",
"getById",
({ id }) => trainService.getById(id).then((r) => r.data),
({ id }) => ["trains", "detail", id],
),
create: endpoint<Partial<Train>, Train>(
"trains",
"create",
(payload) => trainService.create(payload).then((r) => r.data),
undefined,
() => [["trains"]],
),
update: endpoint<{ id: string; data: Partial<Train> }, Train>(
"trains",
"update",
({ id, data }) => trainService.update(id, data).then((r) => r.data),
undefined,
() => [["trains"]],
),
remove: endpoint<string, void>(
"trains",
"remove",
(id) => trainService.delete(id).then(() => undefined),
undefined,
() => [["trains"]],
),
},
locomotives: {
list: endpoint<void, Locomotive[]>(
"locomotives",
"list",
() => locomotivesService.getAll().then((r) => r.data),
() => ["locomotives"],
),
create: endpoint<Partial<SaveLocomotivePayload>, Locomotive>(
"locomotives",
"create",
(payload) => locomotivesService.create(payload).then((r) => r.data),
undefined,
() => [["locomotives"]],
),
update: endpoint<
{ id: string; data: Partial<SaveLocomotivePayload> },
Locomotive
>(
"locomotives",
"update",
({ id, data }) => locomotivesService.update(id, data).then((r) => r.data),
undefined,
() => [["locomotives"]],
),
decommission: endpoint<string, void>(
"locomotives",
"decommission",
(id) => locomotivesService.decommission(id).then(() => undefined),
undefined,
() => [["locomotives"]],
),
},
cargoTypes: {
list: endpoint<void, unknown[]>("cargo-types", "list", () =>
cargoTypesService.getCargoTypes(),
),
},
payments: {
list: endpoint<{ filter?: PaymentListFilter }, PaginatedPayments>(
"payments",
"list",
({ filter }) => paymentsService.list(filter),
({ filter }) => ["payments", "list", filter ?? {}],
),
summary: endpoint<void, PaymentSummary>(
"payments",
"summary",
() => paymentsService.getSummary(),
() => ["payments", "summary"],
),
},
signatures: {
mySignature: endpoint<void, SavedSignature | null>(
"me",
"signature",
() => signaturesService.getMySignature(),
() => ["me", "signature"],
),
save: endpoint<SaveSignaturePayload, SavedSignature | null>(
"me",
"save-signature",
(payload) => signaturesService.saveMySignature(payload),
undefined,
() => [["me", "signature"]],
),
},
fleet: {
list: endpoint<
{ slug: FleetResourceSlug; filters?: FleetListFilters },
FleetRecord[]
>(
"fleet",
"list",
({ slug, filters }) => fleetService.list(slug, filters),
({ slug, filters }) => [...QUERY_KEYS.FLEET.list(slug), filters ?? {}],
),
create: endpoint<
{ slug: FleetResourceSlug; data: Record<string, unknown> },
unknown
>(
"fleet",
"create",
({ slug, data }) => fleetService.create(slug, data),
undefined,
({ slug }) => [QUERY_KEYS.FLEET.list(slug)],
),
update: endpoint<
{ slug: FleetResourceSlug; id: string; data: Record<string, unknown> },
unknown
>(
"fleet",
"update",
({ slug, id, data }) => fleetService.update(slug, id, data),
undefined,
({ slug }) => [QUERY_KEYS.FLEET.list(slug)],
),
remove: endpoint<{ slug: FleetResourceSlug; id: string }, unknown>(
"fleet",
"remove",
({ slug, id }) => fleetService.remove(slug, id),
undefined,
({ slug }) => [QUERY_KEYS.FLEET.list(slug)],
),
},
wagonTypes: {
list: endpoint<void, WagonType[]>("wagon-types", "list", () =>
wagonTypesService.getWagonTypes(),
),
create: endpoint<Partial<WagonType>, WagonType>(
"wagon-types",
"create",
(payload) => wagonTypesService.create(payload).then((r) => r.data),
undefined,
() => [["wagon-types"]],
),
update: endpoint<{ id: string; data: Partial<WagonType> }, WagonType>(
"wagon-types",
"update",
({ id, data }) => wagonTypesService.update(id, data).then((r) => r.data),
undefined,
() => [["wagon-types"]],
),
remove: endpoint<string, void>(
"wagon-types",
"remove",
(id) => wagonTypesService.delete(id).then(() => undefined),
undefined,
() => [["wagon-types"]],
),
},
cargoes: {
list: endpoint<void, Cargo[]>("cargoes", "list", () =>
cargoService.getAll().then((r) => r.data),
@@ -778,46 +1547,68 @@ export const api = {
"file-upload-settings",
"create",
(payload) => fileUploadSettingsService.create(payload),
undefined,
() => [["file-upload-settings"]],
),
update: endpoint<
{ id: string; dto: UpdateFileUploadSettingDto },
FileUploadSetting
>("file-upload-settings", "update", ({ id, dto }) =>
fileUploadSettingsService.update(id, dto),
>(
"file-upload-settings",
"update",
({ id, dto }) => fileUploadSettingsService.update(id, dto),
undefined,
() => [["file-upload-settings"]],
),
remove: endpoint<{ id: string }, void>(
"file-upload-settings",
"remove",
({ id }) => fileUploadSettingsService.remove(id),
undefined,
() => [["file-upload-settings"]],
),
replaceFields: endpoint<
{ id: string; fields: CreateFileUploadFieldDto[] },
FileUploadField[]
>("file-upload-settings", "replaceFields", ({ id, fields }) =>
fileUploadSettingsService.replaceFields(id, fields),
>(
"file-upload-settings",
"replaceFields",
({ id, fields }) => fileUploadSettingsService.replaceFields(id, fields),
undefined,
() => [["file-upload-settings"]],
),
addField: endpoint<
{ settingId: string; dto: CreateFileUploadFieldDto },
FileUploadField
>("file-upload-settings", "addField", ({ settingId, dto }) =>
fileUploadSettingsService.addField(settingId, dto),
>(
"file-upload-settings",
"addField",
({ settingId, dto }) => fileUploadSettingsService.addField(settingId, dto),
undefined,
() => [["file-upload-settings"]],
),
updateField: endpoint<
{ fieldId: string; dto: UpdateFileUploadFieldDto },
FileUploadField
>("file-upload-settings", "updateField", ({ fieldId, dto }) =>
fileUploadSettingsService.updateField(fieldId, dto),
>(
"file-upload-settings",
"updateField",
({ fieldId, dto }) => fileUploadSettingsService.updateField(fieldId, dto),
undefined,
() => [["file-upload-settings"]],
),
removeField: endpoint<{ fieldId: string }, void>(
"file-upload-settings",
"removeField",
({ fieldId }) => fileUploadSettingsService.removeField(fieldId),
undefined,
() => [["file-upload-settings"]],
),
},
@@ -844,46 +1635,68 @@ export const api = {
"dropdown-settings",
"create",
(payload) => dropdownSettingsService.create(payload),
undefined,
() => [["dropdown-settings"]],
),
update: endpoint<
{ id: string; dto: UpdateDropdownSettingDto },
DropdownSetting
>("dropdown-settings", "update", ({ id, dto }) =>
dropdownSettingsService.update(id, dto),
>(
"dropdown-settings",
"update",
({ id, dto }) => dropdownSettingsService.update(id, dto),
undefined,
() => [["dropdown-settings"]],
),
remove: endpoint<{ id: string }, void>(
"dropdown-settings",
"remove",
({ id }) => dropdownSettingsService.remove(id),
undefined,
() => [["dropdown-settings"]],
),
replaceOptions: endpoint<
{ id: string; options: CreateDropdownOptionDto[] },
DropdownOption[]
>("dropdown-settings", "replaceOptions", ({ id, options }) =>
dropdownSettingsService.replaceOptions(id, options),
>(
"dropdown-settings",
"replaceOptions",
({ id, options }) => dropdownSettingsService.replaceOptions(id, options),
undefined,
() => [["dropdown-settings"]],
),
addOption: endpoint<
{ id: string; dto: CreateDropdownOptionDto },
DropdownOption
>("dropdown-settings", "addOption", ({ id, dto }) =>
dropdownSettingsService.addOption(id, dto),
>(
"dropdown-settings",
"addOption",
({ id, dto }) => dropdownSettingsService.addOption(id, dto),
undefined,
() => [["dropdown-settings"]],
),
updateOption: endpoint<
{ optionId: string; dto: UpdateDropdownOptionDto },
DropdownOption
>("dropdown-settings", "updateOption", ({ optionId, dto }) =>
dropdownSettingsService.updateOption(optionId, dto),
>(
"dropdown-settings",
"updateOption",
({ optionId, dto }) => dropdownSettingsService.updateOption(optionId, dto),
undefined,
() => [["dropdown-settings"]],
),
removeOption: endpoint<{ optionId: string }, void>(
"dropdown-settings",
"removeOption",
({ optionId }) => dropdownSettingsService.removeOption(optionId),
undefined,
() => [["dropdown-settings"]],
),
},

View File

@@ -4,9 +4,8 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"useDefineForClassFields": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
"@/*": ["./src/*"]
}
},
"include": ["src"]