mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 12:41:04 +00:00
wagon types added UI with API integrations
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true,
|
||||
"deleteOutDir": false,
|
||||
"assets": [
|
||||
{
|
||||
"include": "migrations/**/*",
|
||||
@@ -20,4 +20,4 @@
|
||||
],
|
||||
"watchAssets": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
"private": true,
|
||||
"description": "EDR Freight Management API",
|
||||
"scripts": {
|
||||
"clean": "node -e \"const fs=require('fs'); fs.rmSync('dist',{recursive:true,force:true}); fs.rmSync('.tsbuildinfo',{force:true});\"",
|
||||
"predev": "pnpm run clean",
|
||||
"dev": "nest start --watch",
|
||||
"prebuild": "pnpm run clean",
|
||||
"build": "nest build",
|
||||
"start": "node dist/main.js",
|
||||
"lint": "eslint src",
|
||||
@@ -80,4 +83,4 @@
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
PositionType,
|
||||
Position,
|
||||
Project,
|
||||
UnitSetting,
|
||||
UnitConfiguration,
|
||||
GlobalUnitConfiguration,
|
||||
Unit,
|
||||
EmployeeSignature,
|
||||
@@ -64,7 +64,7 @@ const iamEntities = [
|
||||
PositionType,
|
||||
Position,
|
||||
Project,
|
||||
UnitSetting,
|
||||
UnitConfiguration,
|
||||
GlobalUnitConfiguration,
|
||||
Unit,
|
||||
EmployeeSignature,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
|
||||
const toNumber = ({ value }: { value: unknown }) =>
|
||||
value === '' || value == null ? value : Number(value);
|
||||
|
||||
const toBoolean = ({ value }: { value: unknown }) => {
|
||||
if (typeof value === 'boolean') return value;
|
||||
if (value === 'true') return true;
|
||||
if (value === 'false') return false;
|
||||
return value;
|
||||
};
|
||||
|
||||
const toStringArray = ({ value }: { value: unknown }) => {
|
||||
if (Array.isArray(value)) return value;
|
||||
if (typeof value !== 'string') return [];
|
||||
return value
|
||||
.split(',')
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
export class CreateWagonTypeDto {
|
||||
@ApiProperty({ maxLength: 32, example: 'FLAT' })
|
||||
@IsString()
|
||||
@MaxLength(32)
|
||||
code!: string;
|
||||
|
||||
@ApiProperty({ maxLength: 100, example: 'Flat wagon' })
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({ example: 60 })
|
||||
@Transform(toNumber)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
capacityTons!: number;
|
||||
|
||||
@ApiProperty({ example: 14.2 })
|
||||
@Transform(toNumber)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
lengthMeters!: number;
|
||||
|
||||
@ApiPropertyOptional({ example: 45 })
|
||||
@IsOptional()
|
||||
@Transform(toNumber)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
maxWagonsPerTrain?: number;
|
||||
|
||||
@ApiPropertyOptional({ type: [String], example: ['container', 'break-bulk'] })
|
||||
@IsOptional()
|
||||
@Transform(toStringArray)
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
supportedLoadTypes?: string[];
|
||||
|
||||
@ApiPropertyOptional({ default: true })
|
||||
@IsOptional()
|
||||
@Transform(toBoolean)
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
|
||||
import { CreateWagonTypeDto } from './create-wagon-type.dto';
|
||||
|
||||
export class UpdateWagonTypeDto extends PartialType(CreateWagonTypeDto) {}
|
||||
@@ -1,5 +1,19 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
ParseUUIDPipe,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation } from '@nestjs/swagger';
|
||||
import { CreateWagonTypeDto } from './dto/create-wagon-type.dto';
|
||||
import { UpdateWagonTypeDto } from './dto/update-wagon-type.dto';
|
||||
import { WagonTypesService } from './wagon-types.service';
|
||||
import { WagonType } from './entities/wagon-type.entity';
|
||||
|
||||
@@ -8,9 +22,37 @@ import { WagonType } from './entities/wagon-type.entity';
|
||||
export class WagonTypesController {
|
||||
constructor(private readonly wagonTypesService: WagonTypesService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Get all active wagon types' })
|
||||
async findAll(): Promise<WagonType[]> {
|
||||
return this.wagonTypesService.findAll();
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a wagon type' })
|
||||
async create(@Body() dto: CreateWagonTypeDto): Promise<WagonType> {
|
||||
return this.wagonTypesService.create(dto);
|
||||
}
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Get wagon types' })
|
||||
async findAll(@Query() query: Record<string, string | undefined>): Promise<WagonType[]> {
|
||||
return this.wagonTypesService.findAll(query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get a wagon type by ID' })
|
||||
async findOne(@Param('id', ParseUUIDPipe) id: string): Promise<WagonType> {
|
||||
return this.wagonTypesService.findById(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiOperation({ summary: 'Update a wagon type' })
|
||||
async update(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: UpdateWagonTypeDto,
|
||||
): Promise<WagonType> {
|
||||
return this.wagonTypesService.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@ApiOperation({ summary: 'Deactivate a wagon type' })
|
||||
async remove(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
|
||||
return this.wagonTypesService.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { FindOptionsOrder } from 'typeorm';
|
||||
|
||||
import { CreateWagonTypeDto } from './dto/create-wagon-type.dto';
|
||||
import { UpdateWagonTypeDto } from './dto/update-wagon-type.dto';
|
||||
import { WagonType } from './entities/wagon-type.entity';
|
||||
import { WagonTypesRepository } from './wagon-types.repository';
|
||||
|
||||
@@ -7,13 +10,52 @@ import { WagonTypesRepository } from './wagon-types.repository';
|
||||
export class WagonTypesService {
|
||||
constructor(private readonly wagonTypesRepository: WagonTypesRepository) {}
|
||||
|
||||
async findAll(): Promise<WagonType[]> {
|
||||
return this.wagonTypesRepository.findAll({
|
||||
where: { isActive: true },
|
||||
order: { code: 'ASC' },
|
||||
async create(dto: CreateWagonTypeDto): Promise<WagonType> {
|
||||
const code = dto.code.trim().toUpperCase();
|
||||
const existing = await this.wagonTypesRepository.findAll({ where: { code } });
|
||||
if (existing.length > 0) {
|
||||
throw new ConflictException(`Wagon type code "${code}" already exists`);
|
||||
}
|
||||
|
||||
return this.wagonTypesRepository.create({
|
||||
...dto,
|
||||
code,
|
||||
name: dto.name.trim(),
|
||||
supportedLoadTypes: dto.supportedLoadTypes ?? [],
|
||||
isActive: dto.isActive ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll(query: Record<string, string | undefined> = {}): Promise<WagonType[]> {
|
||||
const isActive =
|
||||
query.isActive === 'all'
|
||||
? undefined
|
||||
: query.isActive === undefined
|
||||
? true
|
||||
: query.isActive === 'true';
|
||||
const sortBy = ['code', 'name', 'capacityTons', 'lengthMeters', 'isActive'].includes(
|
||||
query.sortBy ?? '',
|
||||
)
|
||||
? (query.sortBy as keyof WagonType)
|
||||
: 'code';
|
||||
const sortOrder = query.sortOrder?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
|
||||
|
||||
return this.wagonTypesRepository.findAll({
|
||||
where: isActive === undefined ? {} : { isActive },
|
||||
order: { [sortBy]: sortOrder } as FindOptionsOrder<WagonType>,
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<WagonType> {
|
||||
const wagonType = await this.wagonTypesRepository.findById(id);
|
||||
|
||||
if (!wagonType) {
|
||||
throw new NotFoundException(`Wagon type ${id} not found`);
|
||||
}
|
||||
|
||||
return wagonType;
|
||||
}
|
||||
|
||||
async findByCode(code: string): Promise<WagonType> {
|
||||
const [wagonType] = await this.wagonTypesRepository.findAll({ where: { code } });
|
||||
|
||||
@@ -23,4 +65,33 @@ export class WagonTypesService {
|
||||
|
||||
return wagonType;
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateWagonTypeDto): Promise<WagonType> {
|
||||
const wagonType = await this.findById(id);
|
||||
const nextCode = dto.code?.trim().toUpperCase();
|
||||
|
||||
if (nextCode && nextCode !== wagonType.code) {
|
||||
const existing = await this.wagonTypesRepository.findAll({ where: { code: nextCode } });
|
||||
if (existing.length > 0) {
|
||||
throw new ConflictException(`Wagon type code "${nextCode}" already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await this.wagonTypesRepository.update(id, {
|
||||
...dto,
|
||||
...(nextCode ? { code: nextCode } : {}),
|
||||
...(dto.name ? { name: dto.name.trim() } : {}),
|
||||
});
|
||||
|
||||
if (!updated) {
|
||||
throw new NotFoundException(`Wagon type ${id} not found`);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findById(id);
|
||||
await this.wagonTypesRepository.update(id, { isActive: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,12 @@ import RuleEngineLegacyRedirect from "./pages/ruleEngine/RuleEngineLegacyRedirec
|
||||
import RuleEngineResourcePage from "./pages/ruleEngine/RuleEngineResourcePage";
|
||||
import TrainsPage from "./pages/trains/TrainsPage";
|
||||
import {
|
||||
CargoesCrudPage,
|
||||
ContainersCrudPage,
|
||||
TrainMasterDataPage,
|
||||
WagonsCrudPage,
|
||||
} from "./pages/fleet/FleetCrudPages";
|
||||
CargoesCrudPage,
|
||||
ContainersCrudPage,
|
||||
TrainMasterDataPage,
|
||||
WagonTypesCrudPage,
|
||||
WagonsCrudPage,
|
||||
} from "./pages/fleet/FleetCrudPages";
|
||||
import { getCategorySidebarChildren } from "./pages/ruleEngine/config/resources";
|
||||
import TrainDetailPage from "./pages/trains/TrainDetailPage";
|
||||
|
||||
@@ -75,10 +76,15 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
|
||||
href: "/dashboard/trains",
|
||||
icon: <Train />,
|
||||
},
|
||||
{
|
||||
label: "Wagons",
|
||||
href: "/dashboard/wagons",
|
||||
icon: <Truck />,
|
||||
{
|
||||
label: "Wagon types",
|
||||
href: "/dashboard/wagon-types",
|
||||
icon: <Boxes />,
|
||||
},
|
||||
{
|
||||
label: "Wagons",
|
||||
href: "/dashboard/wagons",
|
||||
icon: <Truck />,
|
||||
},
|
||||
{
|
||||
label: "Containers",
|
||||
@@ -224,9 +230,10 @@ const App = () => {
|
||||
element={<BookingContractPage />}
|
||||
/>
|
||||
<Route path="operations/train-scheduling" element={<TrainsPage />} />
|
||||
<Route path="trains" element={<TrainMasterDataPage />} />
|
||||
<Route path="trains/:id" element={<TrainDetailPage />} />
|
||||
<Route path="wagons" element={<WagonsCrudPage />} />
|
||||
<Route path="trains" element={<TrainMasterDataPage />} />
|
||||
<Route path="trains/:id" element={<TrainDetailPage />} />
|
||||
<Route path="wagon-types" element={<WagonTypesCrudPage />} />
|
||||
<Route path="wagons" element={<WagonsCrudPage />} />
|
||||
<Route path="containers" element={<ContainersCrudPage />} />
|
||||
<Route path="cargoes" element={<CargoesCrudPage />} />
|
||||
|
||||
|
||||
@@ -93,6 +93,13 @@ const ROUTE_META: Array<{ prefix: string; meta: PageMeta }> = [
|
||||
},
|
||||
},
|
||||
...rulesRouteMeta,
|
||||
{
|
||||
prefix: "/dashboard/wagon-types",
|
||||
meta: {
|
||||
title: "Wagon Types",
|
||||
subtitle: "Manage wagon type capacity and supported load configuration",
|
||||
},
|
||||
},
|
||||
{
|
||||
prefix: "/dashboard/user1",
|
||||
meta: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { wagonTypesService } from '@/services/wagon-types.service';
|
||||
|
||||
export const WAGON_TYPES_QUERY_KEY = ['wagon-types'];
|
||||
@@ -7,6 +7,30 @@ export function useWagonTypes() {
|
||||
return useQuery({
|
||||
queryKey: WAGON_TYPES_QUERY_KEY,
|
||||
queryFn: () => wagonTypesService.getWagonTypes(),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,12 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
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 { useWagonTypes } from '@/hooks/use-wagon-types';
|
||||
import {
|
||||
useCreateWagonType,
|
||||
useDeleteWagonType,
|
||||
useUpdateWagonType,
|
||||
useWagonTypes,
|
||||
} from '@/hooks/use-wagon-types';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useCreateCargo, useDeleteCargo, useCargoes, useUpdateCargo } from '@/hooks/useCargoes';
|
||||
import {
|
||||
@@ -31,8 +36,9 @@ import type { Cargo } from '@/services/cargoService';
|
||||
import type { Container } from '@/services/containerService';
|
||||
import type { Train } from '@/services/trains.service';
|
||||
import type { Wagon } from '@/services/wagon.service';
|
||||
import type { WagonType } from '@/services/wagon-types.service';
|
||||
|
||||
type FormValue = string | number;
|
||||
type FormValue = string | number | boolean | string[];
|
||||
|
||||
type Field = {
|
||||
key: string;
|
||||
@@ -71,8 +77,20 @@ type FleetCrudPageProps<T extends { id: string }> = {
|
||||
const normalizePayload = (values: Record<string, FormValue>) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(values)
|
||||
.map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])
|
||||
.filter(([, value]) => value !== ''),
|
||||
.map(([key, value]) => [
|
||||
key,
|
||||
key === 'supportedLoadTypes' && typeof value === 'string'
|
||||
? value
|
||||
.split(',')
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean)
|
||||
: Array.isArray(value)
|
||||
? value
|
||||
: typeof value === 'string'
|
||||
? value.trim()
|
||||
: value,
|
||||
])
|
||||
.filter(([, value]) => value !== '' && !(Array.isArray(value) && value.length === 0)),
|
||||
);
|
||||
|
||||
const extractBackendErrors = (error: unknown) => {
|
||||
@@ -181,7 +199,10 @@ function FleetCrudPage<T extends { id: string }>({
|
||||
setEditing(item);
|
||||
setForm(
|
||||
Object.fromEntries(
|
||||
Object.keys(emptyValues).map((key) => [key, (item as Record<string, string | number | null | undefined>)[key] ?? '']),
|
||||
Object.keys(emptyValues).map((key) => [
|
||||
key,
|
||||
(item as Record<string, FormValue | null | undefined>)[key] ?? '',
|
||||
]),
|
||||
),
|
||||
);
|
||||
setFieldErrors({});
|
||||
@@ -349,7 +370,11 @@ function FleetCrudPage<T extends { id: string }>({
|
||||
const value = form[field.key] ?? '';
|
||||
const inputValue = field.type === 'number' && value !== '' && !Number.isFinite(Number(value))
|
||||
? ''
|
||||
: value;
|
||||
: Array.isArray(value)
|
||||
? value.join(', ')
|
||||
: typeof value === 'boolean'
|
||||
? String(value)
|
||||
: value;
|
||||
return (
|
||||
<div key={field.key} className="space-y-2">
|
||||
<Label htmlFor={field.key}>{field.label}</Label>
|
||||
@@ -431,6 +456,12 @@ function FleetCrudPage<T extends { id: string }>({
|
||||
|
||||
const statusBadge = (status?: string) => <Badge variant="outline">{status ?? '-'}</Badge>;
|
||||
|
||||
const activeBadge = (isActive?: boolean) => (
|
||||
<Badge variant={isActive === false ? 'secondary' : 'outline'}>
|
||||
{isActive === false ? 'Inactive' : 'Active'}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
const optionLabel = (options: { value: string; label: string }[], value?: string | null) =>
|
||||
options.find((option) => option.value === value)?.label ?? value ?? '-';
|
||||
|
||||
@@ -469,6 +500,69 @@ export function TrainMasterDataPage() {
|
||||
);
|
||||
}
|
||||
|
||||
export function WagonTypesCrudPage() {
|
||||
const query = useWagonTypes();
|
||||
|
||||
return (
|
||||
<FleetCrudPage<WagonType>
|
||||
title="Wagon Types"
|
||||
description="Manage wagon type capacities and load compatibility used by wagon master data."
|
||||
addLabel="Add Wagon Type"
|
||||
data={query.data}
|
||||
isLoading={query.isLoading}
|
||||
create={useCreateWagonType()}
|
||||
update={useUpdateWagonType()}
|
||||
remove={useDeleteWagonType()}
|
||||
searchText={(type) =>
|
||||
[type.code, type.name, type.supportedLoadTypes?.join(' '), String(type.isActive)].join(' ')
|
||||
}
|
||||
columns={[
|
||||
{ key: 'code', label: 'Code' },
|
||||
{ key: 'name', label: 'Name' },
|
||||
{ key: 'capacityTons', label: 'Capacity (tons)' },
|
||||
{ key: 'lengthMeters', label: 'Length (m)' },
|
||||
{
|
||||
key: 'supportedLoadTypes',
|
||||
label: 'Load types',
|
||||
render: (type) => type.supportedLoadTypes?.join(', ') || '-',
|
||||
},
|
||||
{ key: 'isActive', label: 'Status', render: (type) => activeBadge(type.isActive) },
|
||||
]}
|
||||
fields={[
|
||||
{ key: 'code', label: 'Code', required: true },
|
||||
{ key: 'name', label: 'Name', required: true },
|
||||
{ key: 'capacityTons', label: 'Capacity (tons)', type: 'number', required: true },
|
||||
{ key: 'lengthMeters', label: 'Length (meters)', type: 'number', required: true },
|
||||
{ key: 'maxWagonsPerTrain', label: 'Max wagons per train', type: 'number' },
|
||||
{
|
||||
key: 'supportedLoadTypes',
|
||||
label: 'Supported load types',
|
||||
placeholder: 'container, break-bulk',
|
||||
},
|
||||
{
|
||||
key: 'isActive',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ value: 'true', label: 'Active' },
|
||||
{ value: 'false', label: 'Inactive' },
|
||||
],
|
||||
onValueChange: (value) => ({ isActive: value === 'true' }),
|
||||
},
|
||||
]}
|
||||
emptyValues={{
|
||||
code: '',
|
||||
name: '',
|
||||
capacityTons: 0,
|
||||
lengthMeters: 0,
|
||||
maxWagonsPerTrain: '',
|
||||
supportedLoadTypes: '',
|
||||
isActive: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function WagonsCrudPage() {
|
||||
const query = useWagons();
|
||||
const { data: wagonTypes = [] } = useWagonTypes();
|
||||
|
||||
@@ -2,12 +2,28 @@ import { api } from "../auth/http";
|
||||
|
||||
type ListResponse<T> = T[] | { data: T[] };
|
||||
|
||||
export interface WagonType {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
capacityTons: number;
|
||||
lengthMeters: number;
|
||||
maxWagonsPerTrain?: number | null;
|
||||
supportedLoadTypes: string[];
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
const asList = <T>(payload: ListResponse<T>): T[] =>
|
||||
Array.isArray(payload) ? payload : payload.data;
|
||||
|
||||
export const wagonTypesService = {
|
||||
async getWagonTypes() {
|
||||
const response = await api.get<ListResponse<unknown>>('/wagon-types');
|
||||
const response = await api.get<ListResponse<WagonType>>('/wagon-types', {
|
||||
params: { isActive: 'all' },
|
||||
});
|
||||
return asList(response.data);
|
||||
},
|
||||
create: (data: Partial<WagonType>) => api.post('/wagon-types', data),
|
||||
update: (id: string, data: Partial<WagonType>) => api.patch(`/wagon-types/${id}`, data),
|
||||
delete: (id: string) => api.delete(`/wagon-types/${id}`),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user