@autobusal/common 1.27.5 → 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 CHANGED
@@ -1,6 +1,19 @@
1
1
  # Changelog
2
2
 
3
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
+
4
17
  ## 1.27.5
5
18
 
6
19
  ### Added
@@ -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 { RiBusLine, RiExternalLinkLine, RiEyeLine, RiLoginCircleLine, RiReplyLine } from 'react-icons/ri';
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
 
@@ -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>
@@ -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(actions).length > 0 && (
50
+ { rowActions(available).length > 0 && (
44
51
  <Actions
45
52
  id={ item.props.id }
46
53
  url={ url }
47
54
  urlWith={ urlWith }
48
- available={ actions }
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
- { data.label }
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 }
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.27.5",
3
+ "version": "1.28.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"