@autobusal/common 1.35.0 → 1.36.1
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 +4 -0
- package/SeoOverride/SeoOverride.tsx +42 -0
- package/Viewer/Actions.tsx +35 -2
- package/Viewer/Viewer.tsx +13 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.0 (2026-08-29)
|
|
4
|
+
|
|
5
|
+
- Editors offer Save & Close alongside Save & Stay, so correcting a record no longer bounces you back to its list every time (opt-in per page, editing only). The pair is driven from here (Viewer + Actions).
|
|
6
|
+
|
|
3
7
|
## 1.35.0 (2026-08-29)
|
|
4
8
|
|
|
5
9
|
- RouteFeature's compact chip draws a real tooltip on hover AND on focus, so a tap reveals the amenity name; `title` alone was invisible on touch and slow on desktop.
|
|
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
|
|
2
2
|
import { TFunction } from 'i18next';
|
|
3
3
|
import { Container, ContainerLocales, LocaleButton, Notice, Textarea } from './styles';
|
|
4
4
|
import { useGetSettings } from '@autobusal/providers/services';
|
|
5
|
+
import AiFieldAssist from '../AiAssist/AiFieldAssist';
|
|
5
6
|
|
|
6
7
|
export interface SeoEntry {
|
|
7
8
|
title?: string
|
|
@@ -64,6 +65,26 @@ const SeoOverride = ({ value, t, onChange }: Props): JSX.Element => {
|
|
|
64
65
|
});
|
|
65
66
|
};
|
|
66
67
|
|
|
68
|
+
/*
|
|
69
|
+
* Claude - 2026-08-29 (asked for by Ferjolt): "when we have any AI
|
|
70
|
+
* provider enabled, the SEO should make use of that ai by using it to
|
|
71
|
+
* generate, get suggestions or translate the fields".
|
|
72
|
+
*
|
|
73
|
+
* The default language's own copy, which is what a translation is made
|
|
74
|
+
* FROM. Undefined while editing the default language itself, which is
|
|
75
|
+
* what hides the Translate trigger there - there is nothing to translate
|
|
76
|
+
* from when you are standing on the source.
|
|
77
|
+
*
|
|
78
|
+
* AiFieldAssist renders nothing at all when no provider is enabled, so
|
|
79
|
+
* this needs no flag of its own and no condition here: a brand with AI
|
|
80
|
+
* switched off sees exactly the editor it sees today.
|
|
81
|
+
*/
|
|
82
|
+
const isDefault = locale === data.preferences.languages.default;
|
|
83
|
+
|
|
84
|
+
const source = (field: keyof SeoEntry): string | undefined => (
|
|
85
|
+
isDefault ? undefined : overrides[data.preferences.languages.default]?.[field]
|
|
86
|
+
);
|
|
87
|
+
|
|
67
88
|
const written = (language: string): boolean => {
|
|
68
89
|
const entry = overrides[language];
|
|
69
90
|
|
|
@@ -104,6 +125,14 @@ const SeoOverride = ({ value, t, onChange }: Props): JSX.Element => {
|
|
|
104
125
|
value={ current.title ?? '' }
|
|
105
126
|
onChange={ (event) => update('title', event.target.value) }
|
|
106
127
|
/>
|
|
128
|
+
|
|
129
|
+
<AiFieldAssist
|
|
130
|
+
text={ current.title }
|
|
131
|
+
sourceText={ source('title') }
|
|
132
|
+
targetLocale={ isDefault ? undefined : locale }
|
|
133
|
+
onInsert={ (content: string) => update('title', content.slice(0, 250)) }
|
|
134
|
+
t={ t }
|
|
135
|
+
/>
|
|
107
136
|
</div>
|
|
108
137
|
|
|
109
138
|
<div className="row row-nomargin">
|
|
@@ -114,6 +143,19 @@ const SeoOverride = ({ value, t, onChange }: Props): JSX.Element => {
|
|
|
114
143
|
value={ current.description ?? '' }
|
|
115
144
|
onChange={ (event) => update('description', event.target.value) }
|
|
116
145
|
/>
|
|
146
|
+
|
|
147
|
+
{ /* Truncated to the field's own maxLength on the way in: the model
|
|
148
|
+
is asked for a meta description and generally obliges, but
|
|
149
|
+
nothing about a language model guarantees a length, and a draft
|
|
150
|
+
that silently exceeded what the server will store would be
|
|
151
|
+
saved cut off with no warning. */ }
|
|
152
|
+
<AiFieldAssist
|
|
153
|
+
text={ current.description }
|
|
154
|
+
sourceText={ source('description') }
|
|
155
|
+
targetLocale={ isDefault ? undefined : locale }
|
|
156
|
+
onInsert={ (content: string) => update('description', content.slice(0, 500)) }
|
|
157
|
+
t={ t }
|
|
158
|
+
/>
|
|
117
159
|
</div>
|
|
118
160
|
</Container>
|
|
119
161
|
);
|
package/Viewer/Actions.tsx
CHANGED
|
@@ -19,6 +19,25 @@ interface Props {
|
|
|
19
19
|
const Actions = ({ id, available, pending, confirm, t, onDelete }: Props): JSX.Element => {
|
|
20
20
|
const options: ActionData[] = [];
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Edited: Claude - Date: 2026-08-29
|
|
24
|
+
*
|
|
25
|
+
* Two ways to save, when the page asks for them (actions={['save',
|
|
26
|
+
* 'save_stay', ...]}): one that returns to the list this record came
|
|
27
|
+
* from, one that keeps it open for another edit. Saving used to always
|
|
28
|
+
* throw you back to the list, so correcting three fields on one record
|
|
29
|
+
* meant three round trips through it.
|
|
30
|
+
*
|
|
31
|
+
* Opt-in per page, because "close" has to mean something: the brand
|
|
32
|
+
* settings, the ticket editors and the notification preferences have no
|
|
33
|
+
* list to go back to and keep their single Save.
|
|
34
|
+
*
|
|
35
|
+
* ONLY WHEN EDITING. On a create form there is nothing to stay on - the
|
|
36
|
+
* record has no id yet, so the form would sit there still full, and the
|
|
37
|
+
* obvious next click would file a duplicate.
|
|
38
|
+
*/
|
|
39
|
+
const paired = available.includes('save_stay') && id > 0;
|
|
40
|
+
|
|
22
41
|
const onAskDelete = (): void => {
|
|
23
42
|
if (window.confirm(confirm ?? t('table.confirm', { ns: 'common' }))) {
|
|
24
43
|
if (onDelete) {
|
|
@@ -37,7 +56,9 @@ const Actions = ({ id, available, pending, confirm, t, onDelete }: Props): JSX.E
|
|
|
37
56
|
|
|
38
57
|
if (available.includes('save')) {
|
|
39
58
|
options.push({
|
|
40
|
-
|
|
59
|
+
// with a second save button beside it, "Save" no longer says what
|
|
60
|
+
// THIS one does
|
|
61
|
+
label: t(paired ? 'table.actions.save_close' : 'table.actions.save', { ns: 'common' }),
|
|
41
62
|
icon: <RiSave2Line />,
|
|
42
63
|
type: 'submit'
|
|
43
64
|
});
|
|
@@ -68,12 +89,24 @@ const Actions = ({ id, available, pending, confirm, t, onDelete }: Props): JSX.E
|
|
|
68
89
|
|
|
69
90
|
if (available.includes('update')) {
|
|
70
91
|
options.push({
|
|
71
|
-
label: t('table.actions.update', { ns: 'common' }),
|
|
92
|
+
label: t(paired ? 'table.actions.save_close' : 'table.actions.update', { ns: 'common' }),
|
|
72
93
|
icon: <BiEditAlt />,
|
|
73
94
|
type: 'submit'
|
|
74
95
|
});
|
|
75
96
|
}
|
|
76
97
|
|
|
98
|
+
// second, so the first submit button - the one Enter presses - stays the
|
|
99
|
+
// one that has always been there
|
|
100
|
+
if (paired) {
|
|
101
|
+
options.push({
|
|
102
|
+
label: t('table.actions.save_stay', { ns: 'common' }),
|
|
103
|
+
icon: <RiSave2Line />,
|
|
104
|
+
type: 'submit',
|
|
105
|
+
name: 'intent',
|
|
106
|
+
value: 'stay'
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
77
110
|
if (available.includes('approve')) {
|
|
78
111
|
options.push({
|
|
79
112
|
label: t('table.actions.approve', { ns: 'common' }),
|
package/Viewer/Viewer.tsx
CHANGED
|
@@ -21,7 +21,15 @@ interface Props {
|
|
|
21
21
|
*/
|
|
22
22
|
confirm?: string
|
|
23
23
|
t: TFunction<'common'>
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Edited: Claude - Date: 2026-08-29
|
|
26
|
+
*
|
|
27
|
+
* `stay` is true when the editor was saved with "Save & Stay" rather than
|
|
28
|
+
* "Save & Close" - the page keeps the record open instead of returning to
|
|
29
|
+
* its list. Optional, so a form that never navigates anyway (the brand
|
|
30
|
+
* settings, the ticket editors) needs no change and offers one button.
|
|
31
|
+
*/
|
|
32
|
+
onSave?: (data: any, stay?: boolean) => void
|
|
25
33
|
onDelete?: (data?: any) => void
|
|
26
34
|
// Edited: Ferjolt Ozuni - Date: 2026-07-31
|
|
27
35
|
// Optional second submit-type action (actions={['save', 'test']}) - e.g.
|
|
@@ -62,7 +70,10 @@ const Viewer = ({ id, type, data, fetching, pending, actions, confirm, t, onSave
|
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
if (onSave !== undefined) {
|
|
65
|
-
|
|
73
|
+
// 'stay' is set by the second save button (see Actions.tsx); every
|
|
74
|
+
// other submitter - including plain Enter, which fires the FIRST
|
|
75
|
+
// button - leaves it undefined and the page closes as it always has
|
|
76
|
+
onSave(data, submitter?.value === 'stay');
|
|
66
77
|
}
|
|
67
78
|
};
|
|
68
79
|
|