diff --git a/frontend/src/Components/Search/MultiSelectText.jsx b/frontend/src/Components/Search/MultiSelectText.jsx index 193329854fc3f49d7a53d538e6211cddd09ba70a..a0970abcdd25321befaf7d1f5b287c2115fc6b19 100644 --- a/frontend/src/Components/Search/MultiSelectText.jsx +++ b/frontend/src/Components/Search/MultiSelectText.jsx @@ -1,7 +1,13 @@ import { styled } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; import React from 'react'; -import * as MUI from '@mui/material'; +import * as MUI from '@mui/material'; +/** + * Styled wrapper component for a clickable, scrollable flex container with a fixed height. + * + * @param {Object} theme - The theme object containing styling variables. + * @returns {JSX.Element} - A styled div component. + */ const Wrapper = styled('div')(({ theme }) => ({ display: 'flex', flexDirection: 'column', @@ -12,7 +18,13 @@ const Wrapper = styled('div')(({ theme }) => ({ overflowY: 'auto', // Make the content scrollable border: `2px solid black`, // Solid border })); - +/** + * Styled Typography component with conditional styling based on selection state. + * + * @param {Object} theme - The theme object containing styling variables. + * @param {boolean} selected - Flag to determine if the text is selected. + * @returns {JSX.Element} - A styled Typography component. + */ const SelectedText = styled(Typography)(({ theme, selected }) => ({ width: '100%', // Make Typography span the whole width color: selected ? 'black' : theme.palette.text.primary, // Set text color to black for selected options @@ -26,42 +38,58 @@ const SelectedText = styled(Typography)(({ theme, selected }) => ({ color: 'black', // Set text color to black when clicked }, })); - -function MultiSelectText({ selection }) { +/** + * Multi-select text component displaying a title and a list of selectable options. + * + * @param {Object} selection - The selection object containing title and options. + * @param {boolean} multiSelect - Flag to determine if multiple options can be selected. + * @param {Function} indexHandler - Callback function to handle the selected option index. + * @returns {JSX.Element} - A component with a title and a list of selectable text options. + */ +function MultiSelectText({ selection, multiSelect, indexHandler }) { const options = selection.options; const title = selection.title; const [selectedValues, setSelectedValues] = React.useState([]); const handleClick = (option) => { - const currentIndex = selectedValues.indexOf(option); - const newSelectedValues = [...selectedValues]; - if (currentIndex === -1) { - newSelectedValues.push(option); + if (!multiSelect) { + setSelectedValues([option]); + const optionIndex = options.indexOf(option); + console.log(optionIndex) + indexHandler(optionIndex); + } else { - newSelectedValues.splice(currentIndex, 1); - } + const currentIndex = selectedValues.indexOf(option); + const newSelectedValues = [...selectedValues]; + + if (currentIndex === -1) { + newSelectedValues.push(option); + } else { + newSelectedValues.splice(currentIndex, 1); + } - setSelectedValues(newSelectedValues); + setSelectedValues(newSelectedValues); + } }; return ( <div> - <MUI.Typography variant='h6' fontWeight={'bold'}> - {title} -</MUI.Typography> - <Wrapper> - {options.slice(0, 5).map((option) => ( - <SelectedText - key={option} - onClick={() => handleClick(option)} - selected={selectedValues.includes(option)} - > - {option} - </SelectedText> - ))} - </Wrapper> + <MUI.Typography variant='h6' fontWeight={'bold'}> + {title} + </MUI.Typography> + <Wrapper> + {options.map((option) => ( + <SelectedText + key={option} + onClick={() => handleClick(option)} + selected={selectedValues.includes(option)} + > + {option} + </SelectedText> + ))} + </Wrapper> </div> ); } diff --git a/frontend/src/Components/Search/SearchComponent.jsx b/frontend/src/Components/Search/SearchComponent.jsx new file mode 100644 index 0000000000000000000000000000000000000000..cc1ff99c265cee8c4f87a93a1f4d4ec0aa4c3f0b --- /dev/null +++ b/frontend/src/Components/Search/SearchComponent.jsx @@ -0,0 +1,150 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import * as MUI from '@mui/material'; +import MultiSelectText from './MultiSelectText'; +import "../../Css/Search.css"; +import * as externalSelections from './Selections'; // Import selections from external file + +export const SearchComponent = ({ data, changeRegion }) => { + + const [selectedProvince, setSelectedProvince] = useState(null); + + const [parentSelection, setParentSelection] = useState(-1); + + const[component, setComponent] = useState(null); + + const[components, setComponents] = useState(null); + + changeRegion(selectedProvince) + + + let globalChildSelection = -1; + + /* + Some temporary selection allocation + */ + const europaparlamentsval = externalSelections.europaparlamentsval; + const selections = externalSelections.selections; + +/** + * useEffect hook to update the selected province based on the incoming data. + * If the incoming data matches any province option, it sets the selected province. + * Otherwise, it sets the selected province to null. + * + * @param {string} data - The incoming data to check against province options. + * @param {function} setSelectedProvince - Function to set the selected province state. + */ + const provinceOptions = [ + 'Stockholm', 'Uppsala', 'Södermanland', 'Östergötland', 'Jönköping', + 'Kronobergs', 'Kalmar', 'Gotland', 'Blekinge', 'Skåne', 'Halland', + 'Västra Götaland', 'Värmland', 'Örebro', 'Västmanland', 'Dalarna', + 'Gävleborg', 'Västernorrland', 'Jämtland', 'Västerbotten', 'Norrbotten' + ]; + useEffect(() => { + + if (data && provinceOptions.includes(data)) { + setSelectedProvince(data); + + + } else { + setSelectedProvince(null); + } + }, [data]); + + const handleProvinceChange = (event, newValue) => { + setSelectedProvince(newValue); + }; + + const isOptionEqualToValue = (option, value) => option === value; + + const memoizedProvince = useMemo(() => selectedProvince, [selectedProvince]); + + + function parentIndexHandler(index){ + const element = europaparlamentsval.childrens[index]; + setParentSelection(element); + }; + + + function level_1_indexHandler(index){ + console.log("childIndexHandler index:" + index) + globalChildSelection = index; + }; + +// Function to get the child selection based on the parent element +const handleChildSelection = () => { + switch (parentSelection) { + case 'a': + setComponent( <div className='multi_select'><MultiSelectText selection={externalSelections.level_1_selections.level_1_1_selection} multiSelect={false} indexHandler={level_1_indexHandler}/><button onClick={handleComponentSelection}> Select</button></div>); + break; + case 'b': + setComponent( <div className='multi_select'><MultiSelectText selection={externalSelections.level_1_selections.level_1_2_selection} multiSelect={false} indexHandler={level_1_indexHandler}/><button onClick={handleComponentSelection}> Select</button></div>); + break; + case 'c': + setComponent( <div className='multi_select'><MultiSelectText selection={externalSelections.level_1_selections.level_1_3_selection} multiSelect={false} indexHandler={level_1_indexHandler}/><button onClick={handleComponentSelection}> Select</button></div>); + break; + default: + setComponent(null); + } +}; + + +/* + +*/ +const handleComponentSelection = () => { + console.log("handleComponentSelection:, global:;" +globalChildSelection) + const selected = {}; + + for (const [key, value] of Object.entries(selections)) { + if (value.id.includes(globalChildSelection)) { + selected[key] = value; + } + } + + + + setComponents( + <div> + {Object.values(selected).map((selection, index) => ( + <div key={index} className='multi_select'> + <MultiSelectText selection={selection} multiSelect={true} /> + </div> + ))} +</div>); +}; + + return ( + + + + <div> + + + <MUI.Autocomplete + disablePortal + id="combo-box-demo" + options={provinceOptions} + value={memoizedProvince} + onChange={handleProvinceChange} + getOptionSelected={isOptionEqualToValue} // Custom equality test + sx={{ width: 300 }} + renderInput={(params) => <MUI.TextField {...params} label="Län" />} + /> + + <div className='multi_select'><MultiSelectText selection={europaparlamentsval} multiSelect={false} indexHandler={parentIndexHandler}/> + <button onClick={handleChildSelection}> Select</button> + </div> + + <> + {component} + </> + + <>{components}</> + + + + </div> + ); +}; + +export default SearchComponent; diff --git a/frontend/src/Components/Search/Searchbar.jsx b/frontend/src/Components/Search/Searchbar.jsx deleted file mode 100644 index a35d2a5c48b020ce9b291709751478d90b9fafad..0000000000000000000000000000000000000000 --- a/frontend/src/Components/Search/Searchbar.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useState, useEffect, useMemo } from 'react'; -import * as MUI from '@mui/material'; -import MultiSelectText from './MultiSelectText'; -import "../../Css/Search.css"; - -export const Searchbar = ({ data, handlerFunction, changeRegion }) => { - - const [selectedProvince, setSelectedProvince] = useState(null); - changeRegion(selectedProvince) - - const selection1 = { - title: "Tabellinnehåll", - options: ['Antal nominerade', 'Andel nominerade', 'Antal valda', 'Andel valda', 'Antal ej valda'] - }; - - const selection2 = { - title: "Kön", - options: ['män', 'kvinnor', 'totalt'] - }; - - const selection3 = { - title: "Valår", - options: ['1995', '1999', '2004', '2009', '2014', '2019'] - }; - - const provinceOptions = [ - 'Stockholm', 'Uppsala', 'Södermanland', 'Östergötland', 'Jönköping', - 'Kronobergs', 'Kalmar', 'Gotland', 'Blekinge', 'Skåne', 'Halland', - 'Västra Götaland', 'Värmland', 'Örebro', 'Västmanland', 'Dalarna', - 'Gävleborg', 'Västernorrland', 'Jämtland', 'Västerbotten', 'Norrbotten' - ]; - - useEffect(() => { - if (data && provinceOptions.includes(data)) { - setSelectedProvince(data); - - - } else { - setSelectedProvince(null); - } - }, [data]); - - const handleProvinceChange = (event, newValue) => { - setSelectedProvince(newValue); - }; - - const isOptionEqualToValue = (option, value) => option === value; - - const memoizedProvince = useMemo(() => selectedProvince, [selectedProvince]); - - return ( - <div> - <MUI.Autocomplete - disablePortal - id="combo-box-demo" - options={provinceOptions} - value={memoizedProvince} - onChange={handleProvinceChange} - getOptionSelected={isOptionEqualToValue} // Custom equality test - sx={{ width: 300 }} - renderInput={(params) => <MUI.TextField {...params} label="Län" />} - /> - - <div className='multi_select'><MultiSelectText selection={selection1} /></div> - <div className='multi_select'><MultiSelectText selection={selection2} /></div> - <div className='multi_select'><MultiSelectText selection={selection3} /></div> - </div> - ); -}; - -export default Searchbar; diff --git a/frontend/src/Components/Search/Selections.jsx b/frontend/src/Components/Search/Selections.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ac25627e27ab8af14d32320d206151908199346d --- /dev/null +++ b/frontend/src/Components/Search/Selections.jsx @@ -0,0 +1,154 @@ +/** + PSEUDO CODE for future Tree data structure with root, traversal method and helper functions + + let root = selectionTree.root() + + root.getID(): + 0 + root.getTitle(): + "Europaparlamentsval" + root.getOptions(): + [01, 02, 03] + + + let selection = root.traverse(id = 01) + + + */ + export const europaparlamentsval = { + childrens: ['a','b','c'], + title: "Europaparlamentsval", + options: ['Nominerande och valda', "Valdeltagarundersökningen", "Valresultat"] + }; + +export const level_0_selection = { + childrens: ['a','b','c'], + title: "Europaparlamentsval", + options: ['Nominerande och valda', "Valdeltagarundersökningen", "Valresultat"] + }; + +export const level_1_1_selection = { + parent: ['a'], + id: [0, 1, 2, 3, 4, 5], + title: "Nominerade, valda och ej valda kandidater vid val till Europaparlamentet. Valår 1995 - 2019", + options: ['Kön', 'Kön och ålder', 'Kön och inrikes/utrikes födda', 'Kön och utbildning', 'Kön och inkomstintervall efter percentiler', 'Kön och parti'] + }; + + export const level_1_2_selection = { + parent: ['b'], + id: [0, 1, 2, 3, 4, 5], + title: "Valdeltagande i Europaparlamentsval bland: ", + options: [ + 'samtliga röstberättigade efter kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'svenska medborgare folkbokförda i Sverige efter kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'svenska medborgare boende utomlands efter kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'utländska medborgare folkbokförda i Sverige efter kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland svenska medborgare folkbokförda i Sverige efter region, kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland svenska medborgare folkbokförda i Sverige efter inrikes/utrikes född, kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland svenska medborgare folkbokförda i Sverige efter födelseregion och kön (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland svenska medborgare folkbokförda i Sverige efter utländsk/svensk bakgrund och kön (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland svenska medborgare folkbokförda i Sverige efter civilstånd, kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland svenska medborgare folkbokförda i Sverige efter inkomstintervall efter percentiler, kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland svenska medborgare folkbokförda i Sverige efter inkomstintervall efter percentiler, kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland svenska medborgare folkbokförda i Sverige, 18-64 år efter sammanboendeform, arbetskraftstillhörighet och kön (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland sysselsatta svenska medborgare folkbokförda i Sverige, 18-64 år efter yrke och kön (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland sysselsatta svenska medborgare folkbokförda i Sverige, 18-64 år efter socioekonomisk grupp, kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsval bland sysselsatta svenska medborgare folkbokförda i Sverige, 18-64 år efter socioekonomisk grupp, kön och ålder (urvalsundersökning). Valår 2004 - 2014', + 'Valdeltagande i Europaparlamentsvalet efter region, kön och bakgrundsvariabler. Antal röstberättigade och andel röstande. Valår 2019', + 'Valdeltagande i Europaparlamentsval bland anställda och arbetslösa svenska medborgare folkbokförda i Sverige, 18-64 år efter facklig organisation, kön och ålder (urvalsundersökning). Valår 2004 - 2014', + ] + }; + + export const level_1_3_selection = { + parent: ['c'], + id: [0, 1, 2, 3, 4, 5], + title: "Röstberättigade / Valresultat", + options: ['Röstberättigade', 'Valresultat'] + }; + + export const level_1_selections = { + level_1_1_selection, level_1_2_selection, level_1_3_selection + } + + export const level1_3_1_selection = { + title: 'Röstberättigade', + options: [ + 'region och medborgarskap. Valår 1995 - 2019', + 'kommun, rösträttsgrupp och kön. Valår 2004 - 2019', + 'rösträttsgrupp, kön och ålder. Valår 2004 - 2009', + 'svenska medborgare folkbokförda i Sverige efter kön och födelseregion. Valår 2004 - 2019', + 'svenska medborgare folkbokförda i Sverige efter kön och utländsk/svensk bakgrund. Valår 2004 - 2019', + 'svenska medborgare folkbokförda i Sverige efter kön och utbildningsnivå. Valår 2004 - 2019', + 'rösträttsgrupp, kön och ålder/förstagångsväljare. Valår 2004 - 2019' + ] + }; + export const level1_3_2_selection = { + title: 'Röstberättigade', + options: [ + 'erhållna mandat efter parti. Valår 1995 - 2019', + 'personröster efter region, parti och kön. Antal och andelar. Valår 1995 - 2019', + 'valresultat efter region och parti mm. Antal och andelar. Valår 1995 - 2019', + 'region, andelar. Valår 1995 - 2019' + ] + }; + export const nomineeSelection = { + parent: ['a'], + id: [0, 1, 2, 3, 4, 5], + title: "Tabellinnehåll", + options: ['Antal nominerade', 'Andel nominerade', 'Antal valda', 'Andel valda', 'Antal ej valda', 'Andel ej valda'] + }; + + export const genderSelection = { + parent: ['a'], + id: [0, 1, 2, 3, 4, 5], + title: "Kön", + options: ['Män', 'Kvinnor', 'Totalt'] + }; + + export const yearSelection = { + parent: ['a'], + id: [0, 1, 2, 3, 4, 5], + title: "Valår", + options: ['1995', '1999', '2004', '2009', '2014', '2019'] + }; + + export const ageSelection = { + parent: ['a'], + id: [1], + title: "Ålder", + options: ['0-29 år', '30-49 år', '50-64 år', '65+ år'] + }; + + export const bornSelection = { + parent: ['a'], + id: [2], + title: "Inrikes/utrikes född", + options: ['Inrikes född', 'Utrikes född'] + }; + + export const educationSelection = { + parent: ['a'], + id: [3], + title: "Utbildning", + options: ['Förgymnasial utbildning', 'Gymnasial utbildning', 'Eftergymnasial utbildning, mindre är 3 år', 'Eftergymnasial utbildning 3 år eller mer', 'Uppgift om utbildningsnivå saknas'] + }; + + export const incomeSelection = { + parent: ['a'], + id: [4], + title: "Inkomstintervall efter percentiler", + options: ['Nolltaxerare eller uppgift saknas', '0-20 %', '21-40 %', '41-60 %', '61-80 %', '81-100 %'], + }; + + export const partySelection = { + parent: ['a'], + id: [5], + title: "Parti", + options: ["Moderaterna", 'Centerpartiet', 'Liberalerna','Kristdemokraterna','Miljöpartiet','Socialdemokraterna','Vänsterpartiet','Junilistan','Piratpartiet','Feministiskt initiativ', 'Sverigedemonkraterna', 'övrigt partier'] + }; + + + export const selections = { + nomineeSelection, genderSelection, yearSelection, ageSelection, bornSelection, educationSelection, incomeSelection, partySelection + + }; \ No newline at end of file diff --git a/frontend/src/Pages/Dashboard/Dashboard.jsx b/frontend/src/Pages/Dashboard/Dashboard.jsx index ec22a8abee6e30aed8c3e864b509453d58e9d7d0..1d4aa10758bfe8d7395c17bdba4f97ccf4e33ca2 100644 --- a/frontend/src/Pages/Dashboard/Dashboard.jsx +++ b/frontend/src/Pages/Dashboard/Dashboard.jsx @@ -1,45 +1,46 @@ import '../../Css/Dashboard.css' -import React, {useState} from 'react' +import React, { useState } from 'react' import TopNavBar from '../../Components/Navbar/Navbar'; import Map from './Map'; import Visualiser from './Visualiser'; -import { Searchbar } from '../../Components/Search/Searchbar'; +import { SearchComponent } from '../../Components/Search/SearchComponent'; + + export const Dashboard = () => { - const [data, setData] = useState({label: "", id: ""}); + const [data, setData] = useState({ label: "", id: "" }); const [region, setRegion] = useState({}); + const handlerFunction = (Region_data) => { setData(Region_data) } const changeRegion = (regionName) => { // Update the region state to trigger re-render and change the color of the specified region's path - setRegion(regionName); - //const data = {label: regionName, id: "12"}; - - handlerFunction(data); + setRegion(regionName); + handlerFunction(data); + - }; return ( <div className='dashboard'> - <TopNavBar/> + <TopNavBar /> <div className='container'> - <div className='map_container' id="d3_map"> - <Map handlerFunction={handlerFunction} selectedRegion={region}/> - </div> + <div className='search_container'> - <Searchbar data={data.label} handlerFunction={handlerFunction} changeRegion= {changeRegion}/> - + <SearchComponent data={data.label} changeRegion={changeRegion} /> + + </div> + <div className='map_container' id="d3_map"> + <Map handlerFunction={handlerFunction} selectedRegion={region} /> </div> <div className='table_container'> - <Visualiser Regiondata = {data}/> + <Visualiser Regiondata={data} /> </div> </div> </div> ) } -// <Map handlerFunction={handlerFunction}/> \ No newline at end of file diff --git a/frontend/src/Pages/Dashboard/Visualiser.jsx b/frontend/src/Pages/Dashboard/Visualiser.jsx index 447e2f636c6d07e5ae03ca8d148c13f0c5d9a07f..0d93542d42d96189e381d9ff34c8d6a630400971 100644 --- a/frontend/src/Pages/Dashboard/Visualiser.jsx +++ b/frontend/src/Pages/Dashboard/Visualiser.jsx @@ -13,7 +13,7 @@ const Visualiser = ({Regiondata}) => { Visualiser </h1> <h2> LÄN: - {Regiondata.province} + {Regiondata.label} </h2> LÄNSKOD: {Regiondata.id} diff --git a/frontend/src/index.js b/frontend/src/index.js index ffa7a62f1abdaf9578fa3e3ebb927f032c421afd..86c1bcb58a7e4acb86c835a62bddb60473399878 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -5,6 +5,8 @@ import { Dashboard } from './Pages/Dashboard/Dashboard'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( + + //Implement routing from sign forms to dashbboard <React.StrictMode> {/*<TopNavBar/>*/} <Dashboard/>