@autobusal/operator-routes 1.10.3 → 1.10.5
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/Prices/Browse/Browse.tsx +1 -0
- package/Prices/Browse/Group.tsx +62 -4
- package/Prices/services.ts +27 -0
- package/package.json +1 -1
package/Prices/Browse/Browse.tsx
CHANGED
package/Prices/Browse/Group.tsx
CHANGED
|
@@ -3,18 +3,21 @@ import { TFunction } from 'i18next';
|
|
|
3
3
|
import { useForm } from 'react-hook-form';
|
|
4
4
|
import { BiEditAlt } from 'react-icons/bi';
|
|
5
5
|
import { IoArrowDown } from 'react-icons/io5';
|
|
6
|
-
import { FaRegCopy } from 'react-icons/fa';
|
|
6
|
+
import { FaRegCopy, FaArrowDown } from 'react-icons/fa';
|
|
7
7
|
import { Button } from '@autobusal/common';
|
|
8
8
|
import { Validate, Display, success } from '@autobusal/utilities';
|
|
9
9
|
import { Group as Container, Header, Origin, Country, Tools, Dirty, Copy, Columns, Column, Fill, Row, Destination, Changed } from './styles';
|
|
10
10
|
import { FareColumn, GroupForm, OriginGroup } from '../types';
|
|
11
11
|
import { COLUMNS, field, toForm, toPrices } from '../utilities';
|
|
12
|
-
import { usePostPrices, usePostCopy } from '../services';
|
|
12
|
+
import { usePostPrices, usePostCopy, usePostSpread } from '../services';
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
id: number
|
|
16
16
|
type: 'normal' | 'alternate'
|
|
17
17
|
group: OriginGroup
|
|
18
|
+
|
|
19
|
+
/** whether the route has more than this one origin - nothing to spread to otherwise */
|
|
20
|
+
others: boolean
|
|
18
21
|
name: string
|
|
19
22
|
t: TFunction<'normal'>
|
|
20
23
|
onReload: () => void
|
|
@@ -35,7 +38,7 @@ interface Props {
|
|
|
35
38
|
* that changed carry a dot - on a twelve-destination group, pressing Save
|
|
36
39
|
* should not be a guess about what you edited near the top.
|
|
37
40
|
*/
|
|
38
|
-
const Group = ({ id, type, group, name, t, onReload }: Props): JSX.Element => {
|
|
41
|
+
const Group = ({ id, type, group, others, name, t, onReload }: Props): JSX.Element => {
|
|
39
42
|
const defaults = useMemo(() => toForm(group.prices), [ group.prices ]);
|
|
40
43
|
|
|
41
44
|
const { register, handleSubmit, setValue, getValues, reset, formState: { errors, isDirty, dirtyFields } } = useForm<GroupForm>({
|
|
@@ -54,6 +57,7 @@ const Group = ({ id, type, group, name, t, onReload }: Props): JSX.Element => {
|
|
|
54
57
|
|
|
55
58
|
const { mutate: Save, isPending } = usePostPrices(id, type, name);
|
|
56
59
|
const { mutate: CopyPrices, isPending: copying } = usePostCopy(id, type, name);
|
|
60
|
+
const { mutate: Spread, isPending: spreading } = usePostSpread(id, type, name);
|
|
57
61
|
|
|
58
62
|
const city = group.from.city;
|
|
59
63
|
|
|
@@ -76,8 +80,27 @@ const Group = ({ id, type, group, name, t, onReload }: Props): JSX.Element => {
|
|
|
76
80
|
success(t('prices_manage.messages.saved_group', { city: city.name, ns: 'common' }));
|
|
77
81
|
|
|
78
82
|
// the values just written are the new baseline, so the group goes
|
|
79
|
-
// clean
|
|
83
|
+
// clean immediately rather than waiting for the round trip below
|
|
80
84
|
reset(data);
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* Claude - 2026-08-30: AND THE CACHE HAS TO FOLLOW, or the next
|
|
88
|
+
* refetch is a no-op.
|
|
89
|
+
*
|
|
90
|
+
* Resetting the form alone left react-query holding the fares from
|
|
91
|
+
* page load. A later copy writes on the server and refetches - and
|
|
92
|
+
* if what comes back happens to equal that stale cache, structural
|
|
93
|
+
* sharing hands back the SAME object reference, the memo does not
|
|
94
|
+
* recompute, the effect above never fires, and the screen keeps
|
|
95
|
+
* showing the pre-copy fares while the database holds the copied
|
|
96
|
+
* ones. Pressing Update then writes the stale figures back over the
|
|
97
|
+
* copy.
|
|
98
|
+
*
|
|
99
|
+
* Measured, not theorised: copied Tirana's first fare over a
|
|
100
|
+
* destination saved at 99 and watched the row stay at 99 with the
|
|
101
|
+
* group marked clean, while the table said 50.
|
|
102
|
+
*/
|
|
103
|
+
onReload();
|
|
81
104
|
}
|
|
82
105
|
});
|
|
83
106
|
};
|
|
@@ -96,6 +119,29 @@ const Group = ({ id, type, group, name, t, onReload }: Props): JSX.Element => {
|
|
|
96
119
|
});
|
|
97
120
|
};
|
|
98
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Hand this origin's fares to every other origin, destination by
|
|
124
|
+
* destination.
|
|
125
|
+
*
|
|
126
|
+
* Claude - 2026-08-30. The confirm spells out what it will do rather than
|
|
127
|
+
* asking "are you sure": this overwrites every other group on the route and
|
|
128
|
+
* there is no undo, so the sentence has to be the thing the operator checks
|
|
129
|
+
* before agreeing, not a formality they click through.
|
|
130
|
+
*/
|
|
131
|
+
const onSpread = (): void => {
|
|
132
|
+
if (!window.confirm(t('prices_manage.group.spread_ask', { city: city.name, ns: 'common' }))) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Spread({ location_from: group.from.id }, {
|
|
137
|
+
onSuccess: () => {
|
|
138
|
+
success(t('prices_manage.messages.spread', { city: city.name, ns: 'common' }));
|
|
139
|
+
|
|
140
|
+
onReload();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
|
|
99
145
|
/**
|
|
100
146
|
* Fill one column down the group from its first destination.
|
|
101
147
|
*
|
|
@@ -138,6 +184,18 @@ const Group = ({ id, type, group, name, t, onReload }: Props): JSX.Element => {
|
|
|
138
184
|
</Copy>
|
|
139
185
|
) }
|
|
140
186
|
|
|
187
|
+
{ others && (
|
|
188
|
+
<Copy
|
|
189
|
+
type="button"
|
|
190
|
+
disabled={ spreading }
|
|
191
|
+
onClick={ onSpread }
|
|
192
|
+
title={ t('prices_manage.group.spread_hint', { ns: 'common' }) }
|
|
193
|
+
>
|
|
194
|
+
<FaArrowDown />
|
|
195
|
+
{ t('prices_manage.group.spread', { ns: 'common' }) }
|
|
196
|
+
</Copy>
|
|
197
|
+
) }
|
|
198
|
+
|
|
141
199
|
<Button
|
|
142
200
|
type="submit"
|
|
143
201
|
active={ isDirty }
|
package/Prices/services.ts
CHANGED
|
@@ -72,6 +72,33 @@ export const usePostPrices = (id: number, type: ('normal' | 'alternate'), name:
|
|
|
72
72
|
})
|
|
73
73
|
);
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Give every other origin this one's fares, matched destination by
|
|
77
|
+
* destination.
|
|
78
|
+
*
|
|
79
|
+
* Claude - 2026-08-30. Not the same question as usePostCopy below: that one
|
|
80
|
+
* flattens a set of fares to a single figure, this one carries the SHAPE of
|
|
81
|
+
* an origin across - whatever it charges to Thessaloniki becomes what every
|
|
82
|
+
* other stop charges to Thessaloniki.
|
|
83
|
+
*/
|
|
84
|
+
export const usePostSpread = (id: number, type: ('normal' | 'alternate'), name: string): UseMutationResult<{ copied: number }, Error, { location_from: number }, unknown> => (
|
|
85
|
+
useMutation({
|
|
86
|
+
mutationKey: ['operator-prices-spread', { id, type, name }],
|
|
87
|
+
mutationFn: async (data: { location_from: number }) => (
|
|
88
|
+
await apiClient
|
|
89
|
+
.post('/api/prices/operator/copy-origins', {
|
|
90
|
+
...data,
|
|
91
|
+
id,
|
|
92
|
+
type,
|
|
93
|
+
name
|
|
94
|
+
})
|
|
95
|
+
.then(response => (
|
|
96
|
+
response.data
|
|
97
|
+
))
|
|
98
|
+
)
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
|
|
75
102
|
/**
|
|
76
103
|
* Copy the first fare over the rest.
|
|
77
104
|
*
|