@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 +16 -0
- package/Rating/Rating.tsx +73 -0
- package/Rating/styles.ts +31 -0
- package/Table/Actions/Icon.tsx +9 -1
- package/index.ts +2 -0
- package/package.json +1 -1
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;
|
package/Rating/styles.ts
ADDED
|
@@ -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
|
+
`;
|
package/Table/Actions/Icon.tsx
CHANGED
|
@@ -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,
|