diff --git a/package.json b/package.json index 3fd10ea4..1795dec6 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@foerderfunke/matching-engine": "^1.1.7", + "@foerderfunke/matching-engine": "^1.1.8", "@mui/icons-material": "^5.15.19", "@mui/material": "^5.15.19", "@mui/x-charts": "^7.6.2", diff --git a/public/assets/data/requirement-profiles/requirement-profiles-hydration.json b/public/assets/data/requirement-profiles/requirement-profiles-hydration.json index 56ee8a19..844d48ca 100644 --- a/public/assets/data/requirement-profiles/requirement-profiles-hydration.json +++ b/public/assets/data/requirement-profiles/requirement-profiles-hydration.json @@ -8,8 +8,8 @@ "en": "Cost-of-living assistance" }, "description": { - "de": "Die „Hilfe zum Lebensunterhalt“ richtet sich an Menschen, die eine Zeit lang nicht oder nur wenig arbeiten können. Sie dient dazu grundlegende Bedürfnisse wie Essen, Kleidung und Miete zu bezahlen. Hilfe zum Lebensunterhalt erhalten Personen, für die andere staatliche Leistungen, wie etwa Rente oder Bürgergeld (früher ALG II), nicht infrage kommen.", - "en": "If you are currently unable to work due to health reasons, you may be entitled to cost of living assistance. The prerequisite is that you are temporarily unable to earn your living yourself or with the help of other people, for example a partner or your family." + "de": "Die „Hilfe zum Lebensunterhalt“ unterstützt Menschen, die vorübergehend nicht oder nur wenig arbeiten können und keinen Anspruch auf andere Sozialleistungen haben, bei der Deckung von Grundbedürfnissen wie Essen, Kleidung und Miete.", + "en": "The 'Hilfe zum Lebensunterhalt' (cost-of-living assistance) supports people who are temporarily unable to work or can only work a little and do not have access to other social benefits. It helps cover basic needs such as food, clothing, and rent." } }, "ff:kindergeld": { diff --git a/src/core/utils/buildEligibilityReports.js b/src/core/utils/buildEligibilityReports.js index 7bb661ae..5477165f 100644 --- a/src/core/utils/buildEligibilityReports.js +++ b/src/core/utils/buildEligibilityReports.js @@ -7,9 +7,17 @@ export const buildEligibilityReports = (validationReport, metadata, hydrationDat }; } - const reports = validationReport['ff:hasEvaluatedRequirementProfile'] || []; + const reports = validationReport['ff:hasEvaluatedRequirementProfile'] || []; + const hasDefinition = metadata['ff:hasDefinition'] || []; + const defDict = hasDefinition.reduce((acc, cat) => { + const id = cat['@id']; + const label = cat['rdfs:label']?.['@value'] || ''; + acc[id] = label; + return acc; + }, {}); + const allReports = reports.map(report => { - const rpUri = report['ff:hasRpUri']?.['@id'] || null; + const rpUri = report['ff:hasRpUri']?.['@id'] || null; const result = report['ff:hasEligibilityStatus']?.['@id'] || null; // hydration data @@ -27,38 +35,121 @@ export const buildEligibilityReports = (validationReport, metadata, hydrationDat // metadata const rpMetadata = metadata['ff:hasRP']?.find(rp => rp['@id'] === rpUri) const validationStage = rpMetadata?.['ff:validationStage']?.['@id'] || 'Unknown Validation Stage'; + + // administrative level + const rawLevels = rpMetadata?.['ff:administrativeLevel']; + const administrativeLevels = rawLevels + ? (Array.isArray(rawLevels) ? rawLevels : [rawLevels]) + .map(level => ({ + id: level['@id'], + label: defDict[level['@id']] || '' + })) + : []; + + // associated law + const rawLaw = rpMetadata?.['ff:legalBasis']; + const associatedLaws = rawLaw + ? (Array.isArray(rawLaw) ? rawLaw : [rawLaw]) + .map(law => ({ + id: law['@id'], + label: defDict[law['@id']] || '' + })) + : []; - return { uri: rpUri, result, id, title, category, description, status, requiredDocuments, additionalSupport, legalBasis, furtherInformation, validationStage }; - }); + // providing agency + const rawAgencies = rpMetadata?.['ff:providingAgency']; + const providingAgencies = rawAgencies + ? (Array.isArray(rawAgencies) ? rawAgencies : [rawAgencies]) + .map(agency => ({ + id: agency['@id'], + label: defDict[agency['@id']] || '' + })) + : []; - const eligibilityStatus = allReports.reduce((acc, report) => { + + // benefit categories + const rawCategories = rpMetadata?.['ff:category']; + const benefitCategories = rawCategories + ? (Array.isArray(rawCategories) ? rawCategories : [rawCategories]) + .map(cat => ({ + id: cat['@id'], + label: defDict[cat['@id']] || '' + })) + : []; + + return { + uri: rpUri, + result, + id, + title, + category, + description, + status, + requiredDocuments, + additionalSupport, + legalBasis, + furtherInformation, + validationStage, + associatedLaws, + providingAgencies, + administrativeLevels, + benefitCategories + }; + }); + + const filterOptions = { + associatedLaws: Array.from( + new Map( + allReports + .flatMap(r => r.associatedLaws || []) + .filter(Boolean) + .map(cat => [cat.id, cat]) + ).values() + ), + + providingAgencies: Array.from( + new Map( + allReports + .flatMap(r => r.providingAgencies || []) + .filter(Boolean) + .map(cat => [cat.id, cat]) + ).values() + ), + + administrativeLevels: Array.from( + new Map( + allReports + .flatMap(r => r.administrativeLevels || []) + .filter(Boolean) + .map(cat => [cat.id, cat]) + ).values() + ), + + benefitCategories: Array.from( + new Map( + allReports + .flatMap(r => r.benefitCategories || []) + .filter(Boolean) + .map(cat => [cat.id, cat]) + ).values() + ), + }; + + const eligibilityData = allReports.reduce((acc, report) => { const { category, result } = report; if (!acc[category]) { acc[category] = {}; } - if (result === "ff:eligible") { - if (!acc[category][result]) { - acc[category][result] = { preliminary: [], final: [] }; - } - - if (report.validationStage === "ff:complete-validation") { - acc[category][result].final.push(report); - } - - if (report.validationStage === "ff:preliminary-validation") { - acc[category][result].preliminary.push(report); - } - } else { - if (!acc[category][result]) { - acc[category][result] = []; - } - acc[category][result].push(report); + if (!acc[category][result]) { + acc[category][result] = []; } + acc[category][result].push(report); + return acc; }, {}); - return eligibilityStatus; + return { eligibilityData, filterOptions }; }; diff --git a/src/ui/language/translations.js b/src/ui/language/translations.js index 7b878afa..566df126 100644 --- a/src/ui/language/translations.js +++ b/src/ui/language/translations.js @@ -212,6 +212,17 @@ const translations = { probableNotEligible: "Probable ineligibility", notEnoughData: "Not enough data for assessment", beta: "Some benefits are still in beta, meaning they are not fully tested and may contain errors." + }, + filter: { + title: "Filter", + benefitCategories: "Benefit category", + administrativeLevels: "Administrative level", + providingAgencies: "Providing agency", + associatedLaws: "Associated law", + }, + item: { + eligible: "You are likely eligible", + notEligible: "You are likely not eligible", } }, profile: { @@ -498,6 +509,17 @@ const translations = { probableNotEligible: "Wahrscheinlich besteht kein Anspruch", notEnoughData: "Nicht genügend Angaben für eine Einschätzung", beta: "Eininge Leistungen sind noch in der Beta-Phase, d.h. sie sind noch nicht vollständig getestet und können Fehler enthalten." + }, + filter: { + title: "Filter", + benefitCategories: "Leistungskategorie", + administrativeLevels: "Verwaltungsebene", + providingAgencies: "Zuständige Behörde", + associatedLaws: "Zugehöriges Gesetz", + }, + item: { + eligible: "Sie haben womöglich Anspruch", + notEligible: "Sie haben voraussichtlich keinen Anspruch", } }, profile: { diff --git a/src/ui/screens/eligibilty-overview/EligibilityOverviewScreen.js b/src/ui/screens/eligibilty-overview/EligibilityOverviewScreen.js index f5e811db..b19bc1cd 100644 --- a/src/ui/screens/eligibilty-overview/EligibilityOverviewScreen.js +++ b/src/ui/screens/eligibilty-overview/EligibilityOverviewScreen.js @@ -5,45 +5,54 @@ import AppScreenWrapperContainer from "@/ui/shared-components/app-screen-wrapper import { VBox } from '@/ui/shared-components/LayoutBoxes'; import EligibilityOverviewHeader from "./components/EligibilityOverviewHeader"; import EligibilityOverviewSection from "./components/EligibilityOverviewSection"; +import EligibilityOverviewFilter from './components/EligibilityOverviewFilter'; const EligibilityOverviewScreen = ({ t, - iconPaths, eligibilityData, + filterOptions, + filters, + onChangeFilters, }) => { return ( - - { - eligibilityData ? ( - <> - { - eligibilityData["social_benefit"] && ( - - ) - } - { - eligibilityData["business"] && ( - - ) - } - - ) : - - } + + + + { + eligibilityData ? ( + <> + { + eligibilityData["social_benefit"] && ( + + ) + } + { + eligibilityData["business"] && ( + + ) + } + + ) : + + } + diff --git a/src/ui/screens/eligibilty-overview/EligibilityOverviewScreenContainer.js b/src/ui/screens/eligibilty-overview/EligibilityOverviewScreenContainer.js index c3bf44da..35a26319 100644 --- a/src/ui/screens/eligibilty-overview/EligibilityOverviewScreenContainer.js +++ b/src/ui/screens/eligibilty-overview/EligibilityOverviewScreenContainer.js @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useState } from 'react'; import useFetchData from "@/ui/shared-hooks/utility/useFetchResource"; import useTranslation from "@/ui/language/useTranslation"; import EligibilityOverviewScreen from './EligibilityOverviewScreen'; @@ -6,28 +6,31 @@ import useEligibilityData from "./hooks/useEligibilityData"; import { useLanguageStore } from '@/ui/storage/useLanguageStore'; import { useMetadataStore } from '@/ui/storage/zustand'; import useProduceValidationReport from './hooks/useProduceValidationReport'; +import useFilterEligibilityData from './hooks/useFilterEligibilityData'; const EligibilityOverviewScreenContainer = () => { const { t } = useTranslation(); const language = useLanguageStore((state) => state.language); + const [filters, setFilters] = useState({ + providingAgencies: [], + associatedLaws: [], + benefitCategories: [], + administrativeLevels: [] + }); const hydrationData = useFetchData('assets/data/requirement-profiles/requirement-profiles-hydration.json'); - const {validationReport} = useProduceValidationReport(); + const { validationReport } = useProduceValidationReport(); const metadata = useMetadataStore((state) => state.metadata); - const eligibilityData = useEligibilityData(validationReport, metadata, hydrationData, language); - - const iconPaths = useMemo(() => ({ - eligible: `${process.env.PUBLIC_URL}/assets/images/application/icon-image-eligible.svg`, - preliminaryEligible: `${process.env.PUBLIC_URL}/assets/images/application/icon-image-preliminary-eligible.svg`, - ineligible: `${process.env.PUBLIC_URL}/assets/images/application/icon-image-ineligible.svg`, - missing: `${process.env.PUBLIC_URL}/assets/images/application/icon-image-missing.svg`, - }), []); + const { eligibilityData, filterOptions } = useEligibilityData(validationReport, metadata, hydrationData, language); + const filteredEligibilityData = useFilterEligibilityData(eligibilityData, filters); return ( ); }; diff --git a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewBanner.js b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewBanner.js new file mode 100644 index 00000000..c992e723 --- /dev/null +++ b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewBanner.js @@ -0,0 +1,44 @@ +import { Typography } from "@mui/material"; +import { HBox } from "@/ui/shared-components/LayoutBoxes"; +import theme from "@/theme"; + +const EligibilityOverviewBanner = ({ t, eligible }) => { + console.log("EligibilityOverviewBanner", eligible); + + const eligibilityBannerType = (() => { + switch (eligible) { + case 'eligible': + return { + text: 'app.browseAll.item.eligible', + backgroundColor: 'secondary.light' + } + case 'preliminary-eligible': + return { + text: 'app.browseAll.item.preliminaryEligible', + backgroundColor: 'yellow.main' + } + case 'non-eligible': + return { + text: 'app.browseAll.item.notEligible', + backgroundColor: 'error.light' + } + default: + return { + text: 'app.browseAll.item.missingData', + backgroundColor: theme.palette.black.main + } + } + })(); + + return ( + + {t(eligibilityBannerType.text)} + + ); +} +export default EligibilityOverviewBanner; \ No newline at end of file diff --git a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewFilter.js b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewFilter.js new file mode 100644 index 00000000..1ac3308e --- /dev/null +++ b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewFilter.js @@ -0,0 +1,95 @@ +import React from 'react'; +import { + Typography, + FormControl, + InputLabel, + Select, + MenuItem, + Checkbox, + ListItemText, +} from '@mui/material' +import { VBox, HBox } from '@/ui/shared-components/LayoutBoxes'; +import EligibilityOverviewTag from './EligibilityOverviewTag'; +import theme from '@/theme'; + +const FILTER_KEYS = [ + { key: 'benefitCategories', label: 'Benefit Category' }, + { key: 'administrativeLevels', label: 'Administrative Level' }, + { key: 'providingAgencies', label: 'Providing Agency' }, + { key: 'associatedLaws', label: 'Associated Law' }, +] + +const EligibilityOverviewFilter = ({ t, filterOptions, filters, onChangeFilters }) => { + const handleChange = (key) => (event) => { + onChangeFilters(prev => ({ + ...prev, + [key]: event.target.value, + })) + } + + const groupedSelected = Object.entries(filters).reduce((acc, [key, selectedIds]) => { + const labels = (filterOptions[key] || []) + .filter(opt => selectedIds.includes(opt.id)) + .map(opt => opt.label); + if (labels.length) { + acc[key] = labels; + } + return acc; + }, {}); + + return ( + + + + {t('app.browseAll.filter.title')} + + + {FILTER_KEYS.map(({ key, label }) => ( + + {t(`app.browseAll.filter.${key}`)} + + + ))} + + + { + Object.entries(groupedSelected).length > 0 && ( + { + Object.entries(groupedSelected).map(([category, labels]) => ( + labels.map((label, index) => ( + + )) + ))} + + ) + } + + ); +}; + +export default EligibilityOverviewFilter; \ No newline at end of file diff --git a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewHeader.js b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewHeader.js index 92ce34d2..e00acd73 100644 --- a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewHeader.js +++ b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewHeader.js @@ -1,15 +1,11 @@ -import React, { useState } from 'react'; -import { Link } from "react-router-dom"; -import { IconButton, Typography, Collapse, Button } from '@mui/material'; -import PersonIcon from '@mui/icons-material/Person'; -import { VBox, HBox } from "../../../shared-components/LayoutBoxes"; -import useTranslation from "../../../language/useTranslation"; -import EligibilityOverviewLegend from './EligibilityOverviewLegend'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import React from 'react'; +import { Typography } from '@mui/material'; +import { VBox } from "@/ui/shared-components/LayoutBoxes"; +import useTranslation from "@/ui/language/useTranslation"; +import RegularButton from '@/ui/shared-components/buttons/RegularButton'; -const EligibilityOverviewHeader = ({ iconPaths }) => { +const EligibilityOverviewHeader = () => { const { t } = useTranslation(); - const [showLegend, setShowLegend] = useState(false); return ( @@ -17,23 +13,12 @@ const EligibilityOverviewHeader = ({ iconPaths }) => { {t('app.browseAll.header')} - - - + @@ -43,38 +28,6 @@ const EligibilityOverviewHeader = ({ iconPaths }) => { {t('app.browseAll.disclaimerText')} - - - {t('app.browseAll.preliminaryEligibleTitle')} - - - {t('app.browseAll.preliminaryEligibleText')} - - - - - {t('app.browseAll.legendTitle')} - - setShowLegend(!showLegend)}> - - {t('app.browseAll.legendText')} - - - - - - { - showLegend && - - - - } - ); }; diff --git a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewItem.js b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewItem.js index 8fbcfa7e..dd5e4d2b 100644 --- a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewItem.js +++ b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewItem.js @@ -1,44 +1,110 @@ -import React, { useState } from 'react'; -import { Add, Remove } from "@mui/icons-material"; -import { IconButton, Typography, Box } from '@mui/material'; +import React from 'react'; +import { Divider, Typography } from '@mui/material'; import { HBox, VBox } from '@/ui/shared-components/LayoutBoxes'; -import EligibilityOverviewItemDetails from "./EligibilityOverviewItemDetails"; +import theme from '@/theme'; +import RegularButton from '@/ui/shared-components/buttons/RegularButton'; +import { useSelectedBenefitStore, useSelectedTopicsStore } from "@/ui/storage/zustand"; +import EligibilityOverviewTag from './EligibilityOverviewTag'; +import EligibilityOverviewBanner from './EligibilityOverviewBanner'; -const EligibilityOverviewItem = ({ item, eligible, iconPath }) => { - const [showDescription, setShowDescription] = useState(false); +const filterKeys = [ + 'benefitCategories', + 'administrativeLevels', + 'associatedLaws', + 'providingAgencies' +]; - const toggleDescription = () => { - setShowDescription(prevState => !prevState); - }; - - const marginBottom = showDescription ? '16px' : '0'; +const EligibilityOverviewItem = ({ t, item, eligible }) => { const color = eligible === 'indeterminate' ? 'black.light' : 'black.main'; + const setSelectedBenefit = useSelectedBenefitStore((state) => state.setSelectedBenefit); + const clearSelectedTopics = useSelectedTopicsStore((state) => state.clear); return ( - - - logo - - - - {item.title} - - {item.status === "beta" && ( - - Beta + + + + + + + {item.title} - )} - + {item.status === "beta" && ( + + Beta + + )} + + + {item.description} + + - - - {showDescription ? : } - - - {showDescription && ( - - )} + + + { + filterKeys.flatMap(key => ( + item[key] && ( + item[key].map(tag => ( + + )) + ) + ) + )} + + + + { + eligible !== 'indeterminate' && ( + + + + ) + } + {eligible === 'indeterminate' && + + { + clearSelectedTopics() + setSelectedBenefit(item.id); + }} + text={'app.browseAll.checkElBtn'} + link={`/onboarding-welcome/${item.id}`} + size='small' + /> + + } + + + + ); }; diff --git a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewItemDetails.js b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewItemDetails.js index 03345509..19e9c714 100644 --- a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewItemDetails.js +++ b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewItemDetails.js @@ -1,8 +1,7 @@ import React from 'react'; import { Typography } from '@mui/material'; -import { HBox, VBox } from '../../../shared-components/LayoutBoxes'; +import { HBox, VBox } from '@/ui/shared-components/LayoutBoxes'; import { useSelectedBenefitStore, useSelectedTopicsStore } from "@/ui/storage/zustand"; -import theme from '@/theme'; import RegularButton from '@/ui/shared-components/buttons/RegularButton'; const EligibilityOverviewItemDetails = ({ item, eligible }) => { @@ -10,8 +9,8 @@ const EligibilityOverviewItemDetails = ({ item, eligible }) => { const clearSelectedTopics = useSelectedTopicsStore((state) => state.clear); return ( - - + + {item.description} diff --git a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewLegend.js b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewLegend.js deleted file mode 100644 index 0e6739bd..00000000 --- a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewLegend.js +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import { Typography } from '@mui/material'; -import { HBox, VBox } from '../../../shared-components/LayoutBoxes'; -import useTranslation from "../../../language/useTranslation"; -import theme from '../../../../theme'; - -const EligibilityOverviewLegend = ({iconPaths}) => { - const { t } = useTranslation(); - console.log("iconPaths", iconPaths); - const { eligible, ineligible, preliminaryEligible, missing } = iconPaths - - return ( - - - - logo - - {t('app.browseAll.legend.probableEligible')} - - - - logo - - {t('app.browseAll.legend.preliminaryEligible')} - - - - logo - - {t('app.browseAll.legend.probableNotEligible')} - - - - logo - - {t('app.browseAll.legend.notEnoughData')} - - - - - Beta - - - {t('app.browseAll.legend.beta')} - - - - - ); -}; - -export default EligibilityOverviewLegend; diff --git a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewList.js b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewList.js index 7341f750..5a1688b5 100644 --- a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewList.js +++ b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewList.js @@ -1,11 +1,9 @@ import React from 'react'; import { Typography } from '@mui/material'; import { VBox } from '@/ui/shared-components/LayoutBoxes'; -import useTranslation from "@/ui/language/useTranslation"; import EligibilityOverviewItem from "./EligibilityOverviewItem"; -const EligibilityOverviewList = ({ items, eligible, iconPath }) => { - const { t } = useTranslation(); +const EligibilityOverviewList = ({ t, items, eligible, iconPath }) => { const headerText = (() => { switch (eligible) { case 'eligible': @@ -26,7 +24,7 @@ const EligibilityOverviewList = ({ items, eligible, iconPath }) => { {items.map((item, index) => ( - + ))} diff --git a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewSection.js b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewSection.js index 86117dc2..474a90ff 100644 --- a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewSection.js +++ b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewSection.js @@ -4,12 +4,10 @@ import { VBox } from "@/ui/shared-components/LayoutBoxes"; import theme from "@/theme"; import EligibilityOverviewList from "./EligibilityOverviewList"; -const EligibilityOverviewSection = ({ category, eligibilitySection, iconPaths }) => { - const eligibleBenefitsComplete = eligibilitySection['ff:eligible']?.final || []; - const eligibleBenefitsPreliminary = eligibilitySection['ff:eligible']?.preliminary || []; +const EligibilityOverviewSection = ({ t, category, eligibilitySection }) => { + const eligibleBenefitsComplete = eligibilitySection['ff:eligible'] || []; const ineligibleBenefits = eligibilitySection['ff:ineligible'] || [] const undeterminableBenefits = eligibilitySection['ff:missingData'] || [] - const { eligible, preliminaryEligible, ineligible, missing } = iconPaths; return ( {eligibleBenefitsComplete.length > 0 && - } - {eligibleBenefitsPreliminary.length > 0 && - } + } {ineligibleBenefits.length > 0 && - } + } {undeterminableBenefits.length > 0 && - } + } ); diff --git a/src/ui/screens/eligibilty-overview/components/EligibilityOverviewTag.js b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewTag.js new file mode 100644 index 00000000..02a4dddd --- /dev/null +++ b/src/ui/screens/eligibilty-overview/components/EligibilityOverviewTag.js @@ -0,0 +1,40 @@ + +import React from 'react'; +import { Typography } from '@mui/material'; +import { HBox } from '@/ui/shared-components/LayoutBoxes'; +import theme from '@/theme'; + +const EligibilityOverTag = ({ tag, tagType }) => { + + let tagStyle; + switch (tagType) { + case 'administrativeLevels': + tagStyle = theme.palette.green.main ; + break; + case 'associatedLaws': + tagStyle = theme.palette.pink.main; + break; + case 'providingAgencies': + tagStyle = theme.palette.red.main ; + break; + case 'benefitCategories': + tagStyle = theme.palette.blue.main; + break; + default: + tagStyle = theme.palette.black.main; + } + + return ( + + + {tag} + + + ); +} + +export default EligibilityOverTag; diff --git a/src/ui/screens/eligibilty-overview/hooks/useEligibilityData.js b/src/ui/screens/eligibilty-overview/hooks/useEligibilityData.js index eabb705d..ffe50a13 100644 --- a/src/ui/screens/eligibilty-overview/hooks/useEligibilityData.js +++ b/src/ui/screens/eligibilty-overview/hooks/useEligibilityData.js @@ -1,9 +1,162 @@ import { useMemo } from "react"; -import { buildEligibilityReports } from "@/core/utils/buildEligibilityReports"; const useEligibilityData = (validationReport, metadata, hydrationData, language) => { return useMemo(() => { - return buildEligibilityReports(validationReport, metadata, hydrationData, language); + if (!validationReport) { + return { + eligible: [], + nonEligible: [], + missingData: [] + }; + } + + const reports = validationReport['ff:hasEvaluatedRequirementProfile'] || []; + const hasDefinition = metadata['ff:hasDefinition'] || []; + const defDict = hasDefinition.reduce((acc, cat) => { + const id = cat['@id']; + const label = cat['rdfs:label']?.['@value'] || ''; + acc[id] = label; + return acc; + }, {}); + + const allReports = reports.map(report => { + const rpUri = report['ff:hasRpUri']?.['@id'] || null; + const result = report['ff:hasEligibilityStatus']?.['@id'] || null; + + // hydration data + const rpHydration = hydrationData?.[rpUri] + const id = rpHydration?.id || 'Unknown Id'; + const title = rpHydration?.title?.[language] || 'Unknown Title'; + const category = rpHydration?.category || 'Unknown Category'; + const description = rpHydration?.description?.[language] || 'Unknown Description'; + const status = rpHydration?.status || 'Unknown Status'; + const requiredDocuments = rpHydration?.required_documents?.[language] || []; + const additionalSupport = rpHydration?.additional_support?.[language] || {}; + const legalBasis = rpHydration?.legal_basis?.[language] || {}; + const furtherInformation = rpHydration?.further_information?.[language] || []; + + // metadata + const rpMetadata = metadata['ff:hasRP']?.find(rp => rp['@id'] === rpUri) + const validationStage = rpMetadata?.['ff:validationStage']?.['@id'] || 'Unknown Validation Stage'; + + // administrative level + const rawLevels = rpMetadata?.['ff:administrativeLevel']; + const administrativeLevels = rawLevels + ? (Array.isArray(rawLevels) ? rawLevels : [rawLevels]) + .map(level => ({ + id: level['@id'], + label: defDict[level['@id']] || '' + })) + : []; + + // associated law + const rawLaw = rpMetadata?.['ff:legalBasis']; + const associatedLaws = rawLaw + ? (Array.isArray(rawLaw) ? rawLaw : [rawLaw]) + .map(law => ({ + id: law['@id'], + label: defDict[law['@id']] || '' + })) + : []; + + // providing agency + const rawAgencies = rpMetadata?.['ff:providingAgency']; + const providingAgencies = rawAgencies + ? (Array.isArray(rawAgencies) ? rawAgencies : [rawAgencies]) + .map(agency => ({ + id: agency['@id'], + label: defDict[agency['@id']] || '' + })) + : []; + + + // benefit categories + const rawCategories = rpMetadata?.['ff:category']; + const benefitCategories = rawCategories + ? (Array.isArray(rawCategories) ? rawCategories : [rawCategories]) + .map(cat => ({ + id: cat['@id'], + label: defDict[cat['@id']] || '' + })) + : []; + + return { + uri: rpUri, + result, + id, + title, + category, + description, + status, + requiredDocuments, + additionalSupport, + legalBasis, + furtherInformation, + validationStage, + associatedLaws, + providingAgencies, + administrativeLevels, + benefitCategories + }; + }); + + const filterOptions = { + associatedLaws: Array.from( + new Map( + allReports + .flatMap(r => r.associatedLaws || []) + .filter(Boolean) + .map(cat => [cat.id, cat]) + ).values() + ), + + providingAgencies: Array.from( + new Map( + allReports + .flatMap(r => r.providingAgencies || []) + .filter(Boolean) + .map(cat => [cat.id, cat]) + ).values() + ), + + administrativeLevels: Array.from( + new Map( + allReports + .flatMap(r => r.administrativeLevels || []) + .filter(Boolean) + .map(cat => [cat.id, cat]) + ).values() + ), + + benefitCategories: Array.from( + new Map( + allReports + .flatMap(r => r.benefitCategories || []) + .filter(Boolean) + .map(cat => [cat.id, cat]) + ).values() + ), + }; + + const eligibilityData = allReports.reduce((acc, report) => { + const { category, result } = report; + + if (!acc[category]) { + acc[category] = {}; + } + + if (!acc[category][result]) { + acc[category][result] = []; + } + + acc[category][result].push(report); + + return acc; + }, {}); + + return { eligibilityData, filterOptions }; + + }, [validationReport, metadata, hydrationData, language]); }; diff --git a/src/ui/screens/eligibilty-overview/hooks/useFilterEligibilityData.js b/src/ui/screens/eligibilty-overview/hooks/useFilterEligibilityData.js new file mode 100644 index 00000000..a87c626f --- /dev/null +++ b/src/ui/screens/eligibilty-overview/hooks/useFilterEligibilityData.js @@ -0,0 +1,46 @@ +import { useMemo } from 'react'; + +const useFilterEligibilityData = (eligibilityData, filters) => { + return useMemo(() => { + if (!eligibilityData) return null; + + const filterKeys = [ + 'providingAgencies', + 'associatedLaws', + 'benefitCategories', + 'administrativeLevels' + ]; + + const passesAllFilters = item => { + return filterKeys.every(key => { + const selectedValues = filters[key] || []; + if (selectedValues.length === 0) return true; + + const itemValue = item[key].map(v => v['id'] || v).filter(Boolean); + if (Array.isArray(itemValue)) { + return itemValue.some(v => selectedValues.includes(v)); + } else { + return selectedValues.includes(itemValue); + } + }); + }; + + return Object.fromEntries( + Object.entries(eligibilityData) + .map(([categoryName, categoryBuckets]) => { + const filteredBuckets = Object.fromEntries( + Object.entries(categoryBuckets) + .map(([bucketKey, items]) => [ + bucketKey, + items.filter(passesAllFilters) + ]) + .filter(([_bucketKey, items]) => items.length > 0) + ); + return [categoryName, filteredBuckets]; + }) + .filter(([_category, buckets]) => Object.keys(buckets).length > 0) + ); + }, [eligibilityData, filters]); +} + +export default useFilterEligibilityData; diff --git a/src/ui/screens/landing-page/LandingPage.js b/src/ui/screens/landing-page/LandingPage.js index 51a270be..addf34b9 100644 --- a/src/ui/screens/landing-page/LandingPage.js +++ b/src/ui/screens/landing-page/LandingPage.js @@ -1,7 +1,7 @@ import React from "react"; -import { useStore } from "../../shared-components/ViewportUpdater"; -import { VBox } from "../../shared-components/LayoutBoxes"; -import Layout from "../../shared-components/Layout"; +import { useStore } from "@/ui/shared-components/ViewportUpdater"; +import { VBox } from "@/ui/shared-components/LayoutBoxes"; +import Layout from "@/ui/shared-components/Layout"; import LandingPageHowItWorks from "./sections/how-it-works/LandingPageHowItWorks"; import LandingPagePrinciples from "./sections/principles/LandingPagePrinciples"; import LandingPageTeam from "./sections/team/LandingPageTeam"; diff --git a/src/ui/screens/onboarding-pages/choice/OnboardingChoice.js b/src/ui/screens/onboarding-pages/choice/OnboardingChoice.js index bb61e54b..58647931 100644 --- a/src/ui/screens/onboarding-pages/choice/OnboardingChoice.js +++ b/src/ui/screens/onboarding-pages/choice/OnboardingChoice.js @@ -27,7 +27,7 @@ const OnboardingChoice = () => {