@autobusal/operator-routes 1.0.5 → 1.0.7
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/Alternates/Browse.tsx +112 -0
- package/Alternates/services.ts +67 -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/index.ts +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { useParams, Navigate, useNavigate } from 'react-router-dom';
|
|
3
|
+
import { BsPlusCircleFill } from 'react-icons/bs';
|
|
4
|
+
import { Meta, BackWithTitle, Button, Table, Row } from '@autobusal/common';
|
|
5
|
+
import { usePage } from '@autobusal/hooks';
|
|
6
|
+
import { success } from '@autobusal/utilities';
|
|
7
|
+
import { GetAlternates, PostAdd, DeleteAlternate } from '../../../modules/OperatorRoutes/Alternates/services';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
url: string
|
|
11
|
+
backUrl: string
|
|
12
|
+
t: TFunction<'common'>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Browse = ({ url, backUrl, t }: Props): JSX.Element => {
|
|
16
|
+
const params = useParams();
|
|
17
|
+
|
|
18
|
+
const id = Number(params.id);
|
|
19
|
+
|
|
20
|
+
url = url.replace('[id]', String(id));
|
|
21
|
+
backUrl = backUrl.replace('[id]', String(id));
|
|
22
|
+
|
|
23
|
+
const navigate = useNavigate();
|
|
24
|
+
|
|
25
|
+
usePage('routes');
|
|
26
|
+
|
|
27
|
+
const { data, isLoading, isFetching, refetch } = GetAlternates(id);
|
|
28
|
+
|
|
29
|
+
const { mutate: Add, isPending } = PostAdd(id);
|
|
30
|
+
|
|
31
|
+
const { mutate: Delete } = DeleteAlternate(id);
|
|
32
|
+
|
|
33
|
+
if (id === 0) {
|
|
34
|
+
return <Navigate to={ backUrl } />;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const onNew = (): void => {
|
|
38
|
+
if (!window.confirm(t('alternates.browse.confirm', { ns: 'common' }))) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Add({}, {
|
|
43
|
+
onSuccess: (data) => {
|
|
44
|
+
success(t('alternates.browse.messages.saved', { ns: 'common' }));
|
|
45
|
+
|
|
46
|
+
navigate(`${ url }/manage/${ data.id }`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const onDelete = (name: number): void => {
|
|
52
|
+
Delete(name, {
|
|
53
|
+
onSuccess: () => {
|
|
54
|
+
success(t('alternates.browse.messages.deleted', { ns: 'common' }));
|
|
55
|
+
|
|
56
|
+
refetch();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const rows = data?.alternates.map(item => (
|
|
62
|
+
<Row
|
|
63
|
+
key={ item.id }
|
|
64
|
+
id={ item.id }
|
|
65
|
+
data={ [
|
|
66
|
+
`${ item.date_from } - ${ item.date_to }`
|
|
67
|
+
] }
|
|
68
|
+
/>
|
|
69
|
+
));
|
|
70
|
+
|
|
71
|
+
const title = t('alternates.browse.title', { name: data?.name, ns: 'common' });
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Meta title={ title }>
|
|
75
|
+
<BackWithTitle title={ title } to={ backUrl } t={ t } />
|
|
76
|
+
|
|
77
|
+
<Table
|
|
78
|
+
url={ url }
|
|
79
|
+
columns={ [{
|
|
80
|
+
name: t('alternates.browse.from', { ns: 'common' }),
|
|
81
|
+
width: 75
|
|
82
|
+
}] }
|
|
83
|
+
rows={ rows }
|
|
84
|
+
loading={ isLoading }
|
|
85
|
+
fetching={ isFetching }
|
|
86
|
+
t={ t }
|
|
87
|
+
actions={ [
|
|
88
|
+
'view', 'delete', 'extra'
|
|
89
|
+
] }
|
|
90
|
+
handlers={ {
|
|
91
|
+
delete: onDelete
|
|
92
|
+
} }
|
|
93
|
+
extra={
|
|
94
|
+
<Button
|
|
95
|
+
type="button"
|
|
96
|
+
loading={ isPending }
|
|
97
|
+
text={
|
|
98
|
+
<>
|
|
99
|
+
<BsPlusCircleFill />
|
|
100
|
+
{ t('table.actions.new', { ns: 'common' }) }
|
|
101
|
+
</>
|
|
102
|
+
}
|
|
103
|
+
noMargin
|
|
104
|
+
onClick={ onNew }
|
|
105
|
+
/>
|
|
106
|
+
}
|
|
107
|
+
/>
|
|
108
|
+
</Meta>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default Browse;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { UseMutationResult, UseQueryResult, useMutation, useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { apiClient } from '@autobusal/providers';
|
|
3
|
+
import { RouteData, AlternateData } from '@autobusal/providers/types/routes';
|
|
4
|
+
import { ScheduleForm } from '../Prices/types';
|
|
5
|
+
|
|
6
|
+
export const GetAlternates = (id: number): UseQueryResult<RouteData> => (
|
|
7
|
+
useQuery({
|
|
8
|
+
queryKey: ['operator-alternates', { id }],
|
|
9
|
+
queryFn: async () => (
|
|
10
|
+
await apiClient
|
|
11
|
+
.get('/api/alternates/operator/browse', {
|
|
12
|
+
params: { id }
|
|
13
|
+
})
|
|
14
|
+
.then(response => (
|
|
15
|
+
response.data
|
|
16
|
+
))
|
|
17
|
+
)
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const PostAdd = (id: number): UseMutationResult<AlternateData> => (
|
|
22
|
+
useMutation({
|
|
23
|
+
mutationKey: ['operator-alternates-add', { id }],
|
|
24
|
+
mutationFn: async () => (
|
|
25
|
+
await apiClient
|
|
26
|
+
.post('/api/alternates/operator/add', { id })
|
|
27
|
+
.then(response => (
|
|
28
|
+
response.data
|
|
29
|
+
))
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export const PostSchedule = (id: number, name: string): UseMutationResult<void, Error, ScheduleForm, unknown> => (
|
|
35
|
+
useMutation({
|
|
36
|
+
mutationKey: ['operator-alternates-schedule', { id, name }],
|
|
37
|
+
mutationFn: async (data: ScheduleForm) => (
|
|
38
|
+
await apiClient
|
|
39
|
+
.post('/api/alternates/operator/schedule', {
|
|
40
|
+
...data,
|
|
41
|
+
id,
|
|
42
|
+
name
|
|
43
|
+
})
|
|
44
|
+
.then(response => (
|
|
45
|
+
response.data
|
|
46
|
+
))
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export const DeleteAlternate = (id: number): UseMutationResult<void, Error, number, unknown> => (
|
|
52
|
+
useMutation({
|
|
53
|
+
mutationKey: ['operator-alternates-delete'],
|
|
54
|
+
mutationFn: async (name: number) => (
|
|
55
|
+
await apiClient
|
|
56
|
+
.delete('/api/alternates/operator/delete', {
|
|
57
|
+
params: {
|
|
58
|
+
id,
|
|
59
|
+
name
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
.then(response => (
|
|
63
|
+
response.data
|
|
64
|
+
))
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
);
|
|
@@ -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 '../../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/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import Browse from './Browse';
|
|
2
2
|
import Manage from './Manage';
|
|
3
|
+
import AlternatesBrowse from './Alternates/Browse';
|
|
3
4
|
import LocationsManage from './Locations/Manage';
|
|
5
|
+
import PricesManage from './Prices/Manage';
|
|
4
6
|
import SchedulesManage from './Schedule/Manage';
|
|
5
7
|
import TransitingManage from './Transiting/Manage';
|
|
6
8
|
import UnavailableBrowse from './Unavailable/Browse';
|
|
@@ -9,7 +11,9 @@ import UnavailableManage from './Unavailable/Manage';
|
|
|
9
11
|
export {
|
|
10
12
|
Browse,
|
|
11
13
|
Manage,
|
|
14
|
+
AlternatesBrowse,
|
|
12
15
|
LocationsManage,
|
|
16
|
+
PricesManage,
|
|
13
17
|
SchedulesManage,
|
|
14
18
|
TransitingManage,
|
|
15
19
|
UnavailableBrowse,
|