From 0cdc5623202bc873d91150ca5acedebe601d2154 Mon Sep 17 00:00:00 2001 From: Magyari Zoltan Date: Mon, 19 Aug 2024 16:16:04 +0300 Subject: [PATCH] Rework search page: show all service offerings [#88] - Re enable ServiceOffering component - Simplify loading of the data in useEffect - Use semantically correct components - Introduce new generic components to structure better the component (Main, LoadingIndicator, CardContainer) - Refactor css styling in order to match the new data structure - Reorganize translations Signed-off-by: Zoltan M. Signed-off-by: Zoltan Magyari --- public/locales/de/translation.json | 15 +- public/locales/en/translation.json | 15 +- .../cardContainer/CardContainer.tsx | 16 ++ src/components/cards/SelfDescriptionCard.tsx | 18 +- src/components/header/Header.tsx | 12 +- src/components/itemCard/ItemCard.tsx | 4 +- .../loading_view/LoadingIndicator.css | 10 + .../loading_view/LoadingIndicator.tsx | 25 ++ src/components/main/Main.css | 4 + src/components/main/Main.tsx | 26 +++ .../serviceOfferings/ServiceOfferings.css | 28 --- .../serviceOfferings/ServiceOfferings.tsx | 216 ++++-------------- src/hooks/useNavbar.tsx | 8 +- src/services/ApiService.tsx | 3 +- src/utils/dataMapper.ts | 2 +- 15 files changed, 166 insertions(+), 236 deletions(-) create mode 100644 src/components/cardContainer/CardContainer.tsx create mode 100644 src/components/loading_view/LoadingIndicator.css create mode 100644 src/components/loading_view/LoadingIndicator.tsx create mode 100644 src/components/main/Main.css create mode 100644 src/components/main/Main.tsx delete mode 100644 src/components/serviceOfferings/ServiceOfferings.css diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index 5a76f1f8..2ceb0f6d 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -4,7 +4,6 @@ }, "left-menu": { "home": "Startseite", - "service-offerings": "Service Offerings", "resources": "Ressourcen", "participants": "Teilnehmer", "provide": "Bereitstellen", @@ -507,10 +506,11 @@ "download-file": "Turtle-Datei herunterladen", "download-json": "Json-Datei herunterladen" }, + "service-offerings": { + "title": "Dienstleistungen", + "titles": "Dienstleistungsangebote" + }, "resources": { - "is-gaia-x-compliant": "Gaia-X konform", - "not-gaia-x-compliant": "Nicht Gaia-X konform", - "not-authenticated": "Nicht berechtigt", "search-bar-text": "Suchen Sie nach speziellen Ressourcen oder Angeboten" }, "ontologies": { @@ -529,5 +529,12 @@ "no-shapes-available": "Keine Shapes gefunden", "related-shapes": "Verwandte Shapes", "not-found": "Shape nicht gefunden" + }, + "common": { + "is-gaia-x-compliant": "Gaia-X konform", + "not-gaia-x-compliant": "Nicht Gaia-X konform", + "not-authenticated": "Nicht berechtigt", + "results": "Ergebnisse", + "is-loading": "es wird geladen..." } } diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 5d1bfd66..c1ade353 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -5,7 +5,6 @@ "left-menu": { "home": "Home", "resources": "Resources", - "service-offerings": "Service Offerings", "services": "Services", "data": "Data", "provider": "Provider", @@ -509,10 +508,11 @@ "download-file": "Download turtle file", "download-json": "Download json file" }, + "service-offerings": { + "title": "Services", + "titles": "Service Offerings" + }, "resources": { - "is-gaia-x-compliant": "Gaia-X compliant", - "not-gaia-x-compliant": "Not Gaia-X compliant", - "not-authenticated": "Not authenticated", "search-bar-text": "Search for special Resources or Service Offerings" }, "ontologies": { @@ -531,5 +531,12 @@ "no-shapes-available": "No shapes available", "related-shapes": "Related Shapes", "not-found": "Shape not found" + }, + "common": { + "is-gaia-x-compliant": "Gaia-X compliant", + "not-gaia-x-compliant": "Not Gaia-X compliant", + "not-authenticated": "You are not authenticated!", + "results": "results", + "is-loading": "loading..." } } diff --git a/src/components/cardContainer/CardContainer.tsx b/src/components/cardContainer/CardContainer.tsx new file mode 100644 index 00000000..f84b16db --- /dev/null +++ b/src/components/cardContainer/CardContainer.tsx @@ -0,0 +1,16 @@ +import { FC, ReactNode } from 'react'; + +interface IResultContainer { + children: ReactNode; + isLoaded: boolean; +} + +const CardContainer: FC = ({ children, isLoaded }) => { + return ( +
+ {isLoaded && children} +
+ ) +} + +export default CardContainer; diff --git a/src/components/cards/SelfDescriptionCard.tsx b/src/components/cards/SelfDescriptionCard.tsx index 67986b39..545b11ce 100644 --- a/src/components/cards/SelfDescriptionCard.tsx +++ b/src/components/cards/SelfDescriptionCard.tsx @@ -2,7 +2,7 @@ import Title from 'components/Title/Title'; import GaiaXButton from 'components/buttons/GaiaXButton'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { ServiceOffering, Resource } from 'utils/dataMapper'; +import { Resource, ServiceOffering } from 'utils/dataMapper'; import { Ontology } from '../../utils/ontologyMapper'; @@ -46,18 +46,18 @@ export default function SelfDescriptionCard({ }; return ( -
-
+
+
{label} {isGaiaXComlpiant === undefined ? null : ( isGaiaXComlpiant ? ( -

{t('resources.is-gaia-x-compliant')}

+

{t('common.is-gaia-x-compliant')}

) : ( -

{t('resources.not-gaia-x-compliant')}

+

{t('common.not-gaia-x-compliant')}

) )} -
-
+ +
{name}

{description}

@@ -66,7 +66,7 @@ export default function SelfDescriptionCard({ handleOnClick={handleNavigationToDetailsPage} />
-
-
+ + ); } diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx index 5c6bd705..fb2d8115 100644 --- a/src/components/header/Header.tsx +++ b/src/components/header/Header.tsx @@ -10,13 +10,11 @@ interface IHeader { const Header: FC = ({ title }) => { return ( -
-
-
- {title} -
-
-
+
+
+ {title} +
+
); }; diff --git a/src/components/itemCard/ItemCard.tsx b/src/components/itemCard/ItemCard.tsx index 44f918a8..b387115e 100644 --- a/src/components/itemCard/ItemCard.tsx +++ b/src/components/itemCard/ItemCard.tsx @@ -25,9 +25,9 @@ const ItemCard: FC = ({ label, isGaiaXCompliant, ontology, shape }) = {label} {isGaiaXCompliant === undefined ? null : ( isGaiaXCompliant ? ( -

{t('resources.is-gaia-x-compliant')}

+

{t('common.is-gaia-x-compliant')}

) : ( -

{t('resources.not-gaia-x-compliant')}

+

{t('common.not-gaia-x-compliant')}

) )} diff --git a/src/components/loading_view/LoadingIndicator.css b/src/components/loading_view/LoadingIndicator.css new file mode 100644 index 00000000..7326523b --- /dev/null +++ b/src/components/loading_view/LoadingIndicator.css @@ -0,0 +1,10 @@ +.newCarLoader { + display: flex; + justify-content: center; +} + +.car { + width: 100px; + height: 100px; +} + diff --git a/src/components/loading_view/LoadingIndicator.tsx b/src/components/loading_view/LoadingIndicator.tsx new file mode 100644 index 00000000..6b415fd2 --- /dev/null +++ b/src/components/loading_view/LoadingIndicator.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import car from '../../assets/car.gif'; + +import styles from './LoadingIndicator.css'; + +interface ILoadingIndicator { + isLoading: boolean +} + +const LoadingIndicator: FC = ({ isLoading }) => { + const { t } = useTranslation() + + if (isLoading) { + return ( +
+ {t('common.is-loading')} +
+ ) + } + return <> +} + +export default LoadingIndicator diff --git a/src/components/main/Main.css b/src/components/main/Main.css new file mode 100644 index 00000000..f99b005d --- /dev/null +++ b/src/components/main/Main.css @@ -0,0 +1,4 @@ +.content { + margin-top: 20px; +} + diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx new file mode 100644 index 00000000..1a52e944 --- /dev/null +++ b/src/components/main/Main.tsx @@ -0,0 +1,26 @@ +import { FC, ReactNode, useContext } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { AuthContext } from '../../context/AuthContextProvider'; + +import styles from './Main.css' + +interface IMain { + children: ReactNode +} + +const Main: FC = ({ children }) => { + const authContext = useContext(AuthContext); + const { t } = useTranslation(); + + if (authContext.isAuthenticated) { + return ( +
+ {children} +
+ ) + } + return

{t('common.not-authenticated')}

+} + +export default Main; diff --git a/src/components/serviceOfferings/ServiceOfferings.css b/src/components/serviceOfferings/ServiceOfferings.css deleted file mode 100644 index e508a65a..00000000 --- a/src/components/serviceOfferings/ServiceOfferings.css +++ /dev/null @@ -1,28 +0,0 @@ -header { - background: linear-gradient(110deg, rgb(185, 0, 255) 15%, rgb(0, 0, 148) 75%, rgb(70, 218, 255)); - height:100px; -} - -.content { - margin-top:20px; -} - -.carLoader { - margin: 0; -} - -.newCarLoader { - display: flex; - justify-content: center; -} - -.car { - width: 100px; - height: 100px; -} - -.button { - display: inline-block; - margin-top:5px; - margin-left:10px; -} diff --git a/src/components/serviceOfferings/ServiceOfferings.tsx b/src/components/serviceOfferings/ServiceOfferings.tsx index 25056d3e..729a0ea4 100644 --- a/src/components/serviceOfferings/ServiceOfferings.tsx +++ b/src/components/serviceOfferings/ServiceOfferings.tsx @@ -1,193 +1,57 @@ -import { - Button, - FormControl, - InputLabel, - MenuItem, - Select, - SelectChangeEvent, - TextField, -} from '@mui/material'; import SelfDescriptionCard from 'components/cards/SelfDescriptionCard'; import React, { useContext, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; -import car from '../../assets/car.gif'; import { AuthContext } from '../../context/AuthContextProvider'; import { ApiService } from '../../services/ApiService'; -import { Shape } from '../../types/shapes.model'; -import { RDFParser } from '../../utils/RDFParser'; -import { ServiceOffering, mapServiceOfferings } from '../../utils/dataMapper'; -import { getShapeProperties } from '../../utils/shapeHelpers'; -import { Padding } from '../discovery/tabs/style'; -// import SendIcon from '@mui/icons-material/Send'; -// @ts-ignore - -import './ServiceOfferings.css'; +import { mapServiceOfferings, ServiceOffering } from '../../utils/dataMapper'; +import CardContainer from '../cardContainer/CardContainer'; +import Header from '../header/Header'; +import LoadingIndicator from '../loading_view/LoadingIndicator'; +import Main from '../main/Main'; const ServiceOfferings = () => { - const [selfDescriptionData, setSelfDescriptionData] = useState< + const [serviceOfferings, setServiceOfferings] = useState< ServiceOffering[] >([]); - const [isLoading, setIsLoading] = useState(false); - const authContext = useContext(AuthContext); - const isAuthenticated = authContext.isAuthenticated; - - const initShape: Shape = { - shaclShapeId: '', - shortSubject: '', - properties: [], - }; - - const [selectedShape, setSelectedShape] = useState(initShape); - const [shapes, setShapes] = useState([]); - const [isShapeSelected, setIsShapeSelected] = useState(false); - const [isPropertySelected, setIsPropertySelected] = useState(false); - const [selectedProperty, setSelectedProperty] = useState(''); - const [properties, setProperties] = useState([]); + const { t } = useTranslation() - useEffect(() => { - setProperties(getShapeProperties(selectedShape)); - }, [selectedShape]); - - useEffect(() => { - setIsLoading(true); - const getShaclShapes = async () => { - const data = await ApiService.getShaclShapesFromCatalogue(authContext); - setShapes(RDFParser.parseShapesFromRdfResponse(data)); - }; - getShaclShapes(); - setIsLoading(false); - }, [isAuthenticated]); + const [isLoading, setIsLoading] = useState(true); + const authContext = useContext(AuthContext); useEffect(() => { - const fetchAndSetSelfDescriptions = async () => { - setIsLoading(true); - try { - const response = await ApiService.getAllSelfDescriptions(authContext); - console.log('My fetched data: ', response); - const map = mapServiceOfferings(response); - setSelfDescriptionData(map); - } catch (error) { - console.error('Error fetching self descriptions:', error); - } finally { - setIsLoading(false); - } - }; - - fetchAndSetSelfDescriptions(); - }, []); - - const handleShapeChange = (event: SelectChangeEvent) => { - let uiSelectedShape = event.target.value; - shapes.forEach((shape) => { - if (shape.shortSubject === uiSelectedShape) { - setSelectedShape(shape); - } - }); - setIsShapeSelected(true); - }; - - const handlePropertyChange = (event: SelectChangeEvent) => { - setSelectedProperty(event.target.value); - setIsPropertySelected(true); - }; - - async function handleSearch() { - setIsLoading(true); - const targetClass = selectedShape.shortSubject.replace('Shape', ''); - const selfDescriptions = await ApiService.getSelfDescriptionsForShape( - authContext, - targetClass - ); - const map = mapServiceOfferings(selfDescriptions.data); - console.log('Map:', map); - setSelfDescriptionData(map); - setIsLoading(false); - } + if (authContext.isAuthenticated) { + ApiService.getAllSelfDescriptions(authContext) + .then((selfDescriptions) => { + setServiceOfferings(mapServiceOfferings(selfDescriptions)); + }) + .catch(error => console.error('Error fetching self descriptions:', error)) + .finally(() => setIsLoading(false)) + } + }, [authContext.isAuthenticated]); return ( -
-
-

Service Offerings

-
- {authContext.isAuthenticated && ( -
- - Select Shape - - - {isShapeSelected && ( - - Property - - - )} - {isPropertySelected && ( - - - - )} -
- {isShapeSelected && ( - - )} -
- - -
- {!isLoading && - selfDescriptionData.length > 0 && - selfDescriptionData.map((selfDescription) => { - return ( - - ); - })} - {isLoading && ( -
- loading... -
- )} -
-
- )} - {!authContext.isAuthenticated &&

You are not authenticated!

} -
+ <> +
+
+ + + { + serviceOfferings.map( + (serviceOffering) => ( + + )) + } + +
+ ); }; export default ServiceOfferings; diff --git a/src/hooks/useNavbar.tsx b/src/hooks/useNavbar.tsx index 84e1eeac..ecaf7673 100644 --- a/src/hooks/useNavbar.tsx +++ b/src/hooks/useNavbar.tsx @@ -5,10 +5,10 @@ export const useNavbar = () => { return [ // These were commented out for the Hannover Fair - they will be added back later - // { - // path: "/service-offerings", - // navigationItemName: t("left-menu.tooltip.service-offerings"), - // }, + { + path: '/service-offerings', + navigationItemName: t('service-offerings.title'), + }, // { // path: "/participants", // navigationItemName: t("left-menu.tooltip.participants"), diff --git a/src/services/ApiService.tsx b/src/services/ApiService.tsx index 3acebbc8..7b9cf63e 100644 --- a/src/services/ApiService.tsx +++ b/src/services/ApiService.tsx @@ -1,6 +1,7 @@ import axios, { AxiosResponse } from 'axios'; import { AuthContextType } from '../context/AuthContextProvider'; +import { ServiceOfferingInput } from '../utils/dataMapper'; import { isEmpty } from '../utils/helpers'; const getHeaders = (authContext: AuthContextType) => { @@ -62,7 +63,7 @@ export const ApiService = { // Returns every Service Offering available async getAllSelfDescriptions( authContext: AuthContextType - ): Promise> { + ): Promise { const endpoint = queryEndpoint; const headers = getHeaders(authContext); const requestBody = { diff --git a/src/utils/dataMapper.ts b/src/utils/dataMapper.ts index 461667b1..24aa2764 100644 --- a/src/utils/dataMapper.ts +++ b/src/utils/dataMapper.ts @@ -27,7 +27,7 @@ export interface ServiceOffering { } export function mapServiceOfferings(selfDescriptions: ServiceOfferingInput): ServiceOffering[] { - console.log('From mapper: ', selfDescriptions); + console.debug('From mapper: ', selfDescriptions); return selfDescriptions.items.map(({ 'properties(n)': p, 'labels(n)': l }) => ({ label: l[1], name: p.name,