@autobusal/operator-routes 1.5.1 → 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 CHANGED
@@ -1,5 +1,31 @@
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
+
16
+ ## 1.5.2
17
+
18
+ ### Removed
19
+
20
+ - **The confirm dialog in front of removing a stop.** It asked the question
21
+ the withdrawal panel already asks - the panel names the stop, lists
22
+ everybody still travelling through it and has its own button - and it
23
+ answered one part wrongly, warning that the fares would have to be entered
24
+ again. That stopped being true when calibration began reconciling price
25
+ rows instead of rebuilding them: measured on a ten-stop route, withdrawing
26
+ a stop keeps fourteen of sixteen fares and drops only the two legs through
27
+ the stop itself.
28
+
3
29
  ## 1.3.3
4
30
 
5
31
  ### Fixed
@@ -24,11 +24,29 @@ const Actions = ({ item, loading, t, onMove, onDelete }: Props): JSX.Element =>
24
24
  onMove(order);
25
25
  };
26
26
 
27
- const onAskDelete = (): void => {
28
- if (window.confirm(t('locations_manage.actions.delete', { ns: 'common' }))) {
29
- onDelete();
30
- }
31
- };
27
+ /*
28
+ * Edited: Claude - Date: 2026-08-22
29
+ *
30
+ * No window.confirm here any more. Removing a stop opens the withdrawal
31
+ * panel, which names the stop, lists everybody still travelling through
32
+ * it, and has its own explicit button - a far better confirmation than a
33
+ * browser dialog, and the dialog now stood in front of it saying "YOU WILL
34
+ * NEED TO ADD THE PRICES AGAIN", which stopped being true when
35
+ * Locations\Calibration started reconciling price rows instead of
36
+ * rebuilding them. Measured: withdrawing a stop from a ten-stop route kept
37
+ * fourteen of sixteen fares, losing only the two legs through the stop
38
+ * itself.
39
+ *
40
+ * So it was a second gate that asked the same question twice and got the
41
+ * answer wrong once. The panel asks it properly.
42
+ *
43
+ * `locations_manage.actions.delete` is deliberately still in obtapi's
44
+ * language files. obtapi deploys BEFORE the frontends, so for the length
45
+ * of that window the previous bundle is still calling it - and a missing
46
+ * key renders as the key itself, which is what the dialog showed when it
47
+ * was removed. It now says something true and is simply read by nobody
48
+ * once this bundle lands.
49
+ */
32
50
 
33
51
  return (
34
52
  <ContainerActions>
@@ -80,7 +98,7 @@ const Actions = ({ item, loading, t, onMove, onDelete }: Props): JSX.Element =>
80
98
  </>
81
99
  }
82
100
  noMargin
83
- onClick={ onAskDelete }
101
+ onClick={ onDelete }
84
102
  />
85
103
  </Group>
86
104
  </ContainerActions>
@@ -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
+ `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/operator-routes",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"