Skip to content

Commit

Permalink
FeaturePanel: add Food hygiene rating scheme (#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dlurak authored Aug 29, 2023
1 parent d638964 commit 3c83713
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/components/FeaturePanel/FeaturePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const featuredKeys = [
'contact:mobile',
'opening_hours',
'description',
'fhrs:id',
];

const FeaturePanel = () => {
Expand Down
6 changes: 5 additions & 1 deletion src/components/FeaturePanel/FeaturedTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import WebsiteRenderer from './renderers/WebsiteRenderer';
import OpeningHoursRenderer from './renderers/OpeningHoursRenderer';
import PhoneRenderer from './renderers/PhoneRenderer';
import { EditIconButton } from './helpers/EditIconButton';
import { FoodHygieneRatingSchemeRenderer } from './renderers/FoodHygieneRatingScheme';

const Wrapper = styled.div`
position: relative;
Expand All @@ -29,13 +30,16 @@ const Value = styled.div`
`;

const DefaultRenderer = ({ v }) => v;
const renderers = {
const renderers: {
[key: string]: React.FC<{ v: string }>;
} = {
website: WebsiteRenderer,
'contact:website': WebsiteRenderer,
phone: PhoneRenderer,
'contact:phone': PhoneRenderer,
'contact:mobile': PhoneRenderer,
opening_hours: OpeningHoursRenderer,
'fhrs:id': FoodHygieneRatingSchemeRenderer,
};

export const FeaturedTag = ({ k, v, onEdit }) => {
Expand Down
101 changes: 101 additions & 0 deletions src/components/FeaturePanel/renderers/FoodHygieneRatingScheme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { useEffect, useState } from 'react';
import { Typography } from '@material-ui/core';
import { Restaurant } from '@material-ui/icons';
import { getEstablishmentRatingValue } from '../../../services/fhrsApi';

interface StarRatingProps {
stars: number;
maxStars: number;
}

const StarRating = ({ stars, maxStars }: StarRatingProps) => {
const starArray = new Array(maxStars).fill(0);
return (
<div>
{starArray.map((_, i) => (
<span>{i < stars ? '★' : '☆'}</span>
))}
</div>
);
};

const useLoadingState = () => {
const [rating, setRating] = useState<number>();
const [error, setError] = useState<string>();
const [loading, setLoading] = useState(true);

const finishRating = (payload) => {
setLoading(false);
setRating(payload);
};

const startRating = () => {
setLoading(true);
setRating(undefined);
setError(undefined);
};

const failRating = () => {
setError('Could not load rating');
setLoading(false);
};

return { rating, error, loading, startRating, finishRating, failRating };
};

export const FoodHygieneRatingSchemeRenderer = ({ v }) => {
const { rating, error, loading, startRating, finishRating, failRating } =
useLoadingState();

useEffect(() => {
const loadData = async () => {
startRating();
const ratingValue = await getEstablishmentRatingValue(v);
if (Number.isNaN(rating)) {
failRating();
}
finishRating(ratingValue);
};

loadData();
}, []);

const linkStyle: React.CSSProperties = {
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
};
const pStyle: React.CSSProperties = {
marginBlock: '0',
};

return (
<>
{loading ? (
<>
<span className="dotloader" />
<span className="dotloader" />
<span className="dotloader" />
</>
) : (
<>
<Restaurant fontSize="small" />
{Number.isNaN(rating) || error ? (
<Typography color="error">No rating available</Typography>
) : (
<>
<a
href={`https://ratings.food.gov.uk/business/${v}`}
title="Food Hygiene Rating Scheme"
style={linkStyle}
>
<p style={pStyle}>FHRS</p>
<StarRating stars={rating} maxStars={5} />
</a>
</>
)}
</>
)}
</>
);
};
47 changes: 47 additions & 0 deletions src/services/__tests__/fhrsApi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as fetchModule from '../fetch';
import { getEstablishmentRatingValue } from '../fhrsApi';

describe('fetchRating', () => {
it('should return a rating between 0 and 5', async () => {
const fetchJsonMock = jest
.spyOn(fetchModule, 'fetchJson')
.mockResolvedValue({
FHRSID: 423824,
ChangesByServerID: 0,
LocalAuthorityBusinessID: '13359',
BusinessName: 'Caffe Nero',
BusinessType: 'Restaurant/Cafe/Canteen',
BusinessTypeID: 1,
AddressLine1: '',
AddressLine2: '30 Monmouth Street',
AddressLine3: '',
AddressLine4: 'London',
PostCode: 'WC2H 9HA',
Phone: '',
RatingValue: '5',
RatingKey: 'fhrs_5_en-gb',
RatingDate: '2019-09-11T00:00:00',
LocalAuthorityCode: '506',
LocalAuthorityName: 'Camden',
LocalAuthorityWebSite: 'http://www.camden.gov.uk',
LocalAuthorityEmailAddress: '[email protected]',
scores: {
Hygiene: 0,
Structural: 0,
ConfidenceInManagement: 0,
},
SchemeType: 'FHRS',
geocode: {
longitude: '-0.1272279',
latitude: '51.5136148',
},
RightToReply: '',
Distance: null,
NewRatingPending: false,
});

const rating = await getEstablishmentRatingValue(269382);
expect(rating).toBeLessThanOrEqual(5);
fetchJsonMock.mockRestore();
});
});
16 changes: 16 additions & 0 deletions src/services/fhrsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { fetchJson } from './fetch';

function fetchEstablishmentData(id: number) {
return fetchJson(`https://api.ratings.food.gov.uk/Establishments/${id}`, {
headers: {
'x-api-version': '2',
},
});
}

export async function getEstablishmentRatingValue(id: number) {
const allData = await fetchEstablishmentData(id).catch(() => null);
const ratingString = allData?.RatingValue;
const ratingValue = parseInt(ratingString, 10);
return ratingValue;
}

0 comments on commit 3c83713

Please sign in to comment.