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 { useState } from "react";
import { FileSignature, Loader2 } from "lucide-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 { ContractSignaturePad } from "@/components/bookings/ContractSignaturePad";
import { import {
Card, Card,
@@ -10,10 +13,6 @@ import {
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { useAuth } from "@/auth/useAuth"; import { useAuth } from "@/auth/useAuth";
import {
useMySignature,
useSaveSignature,
} from "@/hooks/useSavedSignature";
import { import {
Button, Button,
Dialog, Dialog,
@@ -33,8 +32,10 @@ import {
*/ */
export function MySignatureCard() { export function MySignatureCard() {
const { user } = useAuth(); const { user } = useAuth();
const { data: saved, isLoading } = useMySignature(); const { data: saved, isLoading } = useQuery(
const saveMutation = useSaveSignature(); api.signatures.mySignature.queryOptions({ staleTime: 60_000 }),
);
const saveMutation = useMutation(api.signatures.save.mutationOptions());
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [signerName, setSignerName] = useState(""); const [signerName, setSignerName] = useState("");
@@ -56,7 +57,13 @@ export function MySignatureCard() {
signerDisplayName: signerName.trim(), signerDisplayName: signerName.trim(),
signatureImageBase64: signatureData, 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, Weight,
} from "lucide-react"; } from "lucide-react";
import { import { useMutation, useQuery } from "@tanstack/react-query";
useAvailableLocomotives, import { api } from "@/services/api";
useEligibleBookings,
useScheduleList,
useScheduleMutations,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { useRoutes } from "@/hooks/useRoutes";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { trainSchedulingService } from "@/services/trainScheduling.service"; import { trainSchedulingService } from "@/services/trainScheduling.service";
import type { BookingDetail } from "@/types/booking"; import type { BookingDetail } from "@/types/booking";
@@ -133,13 +128,27 @@ export function AllocateBookingWizard({
[originId, destinationId], [originId, destinationId],
); );
const eligibleQuery = useEligibleBookings(eligibleFilters, opened); const eligibleQuery = useQuery(
const schedulesQuery = useScheduleList(); api.trainScheduling.eligibleBookings.queryOptions({
const routesQuery = useRoutes(); input: { filters: eligibleFilters },
const locomotivesQuery = useAvailableLocomotives( enabled: opened,
scheduleMode === "new" && routeId ? routeId : undefined, }),
); );
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(() => { useEffect(() => {
if (scheduleMode === "new") { if (scheduleMode === "new") {

View File

@@ -13,11 +13,10 @@ import {
} from "@mantine/core"; } from "@mantine/core";
import { CheckCircle2, Layers, Lock, LockOpen, PlayCircle, Repeat, XCircle } from "lucide-react"; 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 { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
import { import { api } from "@/services/api";
useBatchActions,
useBookableSchedules,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import type { TrainScheduleDetail } from "@/types/trainScheduling"; import type { TrainScheduleDetail } from "@/types/trainScheduling";
@@ -33,16 +32,31 @@ const windowColor: Record<string, string> = {
export function ScheduleBatchPanel({ schedule }: ScheduleBatchPanelProps) { export function ScheduleBatchPanel({ schedule }: ScheduleBatchPanelProps) {
const { toast } = useToast(); 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 windowStatus = (schedule as { bookingWindowStatus?: string }).bookingWindowStatus ?? "OPEN";
const locked = schedule.status === "DISPATCHED" || schedule.status === "ARRIVED"; const locked = schedule.status === "DISPATCHED" || schedule.status === "ARRIVED";
const [moveBookingId, setMoveBookingId] = useState<string | null>(null); const [moveBookingId, setMoveBookingId] = useState<string | null>(null);
const [moveTarget, setMoveTarget] = useState<string | null>(null); const [moveTarget, setMoveTarget] = useState<string | null>(null);
const { data: targets } = useBookableSchedules( const { data: targets } = useQuery(
schedule.originStation?.id, api.trainScheduling.bookableSchedules.queryOptions({
schedule.destinationStation?.id, input: {
originYardId: schedule.originStation?.id,
destinationYardId: schedule.destinationStation?.id,
},
enabled: Boolean(
schedule.originStation?.id && schedule.destinationStation?.id,
),
}),
); );
const moveOptions = useMemo( 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 { TrainScheduleDetail } from "@/types/trainScheduling";
import type { BookingDetailData } from "./BookingDetailModal"; import type { BookingDetailData } from "./BookingDetailModal";
import { RemoveBookingConfirmModal, type RemovalTarget } from "./RemoveBookingConfirmModal"; 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 { useToast } from "@/hooks/use-toast";
import { freightBrand } from "@/theme/freight-brand"; import { freightBrand } from "@/theme/freight-brand";
@@ -22,7 +23,7 @@ export const AssignedBookingsPanel = ({
onSelect, onSelect,
}: AssignedBookingsPanelProps) => { }: AssignedBookingsPanelProps) => {
const { toast } = useToast(); const { toast } = useToast();
const unassign = useScheduleMutations(scheduleId).unassign; const unassign = useMutation(api.trainScheduling.unassignBooking.mutationOptions());
const isDispatched = scheduleDetail.status === "DISPATCHED"; const isDispatched = scheduleDetail.status === "DISPATCHED";
const [removalTarget, setRemovalTarget] = useState<RemovalTarget | null>(null); const [removalTarget, setRemovalTarget] = useState<RemovalTarget | null>(null);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,11 +9,10 @@ import {
TextInput, TextInput,
} from '@mantine/core'; } from '@mantine/core';
import { useMutation } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
import { api } from '@/services/api'; import { api } from '@/services/api';
import { useToast } from '@/hooks/use-toast'; import { useToast } from '@/hooks/use-toast';
import { useStations } from '@/hooks/useStations';
import type { SaveWarehousePayload, Warehouse, WarehouseType } from '@/types/warehouse'; import type { SaveWarehousePayload, Warehouse, WarehouseType } from '@/types/warehouse';
import { extractErrorMessage, statusOptions, warehouseTypeOptions } from './options'; import { extractErrorMessage, statusOptions, warehouseTypeOptions } from './options';
@@ -52,7 +51,9 @@ export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWareh
const { toast } = useToast(); const { toast } = useToast();
const createMutation = useMutation(api.warehouses.create.mutationOptions()); const createMutation = useMutation(api.warehouses.create.mutationOptions());
const updateMutation = useMutation(api.warehouses.update.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 [form, setForm] = useState<FormState>(emptyForm());
const stationOptions = (stations ?? []).map((s) => ({ value: s.id, label: `${s.name} (${s.code})` })); 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 { ActionIcon, Card, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { Building2, Eye, MapPin, Pencil } from 'lucide-react'; 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 type { Warehouse } from '@/types/warehouse';
import { WarehouseStatusBadge, WarehouseTypeBadge } from './badges'; import { WarehouseStatusBadge, WarehouseTypeBadge } from './badges';
import { formatCapacity } from './options'; import { formatCapacity } from './options';
@@ -14,7 +16,9 @@ interface WarehouseCardViewProps {
} }
export function WarehouseCardView({ warehouses, onView, onEdit }: 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( const stationNameById = useMemo(
() => new Map((stations ?? []).map((s) => [s.id, s.name])), () => new Map((stations ?? []).map((s) => [s.id, s.name])),
[stations], [stations],

View File

@@ -3,7 +3,9 @@ import { ActionIcon, Group, Text } from '@mantine/core';
import { Eye, Pencil } from 'lucide-react'; import { Eye, Pencil } from 'lucide-react';
import { DataTable, type ColumnDef } from '@edr/ui-common'; 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 type { Warehouse } from '@/types/warehouse';
import { WarehouseStatusBadge, WarehouseTypeBadge } from './badges'; import { WarehouseStatusBadge, WarehouseTypeBadge } from './badges';
import { formatCapacity } from './options'; import { formatCapacity } from './options';
@@ -15,7 +17,9 @@ interface WarehouseTableProps {
} }
export function WarehouseTable({ warehouses, onView, onEdit }: 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( const stationNameById = useMemo(
() => new Map((stations ?? []).map((s) => [s.id, s.name])), () => new Map((stations ?? []).map((s) => [s.id, s.name])),
[stations], [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 { 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 { RuleEngineResourceSlug } from "@/types/rule-engine";
import type { TrainScheduleFilters } from "@/types/trainScheduling";
export const QUERY_KEYS = { export const QUERY_KEYS = {
USERS: { 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 { import {
ActionIcon, ActionIcon,
Badge, Badge,
@@ -23,6 +19,8 @@ import {
ThemeIcon, ThemeIcon,
Tooltip, Tooltip,
} from "@mantine/core"; } from "@mantine/core";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { isAxiosError } from "axios";
import { import {
AlertTriangle, AlertTriangle,
ArrowLeft, ArrowLeft,
@@ -40,14 +38,16 @@ import {
Trash2, Trash2,
Weight, Weight,
} from "lucide-react"; } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast"; 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 { api } from "@/auth/http";
import { unwrap } from "@/utils/endpoint"; import Breadcrumbs from "@/components/ui/Breadcrumbs";
import { URL_CONSTANTS } from "@/constants/URLS"; 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 { interface CompanyOption {
id: string; 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 // 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. // train, no capacity). The batch engine assigns the train after booking.
const { data: availableDays, isLoading: daysLoading } = useAvailableDays( const { data: availableDays, isLoading: daysLoading } = useQuery(
originYardId, appApi.trainScheduling.availableDays.queryOptions({
destinationYardId, input: { originYardId, destinationYardId },
enabled: Boolean(originYardId && destinationYardId),
}),
); );
const dayOptions = (availableDays ?? []).map((day) => ({ const dayOptions = (availableDays ?? []).map((day) => ({
value: day, value: day,
@@ -392,7 +394,7 @@ export default function NewBookingPage() {
</Button> </Button>
</Group> </Group>
<Grid gutter="lg" mt="lg"> <Grid gap="lg" mt="lg">
{/* LEFT — form */} {/* LEFT — form */}
<Grid.Col span={{ base: 12, lg: 8 }}> <Grid.Col span={{ base: 12, lg: 8 }}>
<Stack gap="lg"> <Stack gap="lg">
@@ -453,7 +455,6 @@ export default function NewBookingPage() {
value={originYardId} value={originYardId}
onChange={(v) => { onChange={(v) => {
setOriginYardId(v); setOriginYardId(v);
setTrainScheduleId(null);
}} }}
searchable searchable
disabled={isLoading} disabled={isLoading}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -46,7 +46,8 @@ import {
import { RouteCorridor } from "@/components/trainScheduling/scheduleVisuals"; import { RouteCorridor } from "@/components/trainScheduling/scheduleVisuals";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles"; import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import { FREIGHT_BRAND, FREIGHT_BRAND_DARK } from "@/theme/freight-brand"; 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"; import type { BatchBoardSchedule } from "@/types/trainScheduling";
const fmtTons = (n: number) => const fmtTons = (n: number) =>
@@ -368,7 +369,9 @@ function CardSkeleton() {
export default function BatchBoardPage() { export default function BatchBoardPage() {
const navigate = useNavigate(); 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 { viewMode, setViewMode } = useFleetViewMode("batch-board");
const { pagination, setPagination } = usePagination({ pageSize: 10 }); const { pagination, setPagination } = usePagination({ pageSize: 10 });
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");

View File

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

View File

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

View File

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

View File

@@ -39,13 +39,9 @@ import {
RouteCorridor, RouteCorridor,
StatusPill, StatusPill,
} from "@/components/trainScheduling/scheduleVisuals"; } from "@/components/trainScheduling/scheduleVisuals";
import { import { useMutation, useQuery } from "@tanstack/react-query";
useAvailableLocomotives, import { api } from "@/services/api";
useScheduleList,
useScheduleMutations,
} from "@/hooks/trainScheduling/useTrainScheduling";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { useRoutes } from "@/hooks/useRoutes";
import type { TrainScheduleListItem } from "@/types/trainScheduling"; import type { TrainScheduleListItem } from "@/types/trainScheduling";
import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common"; import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common";
@@ -88,10 +84,17 @@ export default function TrainScheduleV2ListPage() {
const [scheduleDate, setScheduleDate] = useState(""); const [scheduleDate, setScheduleDate] = useState("");
const [locomotiveId, setLocomotiveId] = useState(""); const [locomotiveId, setLocomotiveId] = useState("");
const schedulesQuery = useScheduleList(); const schedulesQuery = useQuery(
const routesQuery = useRoutes(); api.trainScheduling.scheduleList.queryOptions({ input: {} }),
const locomotivesQuery = useAvailableLocomotives(routeId || undefined); );
const { create, cancel } = useScheduleMutations(); 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( const activeRoutes = useMemo(
() => (routesQuery.data ?? []).filter((r) => r.isActive), () => (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 { ArrowLeft } from "lucide-react";
import { Badge, Button, Card, Group, Loader, SimpleGrid, Stack, Text } from "@mantine/core"; 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 { AssignWagonDialog } from "@/components/wagons/AssignWagonDialog";
import { WagonsTable } from "@/components/wagons/WagonsTable"; import { WagonsTable } from "@/components/wagons/WagonsTable";
import { useTrain } from "@/hooks/useTrains"; import { api } from "@/services/api";
export default function TrainDetailPage() { export default function TrainDetailPage() {
const { id } = useParams<{ id: string }>(); 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) { if (isLoading) {
return ( return (

View File

@@ -42,7 +42,68 @@ import {
type Cargo, type Cargo,
type DeliverCargoPayload, type DeliverCargoPayload,
} from "./cargoService"; } 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 { 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 { import type {
AllocationCriteria, AllocationCriteria,
AllocationPreviewResult, AllocationPreviewResult,
@@ -104,7 +165,333 @@ const INVENTORY_INVALIDATIONS: ReadonlyArray<readonly unknown[]> = [
["warehouses"], ["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 = { 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: {
// ── Warehouses ───────────────────────────────────────────────────────── // ── Warehouses ─────────────────────────────────────────────────────────
list: endpoint<{ filter?: WarehouseFilter }, Warehouse[]>( 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: { cargoes: {
list: endpoint<void, Cargo[]>("cargoes", "list", () => list: endpoint<void, Cargo[]>("cargoes", "list", () =>
cargoService.getAll().then((r) => r.data), cargoService.getAll().then((r) => r.data),
@@ -778,46 +1547,68 @@ export const api = {
"file-upload-settings", "file-upload-settings",
"create", "create",
(payload) => fileUploadSettingsService.create(payload), (payload) => fileUploadSettingsService.create(payload),
undefined,
() => [["file-upload-settings"]],
), ),
update: endpoint< update: endpoint<
{ id: string; dto: UpdateFileUploadSettingDto }, { id: string; dto: UpdateFileUploadSettingDto },
FileUploadSetting 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>( remove: endpoint<{ id: string }, void>(
"file-upload-settings", "file-upload-settings",
"remove", "remove",
({ id }) => fileUploadSettingsService.remove(id), ({ id }) => fileUploadSettingsService.remove(id),
undefined,
() => [["file-upload-settings"]],
), ),
replaceFields: endpoint< replaceFields: endpoint<
{ id: string; fields: CreateFileUploadFieldDto[] }, { id: string; fields: CreateFileUploadFieldDto[] },
FileUploadField[] 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< addField: endpoint<
{ settingId: string; dto: CreateFileUploadFieldDto }, { settingId: string; dto: CreateFileUploadFieldDto },
FileUploadField 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< updateField: endpoint<
{ fieldId: string; dto: UpdateFileUploadFieldDto }, { fieldId: string; dto: UpdateFileUploadFieldDto },
FileUploadField 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>( removeField: endpoint<{ fieldId: string }, void>(
"file-upload-settings", "file-upload-settings",
"removeField", "removeField",
({ fieldId }) => fileUploadSettingsService.removeField(fieldId), ({ fieldId }) => fileUploadSettingsService.removeField(fieldId),
undefined,
() => [["file-upload-settings"]],
), ),
}, },
@@ -844,46 +1635,68 @@ export const api = {
"dropdown-settings", "dropdown-settings",
"create", "create",
(payload) => dropdownSettingsService.create(payload), (payload) => dropdownSettingsService.create(payload),
undefined,
() => [["dropdown-settings"]],
), ),
update: endpoint< update: endpoint<
{ id: string; dto: UpdateDropdownSettingDto }, { id: string; dto: UpdateDropdownSettingDto },
DropdownSetting 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>( remove: endpoint<{ id: string }, void>(
"dropdown-settings", "dropdown-settings",
"remove", "remove",
({ id }) => dropdownSettingsService.remove(id), ({ id }) => dropdownSettingsService.remove(id),
undefined,
() => [["dropdown-settings"]],
), ),
replaceOptions: endpoint< replaceOptions: endpoint<
{ id: string; options: CreateDropdownOptionDto[] }, { id: string; options: CreateDropdownOptionDto[] },
DropdownOption[] DropdownOption[]
>("dropdown-settings", "replaceOptions", ({ id, options }) => >(
dropdownSettingsService.replaceOptions(id, options), "dropdown-settings",
"replaceOptions",
({ id, options }) => dropdownSettingsService.replaceOptions(id, options),
undefined,
() => [["dropdown-settings"]],
), ),
addOption: endpoint< addOption: endpoint<
{ id: string; dto: CreateDropdownOptionDto }, { id: string; dto: CreateDropdownOptionDto },
DropdownOption DropdownOption
>("dropdown-settings", "addOption", ({ id, dto }) => >(
dropdownSettingsService.addOption(id, dto), "dropdown-settings",
"addOption",
({ id, dto }) => dropdownSettingsService.addOption(id, dto),
undefined,
() => [["dropdown-settings"]],
), ),
updateOption: endpoint< updateOption: endpoint<
{ optionId: string; dto: UpdateDropdownOptionDto }, { optionId: string; dto: UpdateDropdownOptionDto },
DropdownOption 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>( removeOption: endpoint<{ optionId: string }, void>(
"dropdown-settings", "dropdown-settings",
"removeOption", "removeOption",
({ optionId }) => dropdownSettingsService.removeOption(optionId), ({ optionId }) => dropdownSettingsService.removeOption(optionId),
undefined,
() => [["dropdown-settings"]],
), ),
}, },

View File

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