@autobusal/common 1.18.0 → 1.20.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,24 @@ 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.20.0
10
+
11
+ `Rating` — stars, average and count, or nothing at all. Rendering nothing is
12
+ the feature: obtapi withholds a score until there are enough published reviews
13
+ to say something honest, so an absent rating has to read as "not saying yet"
14
+ rather than as a zero. Anything filling the gap with empty stars would turn a
15
+ new operator into a badly-rated one. Half stars are drawn, because rounding 3.5
16
+ down loses the difference between middling and good — precisely the range where
17
+ a reader is still deciding.
18
+
19
+ ## 1.19.0
20
+
21
+ `extra` joins `create` and `search` as a top-of-table slot rather than a
22
+ per-row action. Any table opting into the extra slot also rendered one empty,
23
+ iconless, still-clickable button in every row's options cell — Icon has no
24
+ case for `extra`, so it drew nothing while taking the click. The refund queue
25
+ has been doing this all along; the new review queue would have too.
26
+
9
27
  ## [1.18.0] - 2026-08-02
10
28
 
11
29
  ### Added
@@ -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
+ `;
@@ -14,7 +14,14 @@ interface Props {
14
14
  }
15
15
 
16
16
  const Get = ({ id, url, urlWith, type, t, handlers, navigate }: Props): (JSX.Element | null) => {
17
- if (['create', 'search'].includes(type)) {
17
+ // Edited: Ferjolt Ozuni - Date: 2026-08-02
18
+ // 'extra' belongs with 'create' and 'search': all three are TOP-of-table
19
+ // slots, not per-row actions. Without it here, any table that opts into
20
+ // the extra slot (the refund queue does, the review queue now does too)
21
+ // also renders one empty, iconless, clickable button in every row's
22
+ // options cell - Icon has no case for 'extra', so it draws nothing at all
23
+ // while still taking the click.
24
+ if (['create', 'search', 'extra'].includes(type)) {
18
25
  return null;
19
26
  }
20
27
 
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.18.0",
3
+ "version": "1.20.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"