@autobusal/common 1.19.0 → 1.21.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
@@ -6,6 +6,22 @@ All notable changes to `@autobusal/common` are documented here. This project fol
6
6
  > Note: 1.10.0 through 1.15.1 were published without changelog entries. The
7
7
  > gap is left as-is rather than reconstructed after the fact.
8
8
 
9
+ ## 1.21.0
10
+
11
+ `reply` joins the table actions with its own icon and label. The operator
12
+ review page was borrowing `approve`, so the button on a review about them read
13
+ "Approve" — which is the one thing they cannot do to it.
14
+
15
+ ## 1.20.0
16
+
17
+ `Rating` — stars, average and count, or nothing at all. Rendering nothing is
18
+ the feature: obtapi withholds a score until there are enough published reviews
19
+ to say something honest, so an absent rating has to read as "not saying yet"
20
+ rather than as a zero. Anything filling the gap with empty stars would turn a
21
+ new operator into a badly-rated one. Half stars are drawn, because rounding 3.5
22
+ down loses the difference between middling and good — precisely the range where
23
+ a reader is still deciding.
24
+
9
25
  ## 1.19.0
10
26
 
11
27
  `extra` joins `create` and `search` as a top-of-table slot rather than a
@@ -0,0 +1,73 @@
1
+ import { TFunction } from 'i18next';
2
+ import { AiFillStar, AiOutlineStar } from 'react-icons/ai';
3
+ import { BsStarHalf } from 'react-icons/bs';
4
+ import { Container, Stars, Average, Count } from './styles';
5
+ import { RatingData } from '@autobusal/providers/types/reviews';
6
+
7
+ interface Props {
8
+ // an aggregate: stars, the average and how many reviews built it
9
+ rating?: RatingData | null
10
+ // Edited: Ferjolt Ozuni - Date: 2026-08-03
11
+ // ONE review's own rating: stars alone. Passing it through `rating` with a
12
+ // count of 1 drew "5.0 (1)" beside every single review and a tooltip
13
+ // reading "from 1 reviews" - a summary of a set of one, which is not what
14
+ // an individual review is.
15
+ value?: number
16
+ size?: 'small' | 'normal'
17
+ t: TFunction<'common'>
18
+ }
19
+
20
+ /**
21
+ * A published rating.
22
+ *
23
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
24
+ *
25
+ * Renders NOTHING when there is no rating, and that is the important part:
26
+ * obtapi withholds the score until a route or operator has enough published
27
+ * reviews to say something honest, so an absent rating must read as "we are
28
+ * not saying yet" rather than as a zero. Anything that filled the gap with
29
+ * empty stars or "0.0" would turn a new operator into a badly-rated one.
30
+ *
31
+ * The half star matters more than it looks. Rounding 3.5 down to three full
32
+ * stars loses the difference between a middling operator and a good one,
33
+ * which is precisely the range where a reader is still deciding.
34
+ */
35
+ const Rating = ({ rating, value, size, t }: Props): (JSX.Element | null) => {
36
+ const average = value ?? rating?.average;
37
+
38
+ if (average === undefined || (value === undefined && !rating?.count)) {
39
+ return null;
40
+ }
41
+
42
+ const rounded = Math.round(average * 2) / 2;
43
+
44
+ const title = value !== undefined
45
+ ? t('rating.value', { average })
46
+ : t('rating.summary', { average, count: rating?.count });
47
+
48
+ return (
49
+ <Container $size={ size ?? 'normal' } title={ title }>
50
+ <Stars aria-hidden="true">
51
+ { [ 1, 2, 3, 4, 5 ].map(value => {
52
+ if (rounded >= value) {
53
+ return <AiFillStar key={ value } />;
54
+ }
55
+
56
+ return rounded >= value - 0.5
57
+ ? <BsStarHalf key={ value } />
58
+ : <AiOutlineStar key={ value } />;
59
+ }) }
60
+ </Stars>
61
+
62
+ { rating && (
63
+ <>
64
+ <Average>{ rating.average.toFixed(1) }</Average>
65
+
66
+ <Count>({ rating.count })</Count>
67
+ </>
68
+ ) }
69
+ </Container>
70
+ );
71
+ };
72
+
73
+ export default Rating;
@@ -0,0 +1,31 @@
1
+ import styled, { css } from 'styled-components';
2
+
3
+ export const Container = styled.span<{ $size: 'small' | 'normal' }>`
4
+ display: inline-flex;
5
+ align-items: center;
6
+ gap: 5px;
7
+ white-space: nowrap;
8
+ line-height: 1;
9
+
10
+ ${ props => props.$size === 'small' && css`
11
+ font-size: ${ props => props.theme.size.s };
12
+ ` }
13
+ `;
14
+
15
+ export const Stars = styled.span`
16
+ display: inline-flex;
17
+ gap: 1px;
18
+ color: ${ props => props.theme.primary.normal };
19
+
20
+ svg {
21
+ display: block;
22
+ }
23
+ `;
24
+
25
+ export const Average = styled.strong`
26
+ font-weight: 700;
27
+ `;
28
+
29
+ export const Count = styled.span`
30
+ color: ${ props => props.theme.font.faded };
31
+ `;
@@ -4,7 +4,7 @@ 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, RiEyeLine, RiLoginCircleLine } from 'react-icons/ri';
7
+ import { RiBusLine, RiEyeLine, RiLoginCircleLine, RiReplyLine } from 'react-icons/ri';
8
8
 
9
9
  interface Props {
10
10
  type: string
@@ -26,6 +26,14 @@ const Icon = ({ type, t }: Props): (JSX.Element | null) => {
26
26
  case 'approve':
27
27
  return <AiOutlineCheck title={ t('table.actions.custom.approve', { ns: 'common' }) } />;
28
28
 
29
+ // Edited: Ferjolt Ozuni - Date: 2026-08-03
30
+ // Its own action rather than borrowing 'approve'. On the operator's
31
+ // review page a tick labelled "Approve" reads as though they are
32
+ // approving the review about them, which is the one thing they cannot
33
+ // do - the verb has to be the one they are actually performing.
34
+ case 'reply':
35
+ return <RiReplyLine title={ t('table.actions.custom.reply', { ns: 'common' }) } />;
36
+
29
37
  case 'deny':
30
38
  return <FaTrash title={ t('table.actions.custom.deny', { ns: 'common' }) } />;
31
39
 
package/index.ts CHANGED
@@ -25,6 +25,7 @@ import Pagination from './Pagination/Pagination';
25
25
  import Paragraph from './Loading/Paragraph';
26
26
  import Passengers from './Passengers/Passengers';
27
27
  import Password from './Password/Password';
28
+ import Rating from './Rating/Rating';
28
29
  import Policy from './Policy/Policy';
29
30
  import Report from './Report/Report';
30
31
  import RouteFeature from './RouteFeature/RouteFeature';
@@ -73,6 +74,7 @@ export {
73
74
  Paragraph,
74
75
  Passengers,
75
76
  Password,
77
+ Rating,
76
78
  Policy,
77
79
  Report,
78
80
  RouteFeature,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"