@autobusal/common 1.27.4 → 1.28.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 +31 -0
- package/Table/Actions/Icon.tsx +12 -1
- package/Table/Row.tsx +12 -0
- package/Table/Rows/NotFound.tsx +4 -2
- package/Table/Rows/Rows.tsx +14 -6
- package/Table/Table.tsx +13 -1
- package/Viewer/Item.tsx +23 -2
- package/Viewer/Items/Dropdown.tsx +20 -2
- package/Viewer/styles.ts +25 -1
- package/Viewer/types.ts +8 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## 1.28.0
|
|
5
|
+
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- **`Row` takes an optional `actions`**, overriding the table's list for that
|
|
9
|
+
row. The table-level prop is one array for every row, which cannot express
|
|
10
|
+
an action only some rows have earned - a ticket download belongs to a paid
|
|
11
|
+
order and to no other. `Rows` falls back to the table's list, so every
|
|
12
|
+
existing caller is unaffected.
|
|
13
|
+
- **`print` and `invoice` row-action icons.** Distinct from the existing
|
|
14
|
+
generic `download`.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## 1.27.5
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`placeholder` on a `select`** — a leading empty option, for "nothing
|
|
22
|
+
chosen".
|
|
23
|
+
|
|
24
|
+
A select whose value matches no option silently displays the first one and
|
|
25
|
+
the browser submits it as though it had been picked. That is exactly how
|
|
26
|
+
every profile whose owner never touched the country field was saved as
|
|
27
|
+
**Afghanistan**: it sorts first, and nothing had ever been selected.
|
|
28
|
+
Changing the default from `0` to `''` did not help on its own — neither
|
|
29
|
+
value exists as an option, so the browser fell back either way.
|
|
30
|
+
|
|
31
|
+
Rendered only when placeholder text is supplied, so a select with a real
|
|
32
|
+
default (sex, status, a type) does not grow an empty row it never wanted.
|
|
33
|
+
|
|
3
34
|
## 1.27.4
|
|
4
35
|
|
|
5
36
|
### Fixed
|
package/Table/Actions/Icon.tsx
CHANGED
|
@@ -4,7 +4,8 @@ import { BiEditAlt, BiTransfer, BiTrash } from 'react-icons/bi';
|
|
|
4
4
|
import { FaTrash } from 'react-icons/fa';
|
|
5
5
|
import { GiMoneyStack } from 'react-icons/gi';
|
|
6
6
|
import { IoDocumentLockOutline } from 'react-icons/io5';
|
|
7
|
-
import {
|
|
7
|
+
import { FaFileInvoice } from 'react-icons/fa';
|
|
8
|
+
import { RiBusLine, RiExternalLinkLine, RiEyeLine, RiLoginCircleLine, RiPrinterLine, RiReplyLine } from 'react-icons/ri';
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
type: string
|
|
@@ -57,6 +58,16 @@ const Icon = ({ type, t }: Props): (JSX.Element | null) => {
|
|
|
57
58
|
case 'download':
|
|
58
59
|
return <AiOutlineCloudDownload title={ t('table.actions.custom.download', { ns: 'common' }) } />;
|
|
59
60
|
|
|
61
|
+
// Edited: Ferjolt Ozuni - Date: 2026-08-07
|
|
62
|
+
// The ticket and its invoice, for tables that can hand both over
|
|
63
|
+
// directly - the order listing does. Distinct from 'download' above,
|
|
64
|
+
// which is the generic one already spoken for by the invoice queue.
|
|
65
|
+
case 'print':
|
|
66
|
+
return <RiPrinterLine title={ t('table.actions.custom.print', { ns: 'common' }) } />;
|
|
67
|
+
|
|
68
|
+
case 'invoice':
|
|
69
|
+
return <FaFileInvoice title={ t('table.actions.custom.invoice', { ns: 'common' }) } />;
|
|
70
|
+
|
|
60
71
|
case 'bus':
|
|
61
72
|
return <RiBusLine title={ t('table.actions.custom.bus', { ns: 'common' }) } />;
|
|
62
73
|
|
package/Table/Row.tsx
CHANGED
|
@@ -2,6 +2,18 @@ import { Td } from './styles';
|
|
|
2
2
|
|
|
3
3
|
interface Props {
|
|
4
4
|
id?: number
|
|
5
|
+
/*
|
|
6
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-07
|
|
7
|
+
*
|
|
8
|
+
* Options for THIS row, overriding the table's own list. The table-level
|
|
9
|
+
* `actions` prop is one array for every row, which cannot express an
|
|
10
|
+
* action that only some rows have earned - a ticket download belongs to a
|
|
11
|
+
* paid order and to no other.
|
|
12
|
+
*
|
|
13
|
+
* Read by Rows/Rows.tsx off this element's props; Row itself renders only
|
|
14
|
+
* the data cells, so it does not use the value.
|
|
15
|
+
*/
|
|
16
|
+
actions?: string[]
|
|
5
17
|
data: any[]
|
|
6
18
|
}
|
|
7
19
|
|
package/Table/Rows/NotFound.tsx
CHANGED
|
@@ -4,15 +4,17 @@ import { ContainerNotFound } from './styles';
|
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
length: number
|
|
7
|
+
// An explanation, where there is one worth giving - see Table's `empty`.
|
|
8
|
+
message?: string
|
|
7
9
|
t: TFunction<'common'>
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
const NotFound = ({ length, t }: Props): JSX.Element => (
|
|
12
|
+
const NotFound = ({ length, message, t }: Props): JSX.Element => (
|
|
11
13
|
<tbody>
|
|
12
14
|
<tr>
|
|
13
15
|
<td colSpan={ length }>
|
|
14
16
|
<ContainerNotFound>
|
|
15
|
-
<RiEmotionUnhappyLine />{ t('table.no_data', { ns: 'common' }) }
|
|
17
|
+
<RiEmotionUnhappyLine />{ message ?? t('table.no_data', { ns: 'common' }) }
|
|
16
18
|
</ContainerNotFound>
|
|
17
19
|
</td>
|
|
18
20
|
</tr>
|
package/Table/Rows/Rows.tsx
CHANGED
|
@@ -13,19 +13,20 @@ interface Props {
|
|
|
13
13
|
loading: boolean
|
|
14
14
|
fetching: boolean
|
|
15
15
|
colLength: number
|
|
16
|
+
empty?: string
|
|
16
17
|
t: TFunction<'common'>
|
|
17
18
|
actions?: string[]
|
|
18
19
|
handlers?: any
|
|
19
20
|
navigate: NavigateFunction
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const Rows = ({ url, urlWith, data, loading, fetching, colLength, t, actions, handlers, navigate }: Props): JSX.Element => {
|
|
23
|
+
const Rows = ({ url, urlWith, data, loading, fetching, colLength, empty, t, actions, handlers, navigate }: Props): JSX.Element => {
|
|
23
24
|
if (loading) {
|
|
24
25
|
return <Loading length={ colLength } t={ t } />;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
if (data?.length === 0) {
|
|
28
|
-
return <NotFound length={ colLength } t={ t } />;
|
|
29
|
+
return <NotFound length={ colLength } message={ empty } t={ t } />;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
const isViewable = actions?.includes('view');
|
|
@@ -36,23 +37,30 @@ const Rows = ({ url, urlWith, data, loading, fetching, colLength, t, actions, ha
|
|
|
36
37
|
}
|
|
37
38
|
};
|
|
38
39
|
|
|
39
|
-
const columns = data?.map((item, index) =>
|
|
40
|
+
const columns = data?.map((item, index) => {
|
|
41
|
+
// Edited: Ferjolt Ozuni - Date: 2026-08-07
|
|
42
|
+
// A row may name its own options - see Row.tsx. Falls back to the
|
|
43
|
+
// table's list, which is what every existing caller relies on.
|
|
44
|
+
const available = item.props.actions ?? actions;
|
|
45
|
+
|
|
46
|
+
return (
|
|
40
47
|
<Tr key={ index } $viewable={ isViewable } onClick={ () => onClick(item.props.id) }>
|
|
41
48
|
{ item }
|
|
42
49
|
|
|
43
|
-
{ rowActions(
|
|
50
|
+
{ rowActions(available).length > 0 && (
|
|
44
51
|
<Actions
|
|
45
52
|
id={ item.props.id }
|
|
46
53
|
url={ url }
|
|
47
54
|
urlWith={ urlWith }
|
|
48
|
-
available={
|
|
55
|
+
available={ available }
|
|
49
56
|
t={ t }
|
|
50
57
|
handlers={ handlers }
|
|
51
58
|
navigate={ navigate }
|
|
52
59
|
/>
|
|
53
60
|
) }
|
|
54
61
|
</Tr>
|
|
55
|
-
|
|
62
|
+
);
|
|
63
|
+
});
|
|
56
64
|
|
|
57
65
|
return (
|
|
58
66
|
<Tbody $fetching={ fetching }>
|
package/Table/Table.tsx
CHANGED
|
@@ -21,10 +21,21 @@ interface Props {
|
|
|
21
21
|
t: TFunction<'common'>
|
|
22
22
|
actions?: string[]
|
|
23
23
|
extra?: JSX.Element
|
|
24
|
+
/*
|
|
25
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-07
|
|
26
|
+
* What to say when the table is empty for a REASON, rather than merely
|
|
27
|
+
* empty. "No data found" is right for a search that matched nothing and
|
|
28
|
+
* useless for a screen that will stay empty until somebody else acts -
|
|
29
|
+
* a new agency's route list reads as a broken page rather than as one
|
|
30
|
+
* waiting on an admin to grant it operators.
|
|
31
|
+
*
|
|
32
|
+
* Optional: a table that does not pass one keeps the generic message.
|
|
33
|
+
*/
|
|
34
|
+
empty?: string
|
|
24
35
|
handlers?: any
|
|
25
36
|
}
|
|
26
37
|
|
|
27
|
-
const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, fetching, t, actions, extra, handlers }: Props): JSX.Element => {
|
|
38
|
+
const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, fetching, t, actions, extra, empty, handlers }: Props): JSX.Element => {
|
|
28
39
|
const navigate = useNavigate();
|
|
29
40
|
|
|
30
41
|
const colLength = columns.length + 1;
|
|
@@ -59,6 +70,7 @@ const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, f
|
|
|
59
70
|
loading={ loading }
|
|
60
71
|
fetching={ fetching }
|
|
61
72
|
colLength={ colLength }
|
|
73
|
+
empty={ empty }
|
|
62
74
|
t={ t }
|
|
63
75
|
actions={ actions }
|
|
64
76
|
handlers={ handlers }
|
package/Viewer/Item.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FieldErrors, UseFormRegister, UseFormSetValue } from 'react-hook-form';
|
|
2
2
|
import { Display } from '@autobusal/utilities';
|
|
3
3
|
import Data from './Data';
|
|
4
|
-
import { Notice } from './styles';
|
|
4
|
+
import { Notice, Label, Required } from './styles';
|
|
5
5
|
import { ViewData } from './types';
|
|
6
6
|
import { TFunction } from 'i18next';
|
|
7
7
|
|
|
@@ -16,7 +16,28 @@ interface Props {
|
|
|
16
16
|
|
|
17
17
|
const Item = ({ id, data, refs, t, errors, onUpdate }: Props): JSX.Element => (
|
|
18
18
|
<div className={ `row row-${ data.type } ${ data.label === undefined && 'spacer' }` }>
|
|
19
|
-
{
|
|
19
|
+
{/*
|
|
20
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-07
|
|
21
|
+
*
|
|
22
|
+
* Required fields say so. Read straight off the validation rules rather
|
|
23
|
+
* than a second flag per field, so a label cannot promise something the
|
|
24
|
+
* form does not enforce - or stay silent about something it does.
|
|
25
|
+
*
|
|
26
|
+
* Label and asterisk share ONE element on purpose. The row is a flex
|
|
27
|
+
* COLUMN, so a bare text node beside a sibling span makes two flex
|
|
28
|
+
* items and the asterisk drops onto a line of its own underneath.
|
|
29
|
+
*
|
|
30
|
+
* Only rendered when there is a label at all - a row without one is the
|
|
31
|
+
* spacer, and an empty element there would take a flex slot and open a
|
|
32
|
+
* gap where the divider should be.
|
|
33
|
+
*/}
|
|
34
|
+
{ data.label !== undefined && (
|
|
35
|
+
<Label>
|
|
36
|
+
{ data.label }
|
|
37
|
+
|
|
38
|
+
{ data.rules?.includes('required') && <Required aria-hidden="true">*</Required> }
|
|
39
|
+
</Label>
|
|
40
|
+
) }
|
|
20
41
|
|
|
21
42
|
<Data
|
|
22
43
|
id={ id }
|
|
@@ -30,9 +30,27 @@ const Dropdown = ({ item, refs, t }: Props): (JSX.Element | null) => {
|
|
|
30
30
|
{ item.name }
|
|
31
31
|
</option>
|
|
32
32
|
));
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-06
|
|
36
|
+
*
|
|
37
|
+
* A select whose value matches NO option silently shows the first one, and
|
|
38
|
+
* the browser then submits it as though it had been chosen. That is how
|
|
39
|
+
* every profile whose owner did not touch the country field was saved as
|
|
40
|
+
* Afghanistan - it sorts first, and nothing had ever been selected.
|
|
41
|
+
*
|
|
42
|
+
* An empty leading option gives "nothing chosen" somewhere to live, so the
|
|
43
|
+
* field reads as unanswered and a `required` rule can say so. Rendered
|
|
44
|
+
* only when the caller supplies placeholder text: a select with a real
|
|
45
|
+
* default (sex, status, a type) should not grow an empty row it does not
|
|
46
|
+
* want.
|
|
47
|
+
*/
|
|
34
48
|
return (
|
|
35
|
-
<select defaultValue={ item?.value } { ...refs(item.name, rules) }>
|
|
49
|
+
<select defaultValue={ item?.value ?? '' } { ...refs(item.name, rules) }>
|
|
50
|
+
{ item.placeholder !== undefined && (
|
|
51
|
+
<option value="">{ item.placeholder }</option>
|
|
52
|
+
) }
|
|
53
|
+
|
|
36
54
|
{ values }
|
|
37
55
|
</select>
|
|
38
56
|
);
|
package/Viewer/styles.ts
CHANGED
|
@@ -25,6 +25,17 @@ export const Container = styled.div`
|
|
|
25
25
|
height: 1px;
|
|
26
26
|
background: ${ props => props.theme.background.neutral };
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-07
|
|
31
|
+
* A rich-text editor gets the full row. At a third of the width it has
|
|
32
|
+
* less room for its own toolbar than for the text, and every consumer
|
|
33
|
+
* that uses one wants it wide - so this belongs here rather than as a
|
|
34
|
+
* flag each caller has to remember to pass.
|
|
35
|
+
*/
|
|
36
|
+
& > div.row-textarea-html {
|
|
37
|
+
flex-basis: 100% !important;
|
|
38
|
+
}
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
@media (min-width: 1300px) {
|
|
@@ -55,4 +66,17 @@ export const Notice = styled.div`
|
|
|
55
66
|
font-size: ${ props => props.theme.size.xxs };
|
|
56
67
|
color: ${ props => props.theme.font.info };
|
|
57
68
|
font-weight: 400;
|
|
58
|
-
`;
|
|
69
|
+
`;
|
|
70
|
+
/*
|
|
71
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-07
|
|
72
|
+
* The asterisk beside a required field's label. aria-hidden where it is
|
|
73
|
+
* used - the requirement is already carried to assistive tech by the
|
|
74
|
+
* field's own validation, and a lone "*" read aloud says nothing.
|
|
75
|
+
*/
|
|
76
|
+
export const Label = styled.span``;
|
|
77
|
+
|
|
78
|
+
export const Required = styled.span`
|
|
79
|
+
margin-left: 3px;
|
|
80
|
+
color: ${ props => props.theme.font.error };
|
|
81
|
+
font-weight: 700;
|
|
82
|
+
`;
|
package/Viewer/types.ts
CHANGED
|
@@ -5,6 +5,14 @@ export interface ViewData {
|
|
|
5
5
|
name?: string
|
|
6
6
|
type?: 'text' | 'select' | 'date' | 'dob' | 'picker' | 'textarea' | 'textarea-html' | 'file' | 'files' | 'email' | 'password' | 'display' | 'component' | 'checkbox' | 'checkbox-list' | 'checkbox-list-all' | 'link' | 'number' | 'float' | 'date-picker'
|
|
7
7
|
value?: string | number | JSX.Element
|
|
8
|
+
/**
|
|
9
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-06
|
|
10
|
+
* Leading empty option for a select, for "nothing chosen". Without one a
|
|
11
|
+
* select whose value matches no option silently shows - and submits - the
|
|
12
|
+
* first, which is how profiles were being saved as Afghanistan.
|
|
13
|
+
*/
|
|
14
|
+
placeholder?: string
|
|
15
|
+
|
|
8
16
|
values?: DropdownData[]
|
|
9
17
|
selected?: any[]
|
|
10
18
|
rules?: string
|