@autobusal/operator-routes 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Locations/Add/Add.tsx +71 -0
- package/Locations/Add/Loading.tsx +17 -0
- package/Locations/Add/styles.ts +39 -0
- package/Locations/Browse/Actions.tsx +90 -0
- package/Locations/Browse/Browse.tsx +45 -0
- package/Locations/Browse/Item.tsx +92 -0
- package/Locations/Browse/styles.ts +36 -0
- package/Locations/Manage.tsx +44 -0
- package/Locations/services.ts +105 -0
- package/Locations/types.ts +9 -0
- package/Locations/utilities.tsx +53 -0
- package/Prices/Browse/Browse.tsx +33 -0
- package/Prices/Browse/Item.tsx +106 -0
- package/Prices/Browse/styles.ts +22 -0
- package/Prices/Manage.tsx +65 -0
- package/Prices/Options/Options.tsx +63 -0
- package/Prices/Options/styles.ts +52 -0
- package/Prices/Schedule/Schedule.tsx +93 -0
- package/Prices/Schedule/styles.ts +10 -0
- package/Prices/services.ts +59 -0
- package/Prices/types.ts +14 -0
- package/Transiting/Countries.tsx +1 -1
- package/index.ts +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import { FiPlus } from 'react-icons/fi';
|
|
4
|
+
import { Button } from '@autobusal/common';
|
|
5
|
+
import { Validate, Display, success } from '@autobusal/utilities';
|
|
6
|
+
import Loading from './Loading';
|
|
7
|
+
import { getStops } from '../utilities';
|
|
8
|
+
import { Container, Title, ContainerAdd, Choose, Notice } from './styles';
|
|
9
|
+
import { AddForm } from '../types';
|
|
10
|
+
import { GetStops, AddLocation } from '../services';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
routeId: number
|
|
14
|
+
t: TFunction<'normal'>
|
|
15
|
+
onReload: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Add = ({ routeId, t, onReload }: Props): JSX.Element => {
|
|
19
|
+
const { register, handleSubmit, formState: { errors } } = useForm<AddForm>();
|
|
20
|
+
|
|
21
|
+
const { data, isLoading } = GetStops(routeId);
|
|
22
|
+
|
|
23
|
+
const { mutate: Save, isPending } = AddLocation(routeId);
|
|
24
|
+
|
|
25
|
+
if (isLoading) {
|
|
26
|
+
return <Loading t={ t } />;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const onSubmit = (data: AddForm): void => {
|
|
30
|
+
Save(data, {
|
|
31
|
+
onSuccess: () => {
|
|
32
|
+
success(t('locations_manage.messages.added', { ns: 'common' }));
|
|
33
|
+
|
|
34
|
+
onReload();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Container className="box">
|
|
41
|
+
<Title>{ t('locations_manage.add_city.title', { ns: 'common' }) }</Title>
|
|
42
|
+
|
|
43
|
+
<form onSubmit={ handleSubmit(onSubmit) }>
|
|
44
|
+
<ContainerAdd>
|
|
45
|
+
<Choose>
|
|
46
|
+
<select { ...register('group_id', Validate('required', t)) }>
|
|
47
|
+
{ getStops(t, data) }
|
|
48
|
+
</select>
|
|
49
|
+
|
|
50
|
+
{ Display(errors.group_id) }
|
|
51
|
+
</Choose>
|
|
52
|
+
|
|
53
|
+
<Button
|
|
54
|
+
loading={ isPending }
|
|
55
|
+
text={
|
|
56
|
+
<>
|
|
57
|
+
<FiPlus />
|
|
58
|
+
{ t('locations_manage.add_city.action', { ns: 'common' }) }
|
|
59
|
+
</>
|
|
60
|
+
}
|
|
61
|
+
noMargin
|
|
62
|
+
/>
|
|
63
|
+
</ContainerAdd>
|
|
64
|
+
|
|
65
|
+
<Notice>{ t('locations_manage.add_city.notice', { ns: 'common' }) }</Notice>
|
|
66
|
+
</form>
|
|
67
|
+
</Container>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default Add;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { Inline } from '@autobusal/common';
|
|
3
|
+
import { Container, Title } from './styles';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
t: TFunction<'normal'>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Loading = ({ t }: Props): JSX.Element => (
|
|
10
|
+
<Container className="box">
|
|
11
|
+
<Title>{ t('locations_manage.add_city.title', { ns: 'common' }) }</Title>
|
|
12
|
+
|
|
13
|
+
<Inline text={ t('locations_manage.add_city.loading', { ns: 'common' }) } />
|
|
14
|
+
</Container>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export default Loading;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const Container = styled.div`
|
|
4
|
+
margin-bottom: 25px;
|
|
5
|
+
`;
|
|
6
|
+
|
|
7
|
+
export const Title = styled.h5`
|
|
8
|
+
margin-bottom: 10px;
|
|
9
|
+
padding-bottom: 15px;
|
|
10
|
+
font-size: ${ props => props.theme.size.m };
|
|
11
|
+
border-bottom: 1px solid ${ props => props.theme.background.neutral };
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export const ContainerAdd = styled.div`
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: 20px;
|
|
19
|
+
|
|
20
|
+
@media (min-width: 640px) {
|
|
21
|
+
flex-direction: row;
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
export const Choose = styled.div`
|
|
26
|
+
flex: 1;
|
|
27
|
+
width: 100%;
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
export const Notice = styled.div`
|
|
31
|
+
margin-top: 15px;
|
|
32
|
+
font-size: ${ props => props.theme.size.xs };
|
|
33
|
+
color: ${ props => props.theme.font.info };
|
|
34
|
+
text-align: center;
|
|
35
|
+
|
|
36
|
+
@media (min-width: 640px) {
|
|
37
|
+
text-align: left;
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { FiArrowDownCircle, FiArrowUpCircle } from 'react-icons/fi';
|
|
3
|
+
import { BiEditAlt, BiTrash } from 'react-icons/bi';
|
|
4
|
+
import { Button } from '@autobusal/common';
|
|
5
|
+
import { ContainerActions, Group } from './styles';
|
|
6
|
+
import { LocationData } from '@autobusal/providers/types/locations';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
item: LocationData
|
|
10
|
+
loading: boolean
|
|
11
|
+
t: TFunction<'normal'>
|
|
12
|
+
onMove: (order: number) => void
|
|
13
|
+
onDelete: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Actions = ({ item, loading, t, onMove, onDelete }: Props): JSX.Element => {
|
|
17
|
+
const onChange = (type: 'up' | 'down'): void => {
|
|
18
|
+
if (!window.confirm(t('locations_manage.actions.move', { ns: 'common' }))) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const order = type === 'up' ? item.order_id - 1 : item.order_id + 1;
|
|
23
|
+
|
|
24
|
+
onMove(order);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const onAskDelete = (): void => {
|
|
28
|
+
if (window.confirm(t('locations_manage.actions.delete', { ns: 'common' }))) {
|
|
29
|
+
onDelete();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<ContainerActions>
|
|
35
|
+
<Group>
|
|
36
|
+
<Button
|
|
37
|
+
type="button"
|
|
38
|
+
loading={ loading }
|
|
39
|
+
subtype="secondary"
|
|
40
|
+
size="small"
|
|
41
|
+
text={ <FiArrowUpCircle /> }
|
|
42
|
+
noMargin
|
|
43
|
+
onClick={ () => onChange('up') }
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<Button
|
|
47
|
+
type="button"
|
|
48
|
+
subtype="secondary"
|
|
49
|
+
loading={ loading }
|
|
50
|
+
size="small"
|
|
51
|
+
text={ <FiArrowDownCircle /> }
|
|
52
|
+
noMargin
|
|
53
|
+
onClick={ () => onChange('down') }
|
|
54
|
+
/>
|
|
55
|
+
</Group>
|
|
56
|
+
|
|
57
|
+
<Group>
|
|
58
|
+
<Button
|
|
59
|
+
type="submit"
|
|
60
|
+
loading={ loading }
|
|
61
|
+
size="small"
|
|
62
|
+
text={
|
|
63
|
+
<>
|
|
64
|
+
<BiEditAlt />
|
|
65
|
+
{ t('table.actions.update', { ns: 'common' }) }
|
|
66
|
+
</>
|
|
67
|
+
}
|
|
68
|
+
noMargin
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<Button
|
|
72
|
+
type="button"
|
|
73
|
+
loading={ loading }
|
|
74
|
+
size="small"
|
|
75
|
+
subtype="delete"
|
|
76
|
+
text={
|
|
77
|
+
<>
|
|
78
|
+
<BiTrash />
|
|
79
|
+
{ t('table.actions.delete', { ns: 'common' }) }
|
|
80
|
+
</>
|
|
81
|
+
}
|
|
82
|
+
noMargin
|
|
83
|
+
onClick={ onAskDelete }
|
|
84
|
+
/>
|
|
85
|
+
</Group>
|
|
86
|
+
</ContainerActions>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default Actions;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import Item from './Item';
|
|
3
|
+
import { Items, NotFound } from './styles';
|
|
4
|
+
import { LocationData } from '@autobusal/providers/types/locations';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
routeId: number
|
|
8
|
+
data?: LocationData[]
|
|
9
|
+
t: TFunction<'normal'>
|
|
10
|
+
onReload: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Browse = ({ routeId, data, t, onReload }: Props): JSX.Element => {
|
|
14
|
+
if (data === undefined || data.length === 0) {
|
|
15
|
+
return (
|
|
16
|
+
<NotFound className="box">
|
|
17
|
+
{ t('locations_manage.no_locations', { ns: 'common' }) }
|
|
18
|
+
</NotFound>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const items = data.map(item => (
|
|
23
|
+
<Item
|
|
24
|
+
key={ item.id }
|
|
25
|
+
item={ item }
|
|
26
|
+
routeId={ routeId }
|
|
27
|
+
t={ t }
|
|
28
|
+
onReload={ onReload }
|
|
29
|
+
/>
|
|
30
|
+
));
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<h2>
|
|
35
|
+
{ t('locations_manage.browse', { ns: 'common' }) }
|
|
36
|
+
</h2>
|
|
37
|
+
|
|
38
|
+
<Items>
|
|
39
|
+
{ items }
|
|
40
|
+
</Items>
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default Browse;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import { Validate, Display, success } from '@autobusal/utilities';
|
|
4
|
+
import Actions from './Actions';
|
|
5
|
+
import { getTomorrow } from '../utilities';
|
|
6
|
+
import { Data } from './styles';
|
|
7
|
+
import { LocationForm } from '../types';
|
|
8
|
+
import { LocationData } from '@autobusal/providers/types/locations';
|
|
9
|
+
import { PostMove, DeleteLocation, PostUpdate } from '../services';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
routeId: number
|
|
13
|
+
item: LocationData
|
|
14
|
+
t: TFunction<'normal'>
|
|
15
|
+
onReload: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Item = ({ routeId, item, t, onReload }: Props): JSX.Element => {
|
|
19
|
+
const { register, handleSubmit, formState: { errors } } = useForm<LocationForm>();
|
|
20
|
+
|
|
21
|
+
const { mutate: Save, isPending: isPendingUpdate } = PostUpdate(routeId, item.id);
|
|
22
|
+
|
|
23
|
+
const { mutate: Move, isPending: isPendingMove } = PostMove(routeId, item.id);
|
|
24
|
+
|
|
25
|
+
const { mutate: Delete, isPending: isPendingDelete } = DeleteLocation(routeId, item.id);
|
|
26
|
+
|
|
27
|
+
const onSubmit = (data: LocationForm): void => {
|
|
28
|
+
Save(data);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const onMove = (order: number): void => {
|
|
32
|
+
Move(order, {
|
|
33
|
+
onSuccess: () => {
|
|
34
|
+
success(t('locations_manage.messages.moved', { ns: 'common' }));
|
|
35
|
+
|
|
36
|
+
onReload();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const onDelete = (): void => {
|
|
42
|
+
Delete({}, {
|
|
43
|
+
onSuccess: () => {
|
|
44
|
+
success(t('locations_manage.messages.deleted', { ns: 'common' }));
|
|
45
|
+
|
|
46
|
+
onReload();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className="box">
|
|
53
|
+
<h4>{ item.city.country?.name } › { item.city.name } › { item.stop?.name }</h4>
|
|
54
|
+
|
|
55
|
+
<form onSubmit={ handleSubmit(onSubmit) }>
|
|
56
|
+
<Data>
|
|
57
|
+
<div className="row">
|
|
58
|
+
{ t('locations_manage.city.tomorrow', { ns: 'common' }) }
|
|
59
|
+
|
|
60
|
+
<select defaultValue={ item.tomorrow } { ...register('tomorrow') }>
|
|
61
|
+
{ getTomorrow(t) }
|
|
62
|
+
</select>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="row">
|
|
66
|
+
{ t('locations_manage.city.departure', { ns: 'common' }) }
|
|
67
|
+
|
|
68
|
+
<input type="text" defaultValue={ item.departure } { ...register('departure', Validate('required', t)) } />
|
|
69
|
+
{ Display(errors.departure) }
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="row">
|
|
73
|
+
{ t('locations_manage.city.arrival', { ns: 'common' }) }
|
|
74
|
+
|
|
75
|
+
<input type="text" defaultValue={ item.arrival } { ...register('arrival', Validate('required', t)) } />
|
|
76
|
+
{ Display(errors.arrival) }
|
|
77
|
+
</div>
|
|
78
|
+
</Data>
|
|
79
|
+
|
|
80
|
+
<Actions
|
|
81
|
+
item={ item }
|
|
82
|
+
loading={ isPendingMove || isPendingDelete || isPendingUpdate }
|
|
83
|
+
t={ t }
|
|
84
|
+
onMove={ onMove }
|
|
85
|
+
onDelete={ onDelete }
|
|
86
|
+
/>
|
|
87
|
+
</form>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default Item;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const Items = styled.div`
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 15px;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
export const NotFound = styled.div`
|
|
10
|
+
margin-top: 15px;
|
|
11
|
+
padding: 30px 0;
|
|
12
|
+
color: ${ props => props.theme.font.neutral };
|
|
13
|
+
text-align: center;
|
|
14
|
+
font-size: ${ props => props.theme.size.xs };
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
export const Data = styled.div`
|
|
18
|
+
display: flex;
|
|
19
|
+
gap: 10px;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
|
|
22
|
+
@media (min-width: 480px) {
|
|
23
|
+
flex-direction: row;
|
|
24
|
+
gap: 15px;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
export const ContainerActions = styled.div`
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export const Group = styled.div`
|
|
34
|
+
display: flex;
|
|
35
|
+
gap: 10px;
|
|
36
|
+
`;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { useParams } from 'react-router-dom';
|
|
3
|
+
import { Meta, BackWithTitle, Report } from '@autobusal/common';
|
|
4
|
+
import { usePage } from '@autobusal/hooks';
|
|
5
|
+
import Add from './Add/Add';
|
|
6
|
+
import Browse from './Browse/Browse';
|
|
7
|
+
import { GetLocations } from './services';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
url: string
|
|
11
|
+
t: TFunction<'common'>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const Manage = ({ url, t }: Props): JSX.Element => {
|
|
15
|
+
const params = useParams();
|
|
16
|
+
|
|
17
|
+
const id = Number(params.id);
|
|
18
|
+
|
|
19
|
+
url = url.replace('[id]', String(id));
|
|
20
|
+
|
|
21
|
+
usePage('routes');
|
|
22
|
+
|
|
23
|
+
const { data, refetch } = GetLocations(id);
|
|
24
|
+
|
|
25
|
+
const onReload = (): void => {
|
|
26
|
+
refetch();
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const title = t('locations_manage.title', { name: data?.name, ns: 'common' });
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Meta title={ title }>
|
|
33
|
+
<BackWithTitle title={ title } to={ url } t={ t } />
|
|
34
|
+
|
|
35
|
+
<Add routeId={ id } t={ t } onReload={ onReload } />
|
|
36
|
+
|
|
37
|
+
<Report margin={ 15 } question={ t('locations_manage.report', { ns: 'common' }) } type="location" t={ t } />
|
|
38
|
+
|
|
39
|
+
<Browse routeId={ id } data={ data?.locations } t={ t } onReload={ onReload } />
|
|
40
|
+
</Meta>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default Manage;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { UseMutationResult, UseQueryResult, UseSuspenseQueryResult, useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
|
2
|
+
import { apiClient } from '@autobusal/providers';
|
|
3
|
+
import { RouteData } from '@autobusal/providers/types/routes';
|
|
4
|
+
import { CountryData } from '@autobusal/providers/types/locations';
|
|
5
|
+
import { AddForm, LocationForm } from './types';
|
|
6
|
+
|
|
7
|
+
export const GetLocations = (id: number): UseSuspenseQueryResult<RouteData> => (
|
|
8
|
+
useSuspenseQuery({
|
|
9
|
+
queryKey: ['operator-locations', { id }],
|
|
10
|
+
queryFn: async () => (
|
|
11
|
+
await apiClient
|
|
12
|
+
.get('/api/locations/operator/get', {
|
|
13
|
+
params: { id }
|
|
14
|
+
})
|
|
15
|
+
.then(response => (
|
|
16
|
+
response.data
|
|
17
|
+
))
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const GetStops = (routeId: number): UseQueryResult<CountryData[]> => (
|
|
23
|
+
useQuery({
|
|
24
|
+
queryKey: ['operator-stops', { routeId }],
|
|
25
|
+
queryFn: async () => (
|
|
26
|
+
await apiClient
|
|
27
|
+
.get('/api/stops/operator/browse', {
|
|
28
|
+
params: {
|
|
29
|
+
route_id: routeId
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.then(response => (
|
|
33
|
+
response.data
|
|
34
|
+
))
|
|
35
|
+
)
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const AddLocation = (id: number): UseMutationResult<void, Error, AddForm, unknown> => (
|
|
40
|
+
useMutation({
|
|
41
|
+
mutationKey: ['operator-locations-add', { id }],
|
|
42
|
+
mutationFn: async (data: AddForm) => {
|
|
43
|
+
await apiClient
|
|
44
|
+
.post('/api/locations/operator/add', {
|
|
45
|
+
...data,
|
|
46
|
+
id
|
|
47
|
+
})
|
|
48
|
+
.then(response => (
|
|
49
|
+
response.data
|
|
50
|
+
))
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export const PostMove = (id: number, location_id: number): UseMutationResult<void, Error, number, unknown> => (
|
|
56
|
+
useMutation({
|
|
57
|
+
mutationKey: ['operator-locations-move', { id, location_id }],
|
|
58
|
+
mutationFn: async (order: number) => (
|
|
59
|
+
await apiClient
|
|
60
|
+
.post('/api/locations/operator/move', {
|
|
61
|
+
order,
|
|
62
|
+
id,
|
|
63
|
+
location_id
|
|
64
|
+
})
|
|
65
|
+
.then(response => (
|
|
66
|
+
response.data
|
|
67
|
+
))
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export const DeleteLocation = (id: number, location_id: number): UseMutationResult<void> => (
|
|
73
|
+
useMutation({
|
|
74
|
+
mutationKey: ['operator-locations-delete', { id, location_id }],
|
|
75
|
+
mutationFn: async () => (
|
|
76
|
+
await apiClient
|
|
77
|
+
.delete('/api/locations/operator/delete', {
|
|
78
|
+
params: {
|
|
79
|
+
id,
|
|
80
|
+
location_id
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
.then(response => (
|
|
84
|
+
response.data
|
|
85
|
+
))
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
export const PostUpdate = (id: number, location_id: number): UseMutationResult<void, Error, LocationForm, unknown> => (
|
|
91
|
+
useMutation({
|
|
92
|
+
mutationKey: ['operator-locations-update', { id, location_id }],
|
|
93
|
+
mutationFn: async (data: LocationForm) => (
|
|
94
|
+
await apiClient
|
|
95
|
+
.post('/api/locations/operator/update', {
|
|
96
|
+
...data,
|
|
97
|
+
id,
|
|
98
|
+
location_id
|
|
99
|
+
})
|
|
100
|
+
.then(response => (
|
|
101
|
+
response.data
|
|
102
|
+
))
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { CityData, CountryData } from '@autobusal/providers/types/locations';
|
|
3
|
+
|
|
4
|
+
export const getStops = (t: TFunction<'normal'>, countries?: CountryData[]): JSX.Element[] => {
|
|
5
|
+
const options: JSX.Element[] = [
|
|
6
|
+
<option key="-none=" value="">{ t('locations_manage.add_city.choose', { ns: 'common' }) }</option>
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
countries?.forEach(country => {
|
|
10
|
+
options.push(
|
|
11
|
+
<optgroup key={ country.id } label={ country.name }>
|
|
12
|
+
{ getOptions(country.cities) }
|
|
13
|
+
</optgroup>
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return options;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getOptions = (cities?: CityData[]): JSX.Element[] => {
|
|
21
|
+
const options: JSX.Element[] = [];
|
|
22
|
+
|
|
23
|
+
cities?.forEach(city => {
|
|
24
|
+
city.stops?.forEach(stop => {
|
|
25
|
+
const value = `${ city.id }#${ stop.id }`;
|
|
26
|
+
|
|
27
|
+
options.push(
|
|
28
|
+
<option key={ value } value={ value }>{ city.name } › { stop.name }</option>
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return options;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const getTomorrow = (t: TFunction<'normal'>): JSX.Element[] => {
|
|
37
|
+
const available = [
|
|
38
|
+
{ name: t('locations_manage.tomorrow.same_day', { ns: 'common' }), value: 0 },
|
|
39
|
+
{ name: t('locations_manage.tomorrow.1_day', { ns: 'common' }), value: 1 },
|
|
40
|
+
{ name: t('locations_manage.tomorrow.2_days', { ns: 'common' }), value: 2 },
|
|
41
|
+
{ name: t('locations_manage.tomorrow.3_days', { ns: 'common' }), value: 3 },
|
|
42
|
+
{ name: t('locations_manage.tomorrow.4_days', { ns: 'common' }), value: 4 },
|
|
43
|
+
{ name: t('locations_manage.tomorrow.5_days', { ns: 'common' }), value: 5 },
|
|
44
|
+
{ name: t('locations_manage.tomorrow.6_days', { ns: 'common' }), value: 6 },
|
|
45
|
+
{ name: t('locations_manage.tomorrow.7_days', { ns: 'common' }), value: 7 }
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const options = available.map(item => (
|
|
49
|
+
<option key={ item.value } value={ item.value }>{ item.name }</option>
|
|
50
|
+
));
|
|
51
|
+
|
|
52
|
+
return options;
|
|
53
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import Item from './Item';
|
|
3
|
+
import { Items } from './styles';
|
|
4
|
+
import { PriceData } from '@autobusal/providers/types/routes';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
id: number
|
|
8
|
+
type: 'normal' | 'alternate'
|
|
9
|
+
data?: PriceData[]
|
|
10
|
+
name: string
|
|
11
|
+
t: TFunction<'normal'>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const Browse = ({ id, type, data, name, t }: Props): JSX.Element | JSX.Element[] | undefined => {
|
|
15
|
+
const items = data?.map(item => (
|
|
16
|
+
<Item
|
|
17
|
+
key={ item.id }
|
|
18
|
+
id={ id }
|
|
19
|
+
type={ type }
|
|
20
|
+
item={ item }
|
|
21
|
+
name={ name }
|
|
22
|
+
t={ t }
|
|
23
|
+
/>
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Items>
|
|
28
|
+
{ items }
|
|
29
|
+
</Items>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default Browse;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import { BiEditAlt } from 'react-icons/bi';
|
|
4
|
+
import { Button } from '@autobusal/common';
|
|
5
|
+
import { Validate, Display, success } from '@autobusal/utilities';
|
|
6
|
+
import { Prices, Actions } from './styles';
|
|
7
|
+
import { PriceData } from '@autobusal/providers/types/routes';
|
|
8
|
+
import { PriceForm } from '../types';
|
|
9
|
+
import { PostPrice } from '../services';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
id: number
|
|
13
|
+
type: 'normal' | 'alternate'
|
|
14
|
+
item: PriceData
|
|
15
|
+
name: string
|
|
16
|
+
t: TFunction<'normal'>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Item = ({ id, type, item, name, t }: Props): JSX.Element => {
|
|
20
|
+
const { register, handleSubmit, formState: { errors } } = useForm<PriceForm>();
|
|
21
|
+
|
|
22
|
+
const { mutate: Save, isPending } = PostPrice(id, item.id, type, name);
|
|
23
|
+
|
|
24
|
+
const onSubmit = (data: PriceForm): void => {
|
|
25
|
+
Save(data, {
|
|
26
|
+
onSuccess: () => success(t('prices_manage.messages.saved', { ns: 'common' }))
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="box">
|
|
32
|
+
<h4>
|
|
33
|
+
{ item.from.city.name } ({ item.from.city.country?.name }) › { item.to.city.name } ({ item.to.city.country?.name })
|
|
34
|
+
</h4>
|
|
35
|
+
|
|
36
|
+
<form onSubmit={ handleSubmit(onSubmit) }>
|
|
37
|
+
<Prices>
|
|
38
|
+
<div className="row">
|
|
39
|
+
{ t('prices_manage.prices.adult', { ns: 'common' }) }
|
|
40
|
+
|
|
41
|
+
<input type="number" defaultValue={ item.adult } { ...register('adult', Validate('required', t)) } />
|
|
42
|
+
|
|
43
|
+
{ Display(errors.adult) }
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div className="row">
|
|
47
|
+
{ t('prices_manage.prices.child', { ns: 'common' }) }
|
|
48
|
+
|
|
49
|
+
<input type="number" defaultValue={ item.child } { ...register('child', Validate('required', t)) } />
|
|
50
|
+
|
|
51
|
+
{ Display(errors.child) }
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="row">
|
|
55
|
+
{ t('prices_manage.prices.baby', { ns: 'common' }) }
|
|
56
|
+
|
|
57
|
+
<input type="number" defaultValue={ item.baby } { ...register('baby', Validate('required', t)) } />
|
|
58
|
+
|
|
59
|
+
{ Display(errors.baby) }
|
|
60
|
+
</div>
|
|
61
|
+
</Prices>
|
|
62
|
+
|
|
63
|
+
<Prices>
|
|
64
|
+
<div className="row">
|
|
65
|
+
{ t('prices_manage.prices.adult_return', { ns: 'common' }) }
|
|
66
|
+
|
|
67
|
+
<input type="number" defaultValue={ item.adult_return } { ...register('adult_return', Validate('required', t)) } />
|
|
68
|
+
|
|
69
|
+
{ Display(errors.adult_return) }
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="row">
|
|
73
|
+
{ t('prices_manage.prices.child_return', { ns: 'common' }) }
|
|
74
|
+
|
|
75
|
+
<input type="number" defaultValue={ item.child_return } { ...register('child_return', Validate('required', t)) } />
|
|
76
|
+
|
|
77
|
+
{ Display(errors.child_return) }
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div className="row">
|
|
81
|
+
{ t('prices_manage.prices.baby_return', { ns: 'common' }) }
|
|
82
|
+
|
|
83
|
+
<input type="number" defaultValue={ item.baby_return } { ...register('baby_return', Validate('required', t)) } />
|
|
84
|
+
|
|
85
|
+
{ Display(errors.baby_return) }
|
|
86
|
+
</div>
|
|
87
|
+
</Prices>
|
|
88
|
+
|
|
89
|
+
<Actions>
|
|
90
|
+
<Button
|
|
91
|
+
type="submit"
|
|
92
|
+
loading={ isPending }
|
|
93
|
+
text={
|
|
94
|
+
<>
|
|
95
|
+
<BiEditAlt />
|
|
96
|
+
{ t('table.actions.update', { ns: 'common' }) }
|
|
97
|
+
</>
|
|
98
|
+
}
|
|
99
|
+
/>
|
|
100
|
+
</Actions>
|
|
101
|
+
</form>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default Item;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const Items = styled.div`
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 15px;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
export const Prices = styled.div`
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
|
|
13
|
+
@media (min-width: 540px) {
|
|
14
|
+
flex-direction: row;
|
|
15
|
+
gap: 15px;
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const Actions = styled.div`
|
|
20
|
+
display: flex;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
`;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { useParams } from 'react-router-dom';
|
|
3
|
+
import { Meta, BackWithTitle } from '@autobusal/common';
|
|
4
|
+
import { usePage } from '@autobusal/hooks';
|
|
5
|
+
import Options from './Options/Options';
|
|
6
|
+
import Schedule from './Schedule/Schedule';
|
|
7
|
+
import Browse from './Browse/Browse';
|
|
8
|
+
import { GetPrices } from './services';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
type: 'normal' | 'alternate'
|
|
12
|
+
url: string
|
|
13
|
+
t: TFunction<'common'>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Manage = ({ type, url, t }: Props): JSX.Element => {
|
|
17
|
+
const params = useParams();
|
|
18
|
+
|
|
19
|
+
const id = Number(params.id);
|
|
20
|
+
const name = String(params.name ?? '');
|
|
21
|
+
|
|
22
|
+
url = url.replace('[id]', String(id));
|
|
23
|
+
|
|
24
|
+
usePage('routes');
|
|
25
|
+
|
|
26
|
+
const { data, refetch } = GetPrices(id, type, name);
|
|
27
|
+
|
|
28
|
+
const onReload = () => {
|
|
29
|
+
refetch();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const title = t(`prices_manage.title.${ type }`, { name: data?.name, ns: 'common' });
|
|
33
|
+
|
|
34
|
+
// get the correct column
|
|
35
|
+
const prices = type === 'alternate' ? data?.alternates : data?.prices;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Meta title={ title }>
|
|
39
|
+
<BackWithTitle title={ title } to={ url } t={ t } />
|
|
40
|
+
|
|
41
|
+
<div className="route-prices">
|
|
42
|
+
<Options
|
|
43
|
+
id={ id }
|
|
44
|
+
type={ type }
|
|
45
|
+
name={ name }
|
|
46
|
+
t={ t }
|
|
47
|
+
onReload={ onReload }
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
{ type === 'alternate' && (
|
|
51
|
+
<Schedule
|
|
52
|
+
id={ id }
|
|
53
|
+
data={ data?.alternates }
|
|
54
|
+
name={ name }
|
|
55
|
+
t={ t }
|
|
56
|
+
/>
|
|
57
|
+
) }
|
|
58
|
+
|
|
59
|
+
<Browse id={ id } type={ type } data={ prices } name={ name } t={ t } />
|
|
60
|
+
</div>
|
|
61
|
+
</Meta>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default Manage;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useState, useRef } from 'react';
|
|
2
|
+
import { TFunction } from 'i18next';
|
|
3
|
+
import { FaCaretDown } from 'react-icons/fa';
|
|
4
|
+
import { Inline } from '@autobusal/common';
|
|
5
|
+
import { useOutside } from '@autobusal/hooks';
|
|
6
|
+
import { success } from '@autobusal/utilities';
|
|
7
|
+
import { Container, ButtonMore, Submenu, ButtonOption, Loading } from './styles';
|
|
8
|
+
import { PostCopy } from '../services';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
id: number
|
|
12
|
+
type: 'normal' | 'alternate'
|
|
13
|
+
name: string
|
|
14
|
+
t: TFunction<'normal'>
|
|
15
|
+
onReload: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Options = ({ id, type, name, t, onReload }: Props): JSX.Element => {
|
|
19
|
+
const [ show, setShow ] = useState<boolean>(false);
|
|
20
|
+
|
|
21
|
+
const ref = useRef(null);
|
|
22
|
+
|
|
23
|
+
const { mutate: Copy, isPending } = PostCopy(id, type, name);
|
|
24
|
+
|
|
25
|
+
useOutside(ref, () => setShow(false));
|
|
26
|
+
|
|
27
|
+
const onCopy = (): void => {
|
|
28
|
+
if (window.confirm(t('prices_manage.options.copy.ask', { ns: 'common' }))) {
|
|
29
|
+
Copy({}, {
|
|
30
|
+
onSuccess: () => {
|
|
31
|
+
success(t('prices_manage.messages.copied', { ns: 'common' }));
|
|
32
|
+
|
|
33
|
+
onReload();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (isPending) {
|
|
40
|
+
return (
|
|
41
|
+
<Loading>
|
|
42
|
+
<Inline text={ t('prices_manage.options.copy.loading', { ns: 'common' }) } />
|
|
43
|
+
</Loading>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Container ref={ ref }>
|
|
49
|
+
<ButtonMore onClick={ () => setShow(true) }>
|
|
50
|
+
{ t('prices_manage.options.more', { ns: 'common' }) }
|
|
51
|
+
<FaCaretDown />
|
|
52
|
+
</ButtonMore>
|
|
53
|
+
|
|
54
|
+
{ show && (
|
|
55
|
+
<Submenu>
|
|
56
|
+
<ButtonOption onClick={ onCopy }>{ t('prices_manage.options.copy.text', { ns: 'common' }) }</ButtonOption>
|
|
57
|
+
</Submenu>
|
|
58
|
+
) }
|
|
59
|
+
</Container>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default Options;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const Container = styled.div`
|
|
4
|
+
position: relative;
|
|
5
|
+
display: flex;
|
|
6
|
+
margin-bottom: 10px;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
export const ButtonMore = styled.button`
|
|
10
|
+
margin-left: auto;
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: 3px;
|
|
14
|
+
color: ${ props => props.theme.font.info };
|
|
15
|
+
font-size: ${ props => props.theme.size.xs };
|
|
16
|
+
|
|
17
|
+
&:hover {
|
|
18
|
+
text-decoration: underline;
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export const Submenu = styled.div`
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 25px;
|
|
25
|
+
right: 0;
|
|
26
|
+
width: 200px;
|
|
27
|
+
padding: 5px;
|
|
28
|
+
background: ${ props => props.theme.submenu.transparent };
|
|
29
|
+
border-radius: ${ props => props.theme.borderRadius };
|
|
30
|
+
box-shadow: ${ props => props.theme.boxShadow };
|
|
31
|
+
backdrop-filter: blur(15px);
|
|
32
|
+
-webkit-backdrop-filter: blur(15px);
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
export const ButtonOption = styled.button`
|
|
36
|
+
display: block;
|
|
37
|
+
width: 100%;
|
|
38
|
+
padding: 5px 10px;
|
|
39
|
+
font-size: ${ props => props.theme.size.s };
|
|
40
|
+
color: ${ props => props.theme.submenu.font };
|
|
41
|
+
border-radius: ${ props => props.theme.borderRadius };
|
|
42
|
+
|
|
43
|
+
&:hover {
|
|
44
|
+
background: ${ props => props.theme.submenu.hover };
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
export const Loading = styled.div`
|
|
49
|
+
display: flex;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
padding: 25px 0;
|
|
52
|
+
`;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import { BiEditAlt } from 'react-icons/bi';
|
|
4
|
+
import { Calendar, Button } from '@autobusal/common';
|
|
5
|
+
import { Display, success } from '@autobusal/utilities';
|
|
6
|
+
import { Container, Dates } from './styles';
|
|
7
|
+
import { AlternateData } from '@autobusal/providers/types/routes';
|
|
8
|
+
import { PostSchedule } from '@pages/Operator/Alternates/services';
|
|
9
|
+
import { ScheduleForm } from '../types';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
id: number
|
|
13
|
+
data?: AlternateData[]
|
|
14
|
+
name: string
|
|
15
|
+
t: TFunction<'normal'>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Schedule = ({ id, data, name, t }: Props): JSX.Element => {
|
|
19
|
+
const { register, handleSubmit, formState: { errors }, setValue } = useForm<ScheduleForm>();
|
|
20
|
+
|
|
21
|
+
// we get the 1st value, as they're identical
|
|
22
|
+
const details = data !== undefined ? data[0] : undefined;
|
|
23
|
+
|
|
24
|
+
const { mutate: Save, isPending } = PostSchedule(id, name);
|
|
25
|
+
|
|
26
|
+
const onSubmit = (data: ScheduleForm): void => {
|
|
27
|
+
Save(data, {
|
|
28
|
+
onSuccess: () => success(t('alternates_manage.messages.saved', { ns: 'common' }))
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Container className="box">
|
|
34
|
+
<h2>{ t('alternates_manage.title', { ns: 'common' }) }</h2>
|
|
35
|
+
|
|
36
|
+
<form onSubmit={ handleSubmit(onSubmit) }>
|
|
37
|
+
<Dates>
|
|
38
|
+
<div className="row">
|
|
39
|
+
{ t('alternates_manage.from', { ns: 'common' }) }
|
|
40
|
+
|
|
41
|
+
<Calendar
|
|
42
|
+
type="picker"
|
|
43
|
+
name="date_from"
|
|
44
|
+
defaultValue={ details?.date_from }
|
|
45
|
+
t={ t }
|
|
46
|
+
validation="required"
|
|
47
|
+
refs={ register }
|
|
48
|
+
onUpdate={ setValue }
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
{ Display(errors.date_from) }
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="row">
|
|
55
|
+
{ t('alternates_manage.to', { ns: 'common' }) }
|
|
56
|
+
|
|
57
|
+
<Calendar
|
|
58
|
+
type="picker"
|
|
59
|
+
name="date_to"
|
|
60
|
+
defaultValue={ details?.date_to }
|
|
61
|
+
t={ t }
|
|
62
|
+
validation="required"
|
|
63
|
+
refs={ register }
|
|
64
|
+
onUpdate={ setValue }
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
{ Display(errors.date_to) }
|
|
68
|
+
</div>
|
|
69
|
+
</Dates>
|
|
70
|
+
|
|
71
|
+
<div className="row">
|
|
72
|
+
<label>
|
|
73
|
+
<input type="checkbox" value="1" defaultChecked={ details?.offer } { ...register('offer') } />
|
|
74
|
+
{ t('alternates_manage.offer', { ns: 'common' }) }
|
|
75
|
+
</label>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<Button
|
|
79
|
+
type="submit"
|
|
80
|
+
loading={ isPending }
|
|
81
|
+
text={
|
|
82
|
+
<>
|
|
83
|
+
<BiEditAlt />
|
|
84
|
+
{ t('table.actions.update', { ns: 'common' }) }
|
|
85
|
+
</>
|
|
86
|
+
}
|
|
87
|
+
/>
|
|
88
|
+
</form>
|
|
89
|
+
</Container>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default Schedule;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { UseMutationResult, UseSuspenseQueryResult, useMutation, useSuspenseQuery } from '@tanstack/react-query';
|
|
2
|
+
import { apiClient } from '@autobusal/providers';
|
|
3
|
+
import { RouteData } from '@autobusal/providers/types/routes';
|
|
4
|
+
import { PriceForm } from './types';
|
|
5
|
+
|
|
6
|
+
export const GetPrices = (id: number, type: string, name: string): UseSuspenseQueryResult<RouteData> => (
|
|
7
|
+
useSuspenseQuery({
|
|
8
|
+
queryKey: [`operator-prices-${ type }`, { id, type, name }],
|
|
9
|
+
queryFn: async () => (
|
|
10
|
+
await apiClient
|
|
11
|
+
.get('/api/prices/operator/get', {
|
|
12
|
+
params: {
|
|
13
|
+
id,
|
|
14
|
+
type,
|
|
15
|
+
name
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
.then(response => (
|
|
19
|
+
response.data
|
|
20
|
+
))
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const PostPrice = (id: number, price_id: number, type: ('normal' | 'alternate'), name: string): UseMutationResult<void, Error, PriceForm, unknown> => (
|
|
26
|
+
useMutation({
|
|
27
|
+
mutationKey: ['operator-prices-save', { id, price_id, type, name }],
|
|
28
|
+
mutationFn: async (data: PriceForm) => (
|
|
29
|
+
await apiClient
|
|
30
|
+
.post('/api/prices/operator/update', {
|
|
31
|
+
...data,
|
|
32
|
+
id,
|
|
33
|
+
price_id,
|
|
34
|
+
type,
|
|
35
|
+
name
|
|
36
|
+
})
|
|
37
|
+
.then(response => (
|
|
38
|
+
response.data
|
|
39
|
+
))
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export const PostCopy = (id: number, type: ('normal' | 'alternate'), name: string): UseMutationResult<void> => (
|
|
45
|
+
useMutation({
|
|
46
|
+
mutationKey: ['operator-prices-copy', { id, type, name }],
|
|
47
|
+
mutationFn: async () => (
|
|
48
|
+
await apiClient
|
|
49
|
+
.post('/api/prices/operator/copy', {
|
|
50
|
+
id,
|
|
51
|
+
type,
|
|
52
|
+
name
|
|
53
|
+
})
|
|
54
|
+
.then(response => (
|
|
55
|
+
response.data
|
|
56
|
+
))
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
);
|
package/Prices/types.ts
ADDED
package/Transiting/Countries.tsx
CHANGED
|
@@ -17,7 +17,7 @@ const Countries = ({ transiting, t, onDelete }: Props): JSX.Element => {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const onDeleteConfirm = (id: number): void => {
|
|
20
|
-
if (window.confirm(t('
|
|
20
|
+
if (window.confirm(t('transiting_manage.confirm', { ns: 'confirm' }))) {
|
|
21
21
|
onDelete(id);
|
|
22
22
|
}
|
|
23
23
|
};
|
package/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import Browse from './Browse';
|
|
2
2
|
import Manage from './Manage';
|
|
3
|
+
import LocationsManage from './Locations/Manage';
|
|
4
|
+
import PricesManage from './Prices/Manage';
|
|
3
5
|
import SchedulesManage from './Schedule/Manage';
|
|
4
6
|
import TransitingManage from './Transiting/Manage';
|
|
5
7
|
import UnavailableBrowse from './Unavailable/Browse';
|
|
@@ -8,6 +10,8 @@ import UnavailableManage from './Unavailable/Manage';
|
|
|
8
10
|
export {
|
|
9
11
|
Browse,
|
|
10
12
|
Manage,
|
|
13
|
+
LocationsManage,
|
|
14
|
+
PricesManage,
|
|
11
15
|
SchedulesManage,
|
|
12
16
|
TransitingManage,
|
|
13
17
|
UnavailableBrowse,
|