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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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