diff --git a/frontend/public/index.html b/frontend/public/index.html index aa069f27cbd9d53394428171c3989fd03db73c76..23f2ec8fe34a40ced4af45d0116c1d114e9ee724 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -9,12 +9,15 @@ name="description" content="Web site created using create-react-app" /> - <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> + + <!-- manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> - <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> + <!--link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> + + --> <!-- Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. diff --git a/frontend/src/Components/Navbar/Navbar.js b/frontend/src/Components/Navbar/Navbar.js index ffc461169add39330ecc5cb34d535a3325f352b8..cd506b38a0942f8288861c56fe07b0a2552f5510 100644 --- a/frontend/src/Components/Navbar/Navbar.js +++ b/frontend/src/Components/Navbar/Navbar.js @@ -1,11 +1,17 @@ import React from 'react'; import '../../Css/Navbar.css' -function TopNavBar() { +function TopNavBar({toggleDisplay, displayMap}) { return ( <div className="top-nav"> <button className='sign_out_button'>Sign Out</button> + + {/* Toggle button to switch between Map and Visualiser */} + <button className='sign_out_button' onClick={toggleDisplay}> + {displayMap ? 'Switch to Visualiser' : 'Switch to Map'} + </button> + <h1 className='h1'>DASHBOARD</h1> </div> ); diff --git a/frontend/src/Components/Search/MultiSelectText.jsx b/frontend/src/Components/Search/MultiSelectText.jsx index 281307cae10451eb38283ab96a08197d090c8386..a0970abcdd25321befaf7d1f5b287c2115fc6b19 100644 --- a/frontend/src/Components/Search/MultiSelectText.jsx +++ b/frontend/src/Components/Search/MultiSelectText.jsx @@ -1,50 +1,97 @@ import { styled } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; import React from 'react'; - +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', alignItems: 'flex-start', gap: theme.spacing(1), cursor: 'pointer', // Make the entire area clickable + maxHeight: '130px', // Set a fixed height + 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 }) => ({ - color: selected ? theme.palette.primary.main : theme.palette.text.primary, + width: '100%', // Make Typography span the whole width + color: selected ? 'black' : theme.palette.text.primary, // Set text color to black for selected options fontWeight: selected ? 'bold' : 'normal', + backgroundColor: selected ? theme.palette.grey[400] : 'transparent', // Darker grey background when clicked for non-selected options + '&:hover': { + backgroundColor: selected ? theme.palette.grey[400] : theme.palette.grey[200], // Grey background on hover for non-selected options + }, + '&:focus': { + backgroundColor: theme.palette.grey[400], // Darker grey background when clicked + color: 'black', // Set text color to black when clicked + }, })); +/** + * 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 options = ['Antal nominerade', 'Andel nominerade', 'Antal valda', 'Andel valda', 'Antal ej valda']; - -function MultiSelectText() { 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 ( - <Wrapper> - {options.map((option) => ( - <SelectedText - key={option} - onClick={() => handleClick(option)} - selected={selectedValues.includes(option)} - > - {option} - </SelectedText> - ))} - </Wrapper> + <div> + <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> ); } -export default MultiSelectText; \ No newline at end of file + +export default MultiSelectText; diff --git a/frontend/src/Components/Search/MultiSelectText2.jsx b/frontend/src/Components/Search/MultiSelectText2.jsx deleted file mode 100644 index afe3824db164a8e5d66b4e75ab988dd390978b14..0000000000000000000000000000000000000000 --- a/frontend/src/Components/Search/MultiSelectText2.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import { styled } from '@mui/material/styles'; -import Typography from '@mui/material/Typography'; -import React from 'react'; - -const Wrapper = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - alignItems: 'flex-start', - gap: theme.spacing(1), - cursor: 'pointer', // Make the entire area clickable -})); - -const SelectedText = styled(Typography)(({ theme, selected }) => ({ - color: selected ? theme.palette.primary.main : theme.palette.text.primary, - fontWeight: selected ? 'bold' : 'normal', -})); - -const options = ['män', 'kvinnor', 'totalt']; - -function MultiSelectText2() { - const [selectedValues, setSelectedValues] = React.useState([]); - - const handleClick = (option) => { - const currentIndex = selectedValues.indexOf(option); - const newSelectedValues = [...selectedValues]; - - if (currentIndex === -1) { - newSelectedValues.push(option); - } else { - newSelectedValues.splice(currentIndex, 1); - } - - setSelectedValues(newSelectedValues); - }; - - return ( - <Wrapper> - {options.map((option) => ( - <SelectedText - key={option} - onClick={() => handleClick(option)} - selected={selectedValues.includes(option)} - > - {option} - </SelectedText> - ))} - </Wrapper> - ); -} -export default MultiSelectText2; \ No newline at end of file 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 aabf99eeaf44d65c962cd2c667041e07f4f0f6ad..0000000000000000000000000000000000000000 --- a/frontend/src/Components/Search/Searchbar.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import * as MUI from '@mui/material'; -import MultiSelectText from './MultiSelectText' -import MultiSelectText2 from './MultiSelectText2'; -import "./Search.css" -export const Searchbar = () => { - - - const provinceOptions = [ - { label: 'Stockholm', id: 1 }, - { label: 'Uppsala', id: 3 }, - { label: 'Södermanland', id: 4 }, - { label: 'Östergötland', id: 5 }, - { label: 'Jönköping', id: 6 }, - { label: 'Kronobergs', id: 7 }, - { label: 'Kalmar', id: 8 }, - { label: 'Gotland', id: 9 }, - { label: 'Blekinge', id: 10 }, - { label: 'Skåne', id: 12 }, - { label: 'Halland', id: 13 }, - { label: 'Västra Götaland', id: 14 }, - { label: 'Värmland', id: 17 }, - { label: 'Örebro', id: 18 }, - { label: 'Västmanland', id: 19 }, - { label: 'Dalarna', id: 20 }, - { label: 'Gävleborg', id: 21 }, - { label: 'Västernorrland', id: 22 }, - { label: 'Jämtland', id: 23 }, - { label: 'Västerbotten', id: 24 }, - { label: 'Norrbotten', id: 25 } - ]; - const optionsAntal = ['Antal nominerade', 'Andel nominerade', 'Antal valda', 'Andel valda', 'Antal ej valda']; - const optionsKön = ['män', 'kvinnor', 'totalt']; - return ( - <div> - <MUI.Autocomplete - disablePortal - id="combo-box-demo" - options={provinceOptions} - sx={{ width: 300 }} - renderInput={(params) => <MUI.TextField {...params} label="Län" />}> - - </MUI.Autocomplete> - <MUI.Typography variant='h6' fontWeight={'bold'}> - Tabellinnehål - </MUI.Typography> - <div className='multi_select'><MultiSelectText/></div> - <MUI.Typography variant='h6' fontWeight={'bold'}> - Kön - </MUI.Typography> - <div className='multi_select'><MultiSelectText2 /></div> - - - </div> - - ) -} 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/Components/Search/asssets.txt b/frontend/src/Components/Search/asssets.txt new file mode 100644 index 0000000000000000000000000000000000000000..4a719621280a223d81a8343e50c0ab93b6fce851 --- /dev/null +++ b/frontend/src/Components/Search/asssets.txt @@ -0,0 +1,23 @@ + const provinceOptions = [ + { label: 'Stockholm', id: 1 }, + { label: 'Uppsala', id: 3 }, + { label: 'Södermanland', id: 4 }, + { label: 'Östergötland', id: 5 }, + { label: 'Jönköping', id: 6 }, + { label: 'Kronobergs', id: 7 }, + { label: 'Kalmar', id: 8 }, + { label: 'Gotland', id: 9 }, + { label: 'Blekinge', id: 10 }, + { label: 'Skåne', id: 12 }, + { label: 'Halland', id: 13 }, + { label: 'Västra Götaland', id: 14 }, + { label: 'Värmland', id: 17 }, + { label: 'Örebro', id: 18 }, + { label: 'Västmanland', id: 19 }, + { label: 'Dalarna', id: 20 }, + { label: 'Gävleborg', id: 21 }, + { label: 'Västernorrland', id: 22 }, + { label: 'Jämtland', id: 23 }, + { label: 'Västerbotten', id: 24 }, + { label: 'Norrbotten', id: 25 } + ]; \ No newline at end of file diff --git a/frontend/src/Css/Map.css b/frontend/src/Css/Map.css index a9a62fb05cc76c4885618c48ab48dce3495051b4..35ba0ed3ba015eb3aebc240e6c55f3656cd8259b 100644 --- a/frontend/src/Css/Map.css +++ b/frontend/src/Css/Map.css @@ -5,10 +5,10 @@ svg { path { stroke: rgb(0, 0, 0); stroke-width: 1px; - fill: white; + } path:hover { stroke-width: 2px; - fill: rgb(214, 214, 214); + /* fill: rgb(214, 214, 214);*/ } \ No newline at end of file diff --git a/frontend/src/Css/Navbar.css b/frontend/src/Css/Navbar.css index 9637c10ba2d4f79729a0b2c7b5e33a8ae3ca465d..53307928882ef7c48a320f3eeb432f546ffa4ed2 100644 --- a/frontend/src/Css/Navbar.css +++ b/frontend/src/Css/Navbar.css @@ -6,9 +6,14 @@ border-style: solid; box-shadow: 0 0 10px rgba(0, 0, 0, .2); } +.sign_out_button{ + margin: 5px; + height: 50px; + +} .h1{ - margin-left: auto; - margin-right: auto; - margin-top: auto; - margin-bottom: auto; + top: 4%; + left: 40%; + right: 50%; + position:absolute; } \ No newline at end of file diff --git a/frontend/src/Components/Search/Search.css b/frontend/src/Css/Search.css similarity index 85% rename from frontend/src/Components/Search/Search.css rename to frontend/src/Css/Search.css index 52f2c3a00a18556fb5f7095dc810c54ea424d8af..f87be40f23d9308de61d57c25b9fdf6f58faa53e 100644 --- a/frontend/src/Components/Search/Search.css +++ b/frontend/src/Css/Search.css @@ -5,7 +5,7 @@ .multi_select{ margin: 5px; margin-top: 20px; - border-style: solid; + padding: 2px; } \ No newline at end of file diff --git a/frontend/src/Css/Visualiser.css b/frontend/src/Css/Visualiser.css index 5532c0fdac4a31500980d6f5a676de585bd31e74..03089fc479184daa44cfd7267300d179874e1bca 100644 --- a/frontend/src/Css/Visualiser.css +++ b/frontend/src/Css/Visualiser.css @@ -12,6 +12,6 @@ } -.h1{ +.h2{ text-align: center; } \ No newline at end of file diff --git a/frontend/src/Pages/Dashboard/Dashboard.jsx b/frontend/src/Pages/Dashboard/Dashboard.jsx index 9e6856dfc41e54702a245e4b2e3c73ac9e5264ea..68f05c84e93bdbc439a20941479fde66fec96f3c 100644 --- a/frontend/src/Pages/Dashboard/Dashboard.jsx +++ b/frontend/src/Pages/Dashboard/Dashboard.jsx @@ -1,34 +1,66 @@ -import '../../Css/Dashboard.css' -import React, {useState} from 'react' +import '../../Css/Dashboard.css'; +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({province: "", id: ""}); + const [data, setData] = useState({ label: "", id: "" }); + const [region, setRegion] = useState({}); + const [displayMap, setDisplayMap] = useState(true); // State variable to track the currently displayed component const handlerFunction = (Region_data) => { - setData(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); + handlerFunction(data); + }; + + const toggleDisplay = () => { + setDisplayMap(!displayMap); // Toggle the displayMap state + }; + return ( <div className='dashboard'> - <TopNavBar/> + <TopNavBar toggleDisplay = {toggleDisplay} displayMap = {displayMap} /> + <div className='container'> - <div className='map_container' id="d3_map"> - <Map handlerFunction={handlerFunction}/> - </div> - + <div className='search_container'> - <Searchbar/> - - </div> - <div className='table_container'> - <Visualiser Regiondata = {data}/> + <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} /> + </div> + </div> </div> - ) -} + ); +}; + + +/* +<div className='content_container'> + {/* Conditional rendering based on the displayMap state */ + /* + } + {displayMap ? ( + <div className='map_container' id="d3_map"> + <Map handlerFunction={handlerFunction} selectedRegion={region} /> + </div> + ) : ( + <div className='table_container'> + <Visualiser Regiondata={data} /> + </div> + )} + </div> -// <Map handlerFunction={handlerFunction}/> \ No newline at end of file +*/ \ No newline at end of file diff --git a/frontend/src/Pages/Dashboard/Map.jsx b/frontend/src/Pages/Dashboard/Map.jsx index 3daa106880e73ef27ea2a2e695005aae21e851f0..95e37fe3e4c2cb94e83aac05fd90babc6ca4d19a 100644 --- a/frontend/src/Pages/Dashboard/Map.jsx +++ b/frontend/src/Pages/Dashboard/Map.jsx @@ -12,12 +12,22 @@ function getgeoJson(){ return geojson; } -const Map = ({handlerFunction}) => { +const Map = ({handlerFunction, selectedRegion}) => { useEffect(() => { + function colorHandler(d){ + if(d.properties.name === selectedRegion){ + const data = {label: d.properties.name, id: d.properties.l_id}; + handlerFunction(data); + return 'lightgray'; + }else{ + return 'white'; + } + } + let geojson = getgeoJson(); //Filtering out non-topological data @@ -39,37 +49,49 @@ const Map = ({handlerFunction}) => { let rectY = 25; - + let clickedPath = null; + // Join GeoJSON data to path elements svg.selectAll('path') .data(geojson.features) .enter().append('path') .attr('d', geoGenerator) - .attr('fill', 'lightgray') + // .attr('fill', d => (d.properties.name === selectedRegion) ? 'lightgrey' : 'white') // Set fill color based on selectedRegion prop + .attr('fill', d => colorHandler(d)) .attr('stroke', 'black') .on('click', function(event, d){ //OnClick event handler // Handle the click event here - const data = {province: d.properties.name, id: d.properties.l_id}; - + // Change the fill color to black on click + // Reset the fill color of the previously clicked path + if (clickedPath) { + clickedPath.attr('fill', 'white'); + } + + // Set the fill color of the clicked path to black + d3.select(this).attr('fill', 'lightgrey'); + clickedPath = d3.select(this); + console.log(d3.select(this)) + + const data = {label: d.properties.name, id: d.properties.l_id}; + handlerFunction(data); - console.log("province: " + data.province +' id: '+data.id); + console.log("province: " + data.label +' id: '+data.id); + // Prevent event bubbling }).on('mouseenter', function(event, d) {//OnMouseEnter event handler - // Update region state + // Handle mouse enter event const [x, y] = d3.pointer(event); let length = d.properties.name.length // Add background rectangle - const textBackground = svg.append('rect') - .attr('id', 'tooltip-background') - .attr('x', x+5) - .attr('y', y - rectY) // Adjust as needed - .attr('width', 160 + (2^length)) // Adjust as needed - .attr('height', 20) // Adjust as needed - .attr('fill', 'white'); // Adjust background color as needed - - + const textBackground = svg.append('rect') + .attr('id', 'tooltip-background') + .attr('x', x+5) + .attr('y', y - rectY) // Adjust as needed + .attr('width', 160 + (2^length)) // Adjust as needed + .attr('height', 20) // Adjust as needed + .attr('fill', 'white'); // Adjust background color as needed // Show text svg.append('text') .attr('id', 'tooltip') @@ -104,7 +126,7 @@ const Map = ({handlerFunction}) => { // Remove SVG when component unmounts svg.remove(); }; - }, []); // Empty dependency array to ensure useEffect runs only once + }, [selectedRegion]); // Empty dependency array to ensure useEffect runs only once // Return null because the map is rendered using D3 inside useEffect return null; diff --git a/frontend/src/Pages/Dashboard/Visualiser.jsx b/frontend/src/Pages/Dashboard/Visualiser.jsx index 7dc2014643e5b86aa552344dba781b3bc8184664..6b9e293b83838c91583b3c69aab222091557b7f6 100644 --- a/frontend/src/Pages/Dashboard/Visualiser.jsx +++ b/frontend/src/Pages/Dashboard/Visualiser.jsx @@ -9,14 +9,14 @@ const Visualiser = ({Regiondata}) => { return ( <div > - <h1 className='h1'> + <h2 > Visualiser - </h1> + </h2> <h2> LÄN: - {Regiondata.province} + {Regiondata.label} </h2> - <h3>LÄNSKOD: {Regiondata.id}</h3> + LÄNSKOD: {Regiondata.id} <div className='table'> <BarChart selectedKey={Regiondata.id}/> diff --git a/frontend/src/index.js b/frontend/src/index.js index 30b0ac78639018a8515eb1d6d3a70949e11633e9..9134671c5864dd89564c4d98305d3a3eec23a9ad 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -8,6 +8,8 @@ import { SignUpForm } from './Pages/SignupSigninForms/SignUpForm.jsx'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( + + //Implement routing from sign forms to dashbboard <React.StrictMode> <BrowserRouter> <Routes>