@autobusal/operator-routes 1.5.2 → 1.6.0
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/CHANGELOG.md +13 -0
- package/Locations/Manage.tsx +8 -0
- package/Locations/Withdrawn/Withdrawn.tsx +74 -0
- package/Locations/Withdrawn/styles.ts +55 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **The stops taken off a route can be put back.** Withdrawal never destroyed
|
|
8
|
+
anything - the stop's row, its price rows and the fares on them all survive
|
|
9
|
+
- and both the API endpoint and this package's `useRestoreLocation` hook
|
|
10
|
+
had existed since it shipped. Nothing rendered them: the locations response
|
|
11
|
+
filters on `active`, so a withdrawn stop was never mentioned and no screen
|
|
12
|
+
could offer to bring one back. From the operator's side a withdrawal was
|
|
13
|
+
invisible and permanent, which is the one thing it was built not to be.
|
|
14
|
+
The section appears only when the route has something to restore.
|
|
15
|
+
|
|
3
16
|
## 1.5.2
|
|
4
17
|
|
|
5
18
|
### Removed
|
package/Locations/Manage.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { usePage, useBreadcrumbs } from '@autobusal/hooks';
|
|
|
5
5
|
import Add from './Add/Add';
|
|
6
6
|
import Propose from './Propose/Propose';
|
|
7
7
|
import Browse from './Browse/Browse';
|
|
8
|
+
import Withdrawn from './Withdrawn/Withdrawn';
|
|
8
9
|
import { useGetLocations, useGetStops } from './services';
|
|
9
10
|
|
|
10
11
|
interface Props {
|
|
@@ -61,6 +62,13 @@ const Manage = ({ url, t }: Props): JSX.Element => {
|
|
|
61
62
|
<Report margin={ 15 } question={ t('locations_manage.report', { ns: 'common' }) } type="location" t={ t } />
|
|
62
63
|
|
|
63
64
|
<Browse routeId={ id } data={ data?.locations } t={ t } onReload={ onReload } />
|
|
65
|
+
|
|
66
|
+
{ /* Claude - 2026-08-22: the stops taken off this route. Withdrawal
|
|
67
|
+
keeps everything - the row, the price rows, the fares - so it is
|
|
68
|
+
reversible, and the endpoint and hook to reverse it already
|
|
69
|
+
existed. Nothing rendered them, so a withdrawn stop was invisible
|
|
70
|
+
and permanent from here. Renders nothing when there are none. */ }
|
|
71
|
+
<Withdrawn routeId={ id } data={ data?.withdrawn } t={ t } onReload={ onReload } />
|
|
64
72
|
</Meta>
|
|
65
73
|
);
|
|
66
74
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { FiRotateCcw } from 'react-icons/fi';
|
|
3
|
+
import { success } from '@autobusal/utilities';
|
|
4
|
+
import { LocationData } from '@autobusal/providers/types/locations';
|
|
5
|
+
import { useRestoreLocation } from '../services';
|
|
6
|
+
import { Section, Lead, Row, Where, Restore } from './styles';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
routeId: number
|
|
10
|
+
data?: LocationData[]
|
|
11
|
+
t: TFunction<'normal'>
|
|
12
|
+
onReload: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The stops taken off this route, and the way to put one back.
|
|
17
|
+
*
|
|
18
|
+
* Claude - Date: 2026-08-22
|
|
19
|
+
*
|
|
20
|
+
* Withdrawal never destroys anything - the stop's row, its price rows and
|
|
21
|
+
* the fares on them all survive, which is what lets a ticket sold through it
|
|
22
|
+
* still say where its passenger boarded. That makes it reversible by
|
|
23
|
+
* construction, and obtapi has had the endpoint and this package has had the
|
|
24
|
+
* `useRestoreLocation` hook since the day withdrawal shipped.
|
|
25
|
+
*
|
|
26
|
+
* Nothing rendered them. `locations` filters on `active`, so the response
|
|
27
|
+
* never mentioned a withdrawn stop and no screen could offer to bring one
|
|
28
|
+
* back: from the operator's side a withdrawal was invisible and permanent,
|
|
29
|
+
* which is the one thing it was built not to be. This is the missing half.
|
|
30
|
+
*
|
|
31
|
+
* Shown only when there is something to show - an operator who has never
|
|
32
|
+
* withdrawn a stop should not be told about a concept they have not met.
|
|
33
|
+
*/
|
|
34
|
+
const Withdrawn = ({ routeId, data, t, onReload }: Props): JSX.Element | null => {
|
|
35
|
+
const { mutate: Restoring, isPending } = useRestoreLocation(routeId);
|
|
36
|
+
|
|
37
|
+
if (!data?.length) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const restore = (id: number): void => {
|
|
42
|
+
Restoring(id, {
|
|
43
|
+
onSuccess: () => {
|
|
44
|
+
success(t('locations_manage.withdrawn.restored', { ns: 'common' }));
|
|
45
|
+
|
|
46
|
+
onReload();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Section className="box">
|
|
53
|
+
<h4>{ t('locations_manage.withdrawn.title', { ns: 'common' }) }</h4>
|
|
54
|
+
|
|
55
|
+
<Lead>{ t('locations_manage.withdrawn.lead', { ns: 'common' }) }</Lead>
|
|
56
|
+
|
|
57
|
+
{ data.map(item => (
|
|
58
|
+
<Row key={ item.id }>
|
|
59
|
+
<Where>
|
|
60
|
+
<strong>{ item.city?.name }</strong>
|
|
61
|
+
<span>{ item.stop?.name }</span>
|
|
62
|
+
</Where>
|
|
63
|
+
|
|
64
|
+
<Restore type="button" disabled={ isPending } onClick={ () => restore(item.id) }>
|
|
65
|
+
<FiRotateCcw />
|
|
66
|
+
{ t('locations_manage.withdrawn.restore', { ns: 'common' }) }
|
|
67
|
+
</Restore>
|
|
68
|
+
</Row>
|
|
69
|
+
)) }
|
|
70
|
+
</Section>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default Withdrawn;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const Section = styled.div`
|
|
4
|
+
margin-bottom: 15px;
|
|
5
|
+
`;
|
|
6
|
+
|
|
7
|
+
export const Lead = styled.p`
|
|
8
|
+
margin-bottom: 12px;
|
|
9
|
+
font-size: ${ props => props.theme.size.xs };
|
|
10
|
+
opacity: .8;
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
export const Row = styled.div`
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-wrap: wrap;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
gap: 10px;
|
|
19
|
+
padding: 8px 0;
|
|
20
|
+
border-top: 1px solid ${ props => props.theme.background.neutral };
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export const Where = styled.div`
|
|
24
|
+
font-size: ${ props => props.theme.size.xs };
|
|
25
|
+
min-width: 0;
|
|
26
|
+
|
|
27
|
+
strong {
|
|
28
|
+
display: block;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
span {
|
|
32
|
+
display: block;
|
|
33
|
+
opacity: .7;
|
|
34
|
+
word-break: break-word;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
export const Restore = styled.button`
|
|
39
|
+
background: none;
|
|
40
|
+
border: 1px solid ${ props => props.theme.background.neutral };
|
|
41
|
+
border-radius: 3px;
|
|
42
|
+
padding: 5px 12px;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
font-size: ${ props => props.theme.size.xs };
|
|
45
|
+
|
|
46
|
+
svg {
|
|
47
|
+
margin-right: 6px;
|
|
48
|
+
vertical-align: -2px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&:disabled {
|
|
52
|
+
opacity: .5;
|
|
53
|
+
cursor: default;
|
|
54
|
+
}
|
|
55
|
+
`;
|