integrate LinkedItems
into /route
pages
This commit is contained in:
parent
9cb939deac
commit
451e1da308
@ -104,7 +104,7 @@ export const LinkedItems = <T extends {id: number; [key: string]: any}>({parentI
|
|||||||
|
|
||||||
<AccordionDetails sx={{background: theme.palette.background.paper}}>
|
<AccordionDetails sx={{background: theme.palette.background.paper}}>
|
||||||
<Stack gap={2}>
|
<Stack gap={2}>
|
||||||
<Grid container gap={2}>
|
<Grid container gap={1.25}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Typography>Загрузка...</Typography>
|
<Typography>Загрузка...</Typography>
|
||||||
) : linkedItems.length > 0 ? (
|
) : linkedItems.length > 0 ? (
|
||||||
@ -112,20 +112,20 @@ export const LinkedItems = <T extends {id: number; [key: string]: any}>({parentI
|
|||||||
<Box
|
<Box
|
||||||
key={index}
|
key={index}
|
||||||
sx={{
|
sx={{
|
||||||
marginBottom: '8px',
|
marginTop: '8px',
|
||||||
padding: '14px',
|
padding: '14px',
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
border: `2px solid ${theme.palette.divider}`,
|
border: `2px solid ${theme.palette.divider}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack gap={0.5}>
|
<Stack gap={0.25}>
|
||||||
{fields.map(({label, data}) => (
|
{fields.map(({label, data}) => (
|
||||||
<Typography key={String(data)}>
|
<Typography variant="body2" color="textSecondary" key={String(data)}>
|
||||||
<strong>{label}:</strong> {item[data]}
|
<strong>{label}:</strong> {item[data]}
|
||||||
</Typography>
|
</Typography>
|
||||||
))}
|
))}
|
||||||
{type === 'edit' && (
|
{type === 'edit' && (
|
||||||
<Button variant="outlined" color="error" onClick={() => deleteItem(item.id)} sx={{mt: 1.5}}>
|
<Button variant="outlined" color="error" size="small" onClick={() => deleteItem(item.id)} sx={{mt: 1.5}}>
|
||||||
Отвязать
|
Отвязать
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -2,6 +2,9 @@ import {Autocomplete, Box, TextField, FormControlLabel, Checkbox, Typography} fr
|
|||||||
import {Edit, useAutocomplete} from '@refinedev/mui'
|
import {Edit, useAutocomplete} from '@refinedev/mui'
|
||||||
import {useForm} from '@refinedev/react-hook-form'
|
import {useForm} from '@refinedev/react-hook-form'
|
||||||
import {Controller} from 'react-hook-form'
|
import {Controller} from 'react-hook-form'
|
||||||
|
import {useParams} from 'react-router'
|
||||||
|
import {LinkedItems} from '../../components/LinkedItems'
|
||||||
|
import {StationItem, VehicleItem, stationFields, vehicleFields} from './types'
|
||||||
|
|
||||||
export const RouteEdit = () => {
|
export const RouteEdit = () => {
|
||||||
const {
|
const {
|
||||||
@ -11,6 +14,8 @@ export const RouteEdit = () => {
|
|||||||
formState: {errors},
|
formState: {errors},
|
||||||
} = useForm({})
|
} = useForm({})
|
||||||
|
|
||||||
|
const {id: routeId} = useParams<{id: string}>()
|
||||||
|
|
||||||
const {autocompleteProps: carrierAutocompleteProps} = useAutocomplete({
|
const {autocompleteProps: carrierAutocompleteProps} = useAutocomplete({
|
||||||
resource: 'carrier',
|
resource: 'carrier',
|
||||||
})
|
})
|
||||||
@ -107,6 +112,14 @@ export const RouteEdit = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{routeId && (
|
||||||
|
<>
|
||||||
|
<LinkedItems<StationItem> type="edit" parentId={routeId} parentResource="route" childResource="station" fields={stationFields} title="станции" />
|
||||||
|
|
||||||
|
<LinkedItems<VehicleItem> type="edit" parentId={routeId} parentResource="route" childResource="vehicle" fields={vehicleFields} title="транспортные средства" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Edit>
|
</Edit>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,182 +1,14 @@
|
|||||||
import {Stack, Typography, Box, Grid2 as Grid, Button, MenuItem, Select, FormControl, InputLabel} from '@mui/material'
|
import {Stack, Typography, Box} from '@mui/material'
|
||||||
import {useShow} from '@refinedev/core'
|
import {useShow} from '@refinedev/core'
|
||||||
import {Show, TextFieldComponent as TextField} from '@refinedev/mui'
|
import {Show, TextFieldComponent as TextField} from '@refinedev/mui'
|
||||||
import {useEffect, useState} from 'react'
|
import {LinkedItems} from '../../components/LinkedItems'
|
||||||
import axios from 'axios'
|
import {StationItem, VehicleItem, stationFields, vehicleFields} from './types'
|
||||||
import {BACKEND_URL, VEHICLE_TYPES} from '../../lib/constants'
|
|
||||||
|
|
||||||
type StationItem = {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
description: string
|
|
||||||
[key: string]: string | number
|
|
||||||
}
|
|
||||||
|
|
||||||
type VehicleItem = {
|
|
||||||
id: number
|
|
||||||
tail_number: number
|
|
||||||
type: number
|
|
||||||
[key: string]: string | number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RouteShow = () => {
|
export const RouteShow = () => {
|
||||||
const {query} = useShow({})
|
const {query} = useShow({})
|
||||||
const {data, isLoading} = query
|
const {data, isLoading} = query
|
||||||
const record = data?.data
|
const record = data?.data
|
||||||
|
|
||||||
// Station states
|
|
||||||
const [stations, setStations] = useState<StationItem[]>([])
|
|
||||||
const [linkedStations, setLinkedStations] = useState<StationItem[]>([])
|
|
||||||
const [selectedStationId, setSelectedStationId] = useState<number | ''>('')
|
|
||||||
const [stationsLoading, setStationsLoading] = useState<boolean>(true)
|
|
||||||
|
|
||||||
// Vehicle states
|
|
||||||
const [vehicles, setVehicles] = useState<VehicleItem[]>([])
|
|
||||||
const [linkedVehicles, setLinkedVehicles] = useState<VehicleItem[]>([])
|
|
||||||
const [selectedVehicleId, setSelectedVehicleId] = useState<number | ''>('')
|
|
||||||
const [vehiclesLoading, setVehiclesLoading] = useState<boolean>(true)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (record?.id) {
|
|
||||||
axios
|
|
||||||
.get(`${BACKEND_URL}/route/${record.id}/station`)
|
|
||||||
.then((response) => {
|
|
||||||
setLinkedStations(response?.data || [])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLinkedStations([])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [record?.id])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
axios
|
|
||||||
.get(`${BACKEND_URL}/station/`)
|
|
||||||
.then((response) => {
|
|
||||||
setStations(response?.data || [])
|
|
||||||
setStationsLoading(false)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setStations([])
|
|
||||||
setStationsLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const availableStations = stations.filter((station) => !linkedStations.some((linked) => linked.id === station.id))
|
|
||||||
|
|
||||||
const linkStation = () => {
|
|
||||||
if (selectedStationId) {
|
|
||||||
axios
|
|
||||||
.post(
|
|
||||||
`${BACKEND_URL}/route/${record?.id}/station`,
|
|
||||||
{station_id: selectedStationId},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
axios
|
|
||||||
.get(`${BACKEND_URL}/route/${record?.id}/station`)
|
|
||||||
.then((response) => {
|
|
||||||
setLinkedStations(response?.data || [])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLinkedStations([])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error linking station:', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteStation = (stationId: number) => {
|
|
||||||
axios
|
|
||||||
.delete(`${BACKEND_URL}/route/${record?.id}/station`, {
|
|
||||||
data: {station_id: stationId},
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
setLinkedStations((prevStations) => prevStations.filter((item) => item.id !== stationId))
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error deleting station:', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vehicle effects
|
|
||||||
useEffect(() => {
|
|
||||||
if (record?.id) {
|
|
||||||
axios
|
|
||||||
.get(`${BACKEND_URL}/route/${record.id}/vehicle`)
|
|
||||||
.then((response) => {
|
|
||||||
setLinkedVehicles(response?.data || [])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLinkedVehicles([])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [record?.id])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
axios
|
|
||||||
.get(`${BACKEND_URL}/vehicle/`)
|
|
||||||
.then((response) => {
|
|
||||||
setVehicles(response?.data || [])
|
|
||||||
setVehiclesLoading(false)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setVehicles([])
|
|
||||||
setVehiclesLoading(false)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const availableVehicles = vehicles.filter((vehicle) => !linkedVehicles.some((linked) => linked.id === vehicle.id))
|
|
||||||
|
|
||||||
const linkVehicle = () => {
|
|
||||||
if (selectedVehicleId) {
|
|
||||||
axios
|
|
||||||
.post(
|
|
||||||
`${BACKEND_URL}/route/${record?.id}/vehicle`,
|
|
||||||
{vehicle_id: selectedVehicleId},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
axios
|
|
||||||
.get(`${BACKEND_URL}/route/${record?.id}/vehicle`)
|
|
||||||
.then((response) => {
|
|
||||||
setLinkedVehicles(response?.data || [])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLinkedVehicles([])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error linking vehicle:', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteVehicle = (vehicleId: number) => {
|
|
||||||
axios
|
|
||||||
.delete(`${BACKEND_URL}/route/${record?.id}/vehicle`, {
|
|
||||||
data: {vehicle_id: vehicleId},
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
setLinkedVehicles((prevVehicles) => prevVehicles.filter((item) => item.id !== vehicleId))
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error deleting vehicle:', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{label: 'Перевозчик', data: 'carrier'},
|
{label: 'Перевозчик', data: 'carrier'},
|
||||||
{label: 'Номер маршрута', data: 'route_number'},
|
{label: 'Номер маршрута', data: 'route_number'},
|
||||||
@ -204,16 +36,6 @@ export const RouteShow = () => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const stationFields: Array<{label: string; data: keyof StationItem}> = [
|
|
||||||
{label: 'Название', data: 'name'},
|
|
||||||
{label: 'Описание', data: 'description'},
|
|
||||||
]
|
|
||||||
|
|
||||||
const vehicleFields: Array<{label: string; data: keyof VehicleItem}> = [
|
|
||||||
{label: 'Бортовой номер', data: 'tail_number'},
|
|
||||||
{label: 'Тип', data: 'type'},
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show isLoading={isLoading}>
|
<Show isLoading={isLoading}>
|
||||||
<Stack gap={4}>
|
<Stack gap={4}>
|
||||||
@ -226,123 +48,13 @@ export const RouteShow = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Stack gap={2}>
|
{record?.id && (
|
||||||
<Typography variant="body1" fontWeight="bold">
|
<>
|
||||||
Привязанные станции
|
<LinkedItems<StationItem> type="show" parentId={record.id} parentResource="route" childResource="station" fields={stationFields} title="станции" />
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Grid container gap={2}>
|
<LinkedItems<VehicleItem> type="show" parentId={record.id} parentResource="route" childResource="vehicle" fields={vehicleFields} title="транспортные средства" />
|
||||||
{stationsLoading ? (
|
</>
|
||||||
<Typography>Загрузка станций...</Typography>
|
|
||||||
) : linkedStations.length > 0 ? (
|
|
||||||
linkedStations.map((station, index) => (
|
|
||||||
<Box
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
marginBottom: '8px',
|
|
||||||
padding: '14px',
|
|
||||||
borderRadius: 2,
|
|
||||||
border: (theme) => `2px solid ${theme.palette.divider}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack gap={0.5}>
|
|
||||||
{stationFields.map(({label, data}) => (
|
|
||||||
<Typography key={data}>
|
|
||||||
<strong>{label}:</strong> {station[data]}
|
|
||||||
</Typography>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Button variant="outlined" color="error" onClick={() => deleteStation(station?.id)} sx={{mt: 1.5}}>
|
|
||||||
Отвязать
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<Typography>Станции не найдены</Typography>
|
|
||||||
)}
|
)}
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Stack gap={2}>
|
|
||||||
<Typography variant="body1" fontWeight="bold">
|
|
||||||
Привязать станцию
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<FormControl fullWidth>
|
|
||||||
<InputLabel>Станция</InputLabel>
|
|
||||||
<Select value={selectedStationId} onChange={(e) => setSelectedStationId(Number(e.target.value))} label="Станция" fullWidth>
|
|
||||||
{availableStations.map((station) => (
|
|
||||||
<MenuItem key={station.id} value={station.id}>
|
|
||||||
{station.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<Button variant="contained" onClick={linkStation} disabled={!selectedStationId}>
|
|
||||||
Привязать
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Stack gap={2}>
|
|
||||||
<Typography variant="body1" fontWeight="bold">
|
|
||||||
Привязанные транспортные средства
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Grid container gap={2}>
|
|
||||||
{vehiclesLoading ? (
|
|
||||||
<Typography>Загрузка транспорта...</Typography>
|
|
||||||
) : linkedVehicles.length > 0 ? (
|
|
||||||
linkedVehicles.map((vehicle, index) => (
|
|
||||||
<Box
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
marginBottom: '8px',
|
|
||||||
padding: '14px',
|
|
||||||
borderRadius: 2,
|
|
||||||
border: (theme) => `2px solid ${theme.palette.divider}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack gap={0.5}>
|
|
||||||
{vehicleFields.map(({label, data}) => (
|
|
||||||
<Typography key={data}>
|
|
||||||
<strong>{label}:</strong> {vehicle[data]}
|
|
||||||
</Typography>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Button variant="outlined" color="error" onClick={() => deleteVehicle(vehicle?.id)} sx={{mt: 1.5}}>
|
|
||||||
Отвязать
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<Typography>Транспортные средства не найдены</Typography>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Stack gap={2}>
|
|
||||||
<Typography variant="body1" fontWeight="bold">
|
|
||||||
Привязать транспортное средство
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<FormControl fullWidth>
|
|
||||||
<InputLabel>Транспортное средство</InputLabel>
|
|
||||||
<Select value={selectedVehicleId} onChange={(e) => setSelectedVehicleId(Number(e.target.value))} label="Транспортное средство" fullWidth>
|
|
||||||
{availableVehicles.map((vehicle) => (
|
|
||||||
<MenuItem key={vehicle.id} value={vehicle.id}>
|
|
||||||
{`${vehicle.tail_number} (${VEHICLE_TYPES.find((type) => type.value === vehicle.type)?.label || vehicle.type})`}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<Button variant="contained" onClick={linkVehicle} disabled={!selectedVehicleId}>
|
|
||||||
Привязать
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
35
src/pages/route/types.ts
Normal file
35
src/pages/route/types.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {VEHICLE_TYPES} from '../../lib/constants'
|
||||||
|
|
||||||
|
export type StationItem = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
[key: string]: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VehicleItem = {
|
||||||
|
id: number
|
||||||
|
tail_number: number
|
||||||
|
type: number
|
||||||
|
[key: string]: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FieldType<T> = {
|
||||||
|
label: string
|
||||||
|
data: keyof T
|
||||||
|
render?: (value: any) => React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stationFields: Array<FieldType<StationItem>> = [
|
||||||
|
{label: 'Название', data: 'name'},
|
||||||
|
{label: 'Описание', data: 'description'},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const vehicleFields: Array<FieldType<VehicleItem>> = [
|
||||||
|
{label: 'Бортовой номер', data: 'tail_number'},
|
||||||
|
{
|
||||||
|
label: 'Тип',
|
||||||
|
data: 'type',
|
||||||
|
render: (value: number) => VEHICLE_TYPES.find((type) => type.value === value)?.label || value,
|
||||||
|
},
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user