Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend refactor #211

Merged
merged 8 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
secrets.txt
*.env
.idea/
6 changes: 6 additions & 0 deletions src/nursery-nav/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
REACT_APP_GEOAPIFY_API_KEY=
REACT_APP_API_URL=
REACT_APP_NAME=
REACT_APP_CONTACT_MAIL=
REACT_APP_GOOGLE_ANALYTICS_TRACKING_ID=
REACT_APP_DATA_SOURCE_UPDATE_DATE=
15 changes: 6 additions & 9 deletions src/nursery-nav/src/api/CitiesFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import axios from "axios";
import { fetchFromApi } from './fetcher';

export interface getCitiesResponse {
city: string;
voivodeship: string;
}

const API_URL = process.env.REACT_APP_API_URL;

export const getCities = async (): Promise<getCitiesResponse[]> => {
const url = `${process.env.REACT_APP_API_URL}/cities`;
const res = await axios.get(url);
if (res.status !== 200) {
throw new Error('Failed to fetch cities');
}
const url = `${API_URL}/cities`;

const data = res.data as getCitiesResponse[];
return data;
return fetchFromApi<getCitiesResponse[]>(url);
}

export { }
export { }
64 changes: 22 additions & 42 deletions src/nursery-nav/src/api/InstitutionsFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from "axios";
import { Institution, InstitutionListItem } from "../shared/nursery.interface";
import { fetchFromApi } from './fetcher';

export interface getInstitutionsResponse {
items: InstitutionListItem[],
Expand All @@ -13,55 +13,35 @@ export interface getInstitutionsAutocompleteResponse {
name: string
}

export const getInstitutions = async (searchParams: URLSearchParams, pageNum?: number | null): Promise<getInstitutionsResponse> => {
let url = `${process.env.REACT_APP_API_URL}/institutions`;
if (pageNum) {
url += `?page=${pageNum}&${searchParams}`;
}
else {
url += `?${searchParams}`;
}
const API_URL = process.env.REACT_APP_API_URL;

const res = await axios.get(url);
if (res.status !== 200) {
throw new Error('Failed to fetch institutions');
}
export const getInstitutions = async (
searchParams: URLSearchParams,
pageNum?: number | null
): Promise<getInstitutionsResponse> => {
const url = `${API_URL}/institutions`;
const params = { page: pageNum || undefined, ...Object.fromEntries(searchParams) };

const data = res.data as getInstitutionsResponse;
return data;
}
return fetchFromApi<getInstitutionsResponse>(url, params);
};

export const getInstitutionDetails = async (id: number): Promise<Institution> => {
const url = `${process.env.REACT_APP_API_URL}/institutions/details/${id}`;
const res = await axios.get(url);
if (res.status !== 200) {
throw new Error('Failed to fetch institution details');
}

const data = res.data as Institution;
return data;
}
const url = `${API_URL}/institutions/details/${id}`;
return fetchFromApi<Institution>(url);
};

export const getInstitutionsDetails = async (ids: number[]): Promise<Institution[]> => {
const url = `${process.env.REACT_APP_API_URL}/institutions/details?id=${ids.join('&id=')}`;
const res = await axios.get(url);
if (res.status !== 200) {
throw new Error('Failed to fetch institution details');
}
const url = `${API_URL}/institutions/details`;
const params = { id: ids };

const data = res.data as Institution[];
return data;
}
return fetchFromApi<Institution[]>(url, params);
};

export const getInstitutionAutocomplete = async (search: string): Promise<getInstitutionsAutocompleteResponse[]> => {
const url = `${process.env.REACT_APP_API_URL}/institutions/autocomplete?search=${search}`;
const res = await axios.get(url);
if (res.status !== 200) {
throw new Error('Failed to fetch institution autocomplete');
}
const url = `${API_URL}/institutions/autocomplete`;
const params = { search };

const data = res.data as getInstitutionsAutocompleteResponse[];
return data;
}
return fetchFromApi<getInstitutionsAutocompleteResponse[]>(url, params);
};

export { }
export { }
15 changes: 6 additions & 9 deletions src/nursery-nav/src/api/LocationsFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import axios from "axios";
import { fetchFromApi } from './fetcher';
import { LocationResponse } from "../shared/nursery.interface";

const API_URL = process.env.REACT_APP_API_URL;

export const getLocations = async (): Promise<LocationResponse[]> => {
const url = `${process.env.REACT_APP_API_URL}/locations`;
const res = await axios.get(url);
if (res.status !== 200) {
throw new Error('Failed to fetch locations');
}

const data = res.data as LocationResponse[];
return data;
const url = `${API_URL}/locations`;

return fetchFromApi<LocationResponse[]>(url);
}

export { }
11 changes: 11 additions & 0 deletions src/nursery-nav/src/api/fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import axios from "axios";

export const fetchFromApi = async <T>(url: string, params?: object): Promise<T> => {
const { data, status } = await axios.get<T>(url, { params });

if (status !== 200) {
throw new Error(`Failed to fetch data from ${url}. Status: ${status}`);
}

return data;
};
83 changes: 43 additions & 40 deletions src/nursery-nav/src/components/Filters/Filters.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,71 @@
import { useState } from "react";
import { generatePath, useNavigate, useSearchParams } from "react-router-dom";

import { FormControlLabel, Autocomplete, TextField, debounce, RadioGroup, Stack } from "@mui/material";
import Radio from '@mui/material/Radio';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import { useState } from "react";
import { InstitutionAutocomplete, InstitutionType } from "../../shared/nursery.interface";
import { generatePath, useNavigate, useSearchParams } from "react-router-dom";
import PathConstants from "../../shared/pathConstants";

import { getInstitutionAutocomplete } from "../../api/InstitutionsFetcher";
import { getCitiesResponse } from "../../api/CitiesFetcher";

import PathConstants from "../../shared/pathConstants";
import { InstitutionAutocomplete, InstitutionType } from "../../shared/nursery.interface";

interface FiltersProps {
defaultVoivodeship?: string;
defaultCity?: string;
isMobile?: boolean;
citiesResponse?: getCitiesResponse[];
}

interface City {
city: string;
voivodeship: string;
}

const voivodeships = [
'DOLNOŚLĄSKIE',
'KUJAWSKO-POMORSKIE',
'LUBELSKIE',
'LUBUSKIE',
'ŁÓDZKIE',
'MAŁOPOLSKIE',
'MAZOWIECKIE',
'OPOLSKIE',
'PODKARPACKIE',
'PODLASKIE',
'POMORSKIE',
'ŚLĄSKIE',
'ŚWIĘTOKRZYSKIE',
'WARMIŃSKO-MAZURSKIE',
'WIELKOPOLSKIE',
'ZACHODNIOPOMORSKIE',
] as const;

export default function Filters({ defaultVoivodeship, defaultCity, isMobile, citiesResponse }: FiltersProps) {
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();

const [institutionsAutocomplete, setInstitutionsAutocomplete] = useState<InstitutionAutocomplete[]>([]);
const voivodeships = [
'DOLNOŚLĄSKIE',
'KUJAWSKO-POMORSKIE',
'LUBELSKIE',
'LUBUSKIE',
'ŁÓDZKIE',
'MAŁOPOLSKIE',
'MAZOWIECKIE',
'OPOLSKIE',
'PODKARPACKIE',
'PODLASKIE',
'POMORSKIE',
'ŚLĄSKIE',
'ŚWIĘTOKRZYSKIE',
'WARMIŃSKO-MAZURSKIE',
'WIELKOPOLSKIE',
'ZACHODNIOPOMORSKIE',
];


const citiesUnique = (citiesResponse || [])
.map(city => ({ city: city?.city, voivodeship: city?.voivodeship }))
.filter((city, index, self) => self.findIndex(c => c.city === city.city) === index);
const cities = citiesUnique.filter(city => city !== undefined && city !== null);

const cities = (citiesResponse || [])
.reduce<City[]>((uniqueCities, { city, voivodeship }) => {
// Check if city exists and if it's already in the uniqueCities array
if (city && !uniqueCities.some(c => c.city === city)) {
// If not, add the city to the uniqueCities array
uniqueCities.push({ city, voivodeship });
}
return uniqueCities;
}, []);

const getAutocompleteData = async (value: string) => {
const institutions = await getInstitutionAutocomplete(value);
setInstitutionsAutocomplete(institutions.map(institution => ({ name: institution.name, id: institution.id })));
}

const onInputChange = (_event: any, value: string | null) => {
if (value) {
getAutocompleteData(value);
}
else {
setInstitutionsAutocomplete([]);
}
value ? getAutocompleteData(value) : setInstitutionsAutocomplete([]);
}

const goToDetails = (_event: any, value: InstitutionAutocomplete | null) => {
Expand All @@ -67,12 +75,7 @@ export default function Filters({ defaultVoivodeship, defaultCity, isMobile, cit
}

const handleInstitutionTypeFilter = (value: string) => {
if (value === 'ALL') {
searchParams.delete('insType');
}
else {
searchParams.set('insType', value);
}
value === 'ALL' ? searchParams.delete('insType') : searchParams.set('insType', value);
setSearchParams(searchParams);
}

Expand Down
25 changes: 25 additions & 0 deletions src/nursery-nav/src/components/Metadata/Metadata.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Helmet } from "react-helmet-async";

interface MetadataProps {
title: string;
description?: string;
image: string;
url?: string;
}

export default function Metadata({ title, description, image, url }: MetadataProps) {
return (
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:url" content={url} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
<meta name="twitter:card" content="summary_large_image" />
</Helmet>
)
};
18 changes: 4 additions & 14 deletions src/nursery-nav/src/pages/AboutPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Box, Container, Link, List, ListItem, ListItemText, Paper, Stack, Typography } from "@mui/material";
import { Helmet } from "react-helmet-async";

import Metadata from "../components/Metadata/Metadata";

export default function AboutPage() {
const title = `O aplikacji - ${process.env.REACT_APP_NAME}`;
Expand All @@ -8,18 +9,7 @@ export default function AboutPage() {

return (
<>
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:url" content={window.location.href} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
<meta name="twitter:card" content="summary_large_image" />
</Helmet>
<Metadata title={title} description={description} image={image} url={window.location.href} />
<Container sx={{ padding: '1rem' }}>
<Paper elevation={3} sx={{ padding: '1rem' }}>
<Stack direction="column" spacing={2}>
Expand Down Expand Up @@ -69,4 +59,4 @@ export default function AboutPage() {
</Container>
</>
);
}
}
20 changes: 5 additions & 15 deletions src/nursery-nav/src/pages/ComparisonPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useSearchParams } from "react-router-dom";
import { Box, Container, Grid, Stack, Typography } from "@mui/material";
import { Helmet } from "react-helmet-async";

import Comparison from "../components/Comparison/Comparison";
import { useSearchParams } from "react-router-dom";
import Metadata from "../components/Metadata/Metadata";


export default function ComparisonPage() {
Expand All @@ -18,18 +19,7 @@ export default function ComparisonPage() {

return (
<>
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:url" content={title} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
<meta name="twitter:card" content="summary_large_image" />
</Helmet>
<Metadata title={title} description={description} image={image} url={title} />
<Grid item xs={12}>
{displayError &&
<Container fixed>
Expand All @@ -50,4 +40,4 @@ export default function ComparisonPage() {
</Grid>
</>
);
}
}
Loading
Loading