diff --git a/backend/dist/index.js b/backend/dist/index.js index f2f7b3f..ce93355 100644 --- a/backend/dist/index.js +++ b/backend/dist/index.js @@ -28,9 +28,10 @@ const connectWithMongoDB = async (mongo_url, db_name) => { // Build things you need inside to pass to context const contextWrapper = async () => { // Context Metadata - const mongo_url = process.env.NAACP_MONGODB || "mongodb://mongo:6hVtkUf8XDMeUzsH66dk@containers-us-west-127.railway.app:8024"; // Local development - // const mongo_url = process.env.NAACP_MONGODB; - const dbName = "se_naacp_db"; + const mongo_url = process.env.NAACP_MONGODB || "mongodb://localhost:27017"; + // const mongo_url = "mongodb://localhost:27017"; + // const dbName = "se_naacp_db"; + const dbName = "se_naacp_gbh"; return { db: await connectWithMongoDB(mongo_url, dbName) }; }; const { url } = await startStandaloneServer(server, { diff --git a/backend/index.ts b/backend/index.ts index 3d1a77a..dba831a 100644 --- a/backend/index.ts +++ b/backend/index.ts @@ -42,12 +42,10 @@ const connectWithMongoDB = async ( const contextWrapper: ContextWrapperFunction = async () => { // Context Metadata - const mongo_url = process.env.NAACP_MONGODB || "mongodb+srv://naacpUser:naacpUser@cluster0.5bib9wc.mongodb.net/"; // Local development - - // const mongo_url = process.env.NAACP_MONGODB || "mongodb://mongo:6hVtkUf8XDMeUzsH66dk@containers-us-west-127.railway.app:8024"; // Local development - // const mongo_url = process.env.NAACP_MONGODB; - const dbName = "se_naacp_db"; - // const dbName = "se_naacp_gbh"; + const mongo_url = process.env.NAACP_MONGODB || "mongodb://localhost:27017"; + // const mongo_url = "mongodb://localhost:27017"; + // const dbName = "se_naacp_db"; + const dbName = "se_naacp_gbh"; return { db: await connectWithMongoDB(mongo_url, dbName) }; }; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3bc09e7..bedb325 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -48,11 +48,13 @@ "pigeon-maps": "^0.21.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^4.12.0", "react-konva": "^18.2.10", "react-lottie-player": "^1.5.4", "react-redux": "^8.0.5", "react-router-dom": "^6.8.0", "react-scripts": "^5.0.1", + "react-tabs": "^6.0.2", "react-tooltip": "^5.10.0", "redux-logger": "^3.0.6", "typescript": "^4.9.5", @@ -22442,6 +22444,14 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, + "node_modules/react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -22699,6 +22709,18 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/react-tabs": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz", + "integrity": "sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==", + "dependencies": { + "clsx": "^2.0.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/react-tooltip": { "version": "5.23.0", "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.23.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6653677..a08a649 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,11 +43,13 @@ "pigeon-maps": "^0.21.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^4.12.0", "react-konva": "^18.2.10", "react-lottie-player": "^1.5.4", "react-redux": "^8.0.5", "react-router-dom": "^6.8.0", "react-scripts": "^5.0.1", + "react-tabs": "^6.0.2", "react-tooltip": "^5.10.0", "redux-logger": "^3.0.6", "typescript": "^4.9.5", diff --git a/frontend/src/components/Accordion/Accordion.tsx b/frontend/src/components/Accordion/Accordion.tsx index c30cd00..2e77225 100644 --- a/frontend/src/components/Accordion/Accordion.tsx +++ b/frontend/src/components/Accordion/Accordion.tsx @@ -9,10 +9,11 @@ import { ArticleContext } from "../../contexts/article_context"; import { Article } from "../../__generated__/graphql"; import Callback from "../../pages/Callback/Callback"; import { NeighborhoodContext } from "../../contexts/neighborhood_context"; -import "./Accordion.css" +import "./Accordion.css"; +import BubbleChart from "../BubbleChart/BubbleChart"; interface TractCount { - tract: string; - count: number; + name: string; + value: number; } interface LabelDetail { @@ -22,8 +23,8 @@ interface LabelDetail { } interface LabelCount { - label: string; - count: number; + name: string; + value: number; } interface TractDetail { @@ -44,44 +45,42 @@ function getNeighborhood( return ""; } -const getLabelDetailsWithLimitedTracts = ( - articles: Article[], - neighborhoods: { [key: string]: string[] } -): React.ReactElement => { +const getLabelDetailsWithLimitedTracts = (articles: Article[], neighborhoods: { [key: string]: string[] }): React.ReactElement => { const labelDetails: Record< string, - { totalCount: number; tracts: Record } + { totalCount: number; tracts: Record } > = {}; - // Counting labels and tracts + // Counting labels, tracts, and storing articles for each tract articles.forEach((article) => { - const countedLabels: Set = new Set(); - article.openai_labels.forEach((label) => { - if (!countedLabels.has(label)) { - if (!labelDetails[label]) { - labelDetails[label] = { totalCount: 0, tracts: {} }; - } - labelDetails[label].totalCount = - labelDetails[label].totalCount + article.tracts.length; - countedLabels.add(label); + if (!labelDetails[label]) { + labelDetails[label] = { totalCount: 0, tracts: {} }; } + labelDetails[label].totalCount += article.tracts.length; article.tracts.forEach((tract) => { - labelDetails[label].tracts[tract] = - (labelDetails[label].tracts[tract] || 0) + 1; + if (!labelDetails[label].tracts[tract]) { + labelDetails[label].tracts[tract] = { count: 0, articles: [] }; + } + labelDetails[label].tracts[tract].count++; + labelDetails[label].tracts[tract].articles.push(article); }); }); }); - // Converting to desired structure with sorting and limiting tracts + // Transforming to the desired structure const labelDetailsArray: LabelDetail[] = Object.entries(labelDetails).map( ([label, detail]) => ({ label: label, totalCount: detail.totalCount, tractsCount: Object.entries(detail.tracts) - .map(([tract, count]) => ({ tract, count })) - .sort((a, b) => b.count - a.count) + .map(([name, tractDetail]) => ({ + name, + value: tractDetail.count, + articles: tractDetail.articles + })) + .sort((a, b) => b.value - a.value) .slice(0, 5), // Limit to first 5 tracts }) ); @@ -91,6 +90,8 @@ const getLabelDetailsWithLimitedTracts = ( .sort((a, b) => b.totalCount - a.totalCount) .slice(0, 5); // Limit to top 5 labels + console.log(result); + return ( <>
@@ -104,13 +105,8 @@ const getLabelDetailsWithLimitedTracts = ( {`${label.label} - ${label.totalCount} articles`} - - {label.tractsCount.map((tract) => ( -
  • - {`${tract.tract} - ${getNeighborhood(tract.tract, neighborhoods)} - ${tract.count} articles`} -
  • - ))} -
    + +
    ))} @@ -119,16 +115,17 @@ const getLabelDetailsWithLimitedTracts = ( ); }; + const getTractDetailsWithTotalCount = ( articles: Article[], neighborhoods: { [key: string]: string[] } ): React.ReactElement => { const tractDetails: Record< string, - { totalLabelCount: number; labels: Record } + { totalLabelCount: number; labels: Record } > = {}; - // Counting tracts and labels within each tract + // Counting tracts, labels within each tract, and storing articles articles.forEach((article) => { article.tracts.forEach((tract) => { if (!tractDetails[tract]) { @@ -136,46 +133,54 @@ const getTractDetailsWithTotalCount = ( } article.openai_labels.forEach((label) => { - tractDetails[tract].labels[label] = - (tractDetails[tract].labels[label] || 0) + 1; - tractDetails[tract].totalLabelCount++; + if (!tractDetails[tract].labels[label]) { + tractDetails[tract].labels[label] = { count: 0, articles: [] }; + } + tractDetails[tract].labels[label].count++; + tractDetails[tract].labels[label].articles.push(article); }); + + tractDetails[tract].totalLabelCount++; }); }); - // Converting to desired structure and slicing top 5 tracts + // Converting to desired structure const tractDetailsArray: TractDetail[] = Object.entries(tractDetails) .map(([tract, detail]) => ({ tract: tract, totalLabelCount: detail.totalLabelCount, labelsCount: Object.entries(detail.labels) - .map(([label, count]) => ({ label, count })) - .sort((a, b) => b.count - a.count) + .map(([name, labelDetail]) => ({ + name, + value: labelDetail.count, + articles: labelDetail.articles + })) + .sort((a, b) => b.value - a.value) .slice(0, 5), // Limit to top 5 labels })) .sort((a, b) => b.totalLabelCount - a.totalLabelCount) .slice(0, 5); // Limit to top 5 tracts + console.log(tractDetailsArray); return ( <>
    - {tractDetailsArray.map((label, index) => ( - + {tractDetailsArray.map((tractDetail, index) => ( + } aria-controls={`panel${index + 1}a-content`} id={`panel${index + 1}a-header`} > - {`${label.tract} - ${getNeighborhood(label.tract, neighborhoods)} - ${label.totalLabelCount} articles`} + {`${ + tractDetail.tract + } - ${getNeighborhood(tractDetail.tract, neighborhoods)} - ${ + tractDetail.totalLabelCount + } articles`} - - {label.labelsCount.map((tract) => ( -
  • - {`${tract.label} - ${tract.count} articles`} -
  • - ))} -
    + +
    ))} @@ -185,16 +190,16 @@ const getTractDetailsWithTotalCount = ( }; -const wrapper = (flag: boolean, - articles: Article[], - neighborhoods: { [key: string]: string[] } - ): React.ReactElement => { - - return flag ? getLabelDetailsWithLimitedTracts(articles, neighborhoods) :getTractDetailsWithTotalCount(articles, neighborhoods) - - } - +const wrapper = ( + flag: boolean, + articles: Article[], + neighborhoods: { [key: string]: string[] } +): React.ReactElement => { + return flag + ? getLabelDetailsWithLimitedTracts(articles, neighborhoods) + : getTractDetailsWithTotalCount(articles, neighborhoods); +}; interface AccordionProps { isLabels: boolean; @@ -211,7 +216,6 @@ const BasicAccordion: React.FC = ({ isLabels }) => { } }, [articleData]); - const component = isLabels ? getLabelDetailsWithLimitedTracts(articles, neighborhoodMasterList!) : getTractDetailsWithTotalCount(articles, neighborhoodMasterList!); @@ -220,10 +224,10 @@ const BasicAccordion: React.FC = ({ isLabels }) => { <> - {articles === null || articles.length === 0 ?( + {articles === null || articles.length === 0 ? ( ) : ( - wrapper(isLabels,articles, neighborhoodMasterList!) + wrapper(isLabels, articles, neighborhoodMasterList!) )} diff --git a/frontend/src/components/ArticleCard/ArticleCard.tsx b/frontend/src/components/ArticleCard/ArticleCard.tsx index 84205f5..a37838a 100644 --- a/frontend/src/components/ArticleCard/ArticleCard.tsx +++ b/frontend/src/components/ArticleCard/ArticleCard.tsx @@ -20,8 +20,6 @@ import Lottie from "react-lottie-player"; import { useState } from "react"; import { ArticleContext } from "../../contexts/article_context"; - - const columns = [ { field: "title", @@ -35,25 +33,31 @@ const columns = [ }, { field: "author", headerName: "Author", width: 130 }, { field: "publishingDate", headerName: "Publishing Date", width: 120 }, - { field: "neighborhood", headerName: "Neighborhood", width: 580 }, + { field: "neighborhood", headerName: "Neighborhood", width: 200 }, { field: "censusTract", headerName: "Census Tract", width: 200 }, { field: "category", headerName: "Category", width: 90 }, ]; -interface ArticleCardProps {} +interface ArticleCardProps { + optionalArticles?: Article[]; +} -const ArticleCard: React.FC = () => { +const ArticleCard: React.FC = ({ optionalArticles }) => { var articleRow: any = []; - const [articles, setArticles] = useState([]); - const { articleData, queryArticleDataType } = - React.useContext(ArticleContext)!; + const { articleData, queryArticleDataType } = React.useContext(ArticleContext)!; + + + React.useEffect(() => { - if (articleData) { + if (optionalArticles && optionalArticles.length > 0) { + setArticles(optionalArticles); + } else if (articleData) { setArticles(articleData); } - }, [articleData]); + }, [articleData, optionalArticles]); + articles.forEach((article, index) => { articleRow.push({ diff --git a/frontend/src/components/BubbleChart/BubbleChart.js b/frontend/src/components/BubbleChart/BubbleChart.js deleted file mode 100644 index 1ee0f79..0000000 --- a/frontend/src/components/BubbleChart/BubbleChart.js +++ /dev/null @@ -1,302 +0,0 @@ -import "./BubbleChart.css"; - -import { ResponsiveCirclePackingCanvas } from "@nivo/circle-packing"; -import { useState } from "react"; - -import { ResponsivePie } from '@nivo/pie' - - -import queryMethods from "../../Pipelines/data"; - -import * as React from "react"; - -import { Link } from "@mui/material"; - -import uniqid from "uniqid"; - -import dayjs from "dayjs"; -import { DataGrid } from "@mui/x-data-grid"; - -import Card from "@mui/material/Card"; -import CardContent from "@mui/material/CardContent"; - -const columns = [ - { - field: "title", - headerName: "Title", - width: 580, - renderCell: (params) => ( - - {params.row.title.title} - - ), - }, - { field: "author", headerName: "Author", width: 130 }, - { field: "publishingDate", headerName: "Publishing Date", width: 120 }, - // { field: "neighborhood", headerName: "Neighborhood", width: 110 }, - // { field: "censusTract", headerName: "Census Tract", width: 110 }, - // { field: "category", headerName: "Category", width: 90 }, -]; - -const colors = [ - "hsl(281, 70%, 50%)", - "hsl(55, 70%, 50%)", - "hsl(147, 70%, 50%)", - "hsl(9, 70%, 50%)", - "hsl(10, 70%, 50%)", - "hsl(150, 70%, 50%)", - "hsl(211, 70%, 50%)", - "hsl(100, 70%, 50%)", - "hsl(78, 70%, 50%)", - "hsl(39, 70%, 50%)", -]; - -function toFixed(num, fixed) { - // console.log(num,fixed); - var re = new RegExp("^-?\\d+(?:.\\d{0," + (fixed || -1) + "})?"); - return num?.toString().match(re)[0]; -} - -function extractNumbers(string) { - const pattern = /\d{6}/; // Matches six consecutive digits - const match = string.match(pattern); - if (match) { - return match[0]; - } else { - return null; - } -} - -export default function BubbleChart(Props) { - const minDate = dayjs("2020-11-01"); // November 2020 - const maxDate = dayjs("2023-01-09"); // February 2021 - - const [demographicData, setDemogaphicData] = React.useState([]); - const [demographicKeys, setDemographicKeys] = React.useState([]); - - const [zoomedId, setZoomedId] = useState(null); - - const [articleData, setArticleData] = React.useState([]); - - const data = { - name: "root", - children: Props.data, - }; - - const handleNodeClick = async (node) => { - console.log(); - if (zoomedId === node.id) { - // If the clicked node is already zoomed, reset the zoom - setZoomedId(null); - } else { - // Zoom in on the clicked node - - setZoomedId(node.id); - let articles = await queryMethods.getArticleData(node.data.articles); - - let articleRow = []; - console.log("Articles:", articles[0]); - for (const article_arr of articles[0]) { - let article = article_arr[0]; - articleRow.push({ - id: uniqid(), - title: { link: article.link, title: article.hl1 }, - author: `${article.author}`, - publishingDate: `${dayjs(article.pub_date).format("MMM D, YYYY")}`, - neighborhood: `${node.data.neighborhood}`, - censusTract: `${article.tracts[0]}`, - category: `${article.position_section}`, - }); - } - setArticleData(articleRow); - - // console.log(node.id); - - let data = await queryMethods.getCensusDateData( - minDate, - maxDate, - extractNumbers(node.id) - ); - - data = data.demographics; - - //Remove datapoints from data obj - delete data["Total_Population"]; - delete data["Other_Pop"]; - delete data["Total_not_H_and_L"]; - - let demoCounts = []; - let demoKeys = []; - let demoData = []; - let demoSum = 0; - - for (const [key, value] of Object.entries(data)) { - if (`${key}` !== "counties") { - let k = `${key}`; - let v = `${value}`; - if (k === "Total_H_and_L") { - k = "Hispanic/Latine"; - } - demoKeys.push(k.replace("_", " ")); - demoCounts.push(v); - demoSum += value; - } - } - - for (let i = 0; i < demoKeys.length; i++) { - let piechart = { - id: `${demoKeys[i]}`, - label: `${demoKeys[i]}`, - color: colors[i], - }; - let val = (demoCounts[i] / demoSum) * 100; - piechart["value"] = parseFloat(toFixed(val, 2)); - demoData.push(piechart); - } - setDemographicKeys(demoKeys); - setDemogaphicData(demoData); - } - }; - - return ( - <> -
    -
    - e.id} - labelTextColor={{ - from: "color", - modifiers: [["darker", 2.4]], - }} - borderColor={{ - from: "color", - modifiers: [["darker", 0.3]], - }} - animate={false} - /> -
    - -
    - {zoomedId && ( -
    - - - {/*

    Articles From Neighborhood/Tract

    */} -
    - -
    -
    -
    -
    - )} - - {zoomedId && ( -
    - -
    - )} -
    -
    - - ); -} diff --git a/frontend/src/components/BubbleChart/BubbleChart.tsx b/frontend/src/components/BubbleChart/BubbleChart.tsx new file mode 100644 index 0000000..068efc5 --- /dev/null +++ b/frontend/src/components/BubbleChart/BubbleChart.tsx @@ -0,0 +1,92 @@ +import "./BubbleChart.css"; + +import { ResponsiveCirclePackingCanvas } from "@nivo/circle-packing"; +import { useState } from "react"; + +import { ResponsivePie } from "@nivo/pie"; + +import queryMethods from "../../Pipelines/data"; + +import * as React from "react"; + +import { Link } from "@mui/material"; + +import uniqid from "uniqid"; + +import dayjs from "dayjs"; +import { DataGrid } from "@mui/x-data-grid"; + +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import ArticleCard from "../ArticleCard/ArticleCard"; + + +interface BubbleChartProps { + bubbleData: { name: string; value: number }[]; +} + +const BubbleChart: React.FC = ({ bubbleData }) => { + const [zoomedId, setZoomedId] = useState(null); + const [length, setLength] = useState("col-md-12 col-sm-12"); + const [articles, setArticles] = useState([]); + + const data = { + name: "root", + children: bubbleData, + }; + + const handleNodeClick = async (node: any) => { + console.log(); + if (zoomedId === node.id) { + setZoomedId(null); + setLength("col-md-12 col-sm-12"); + } else { + setArticles(node.data.articles); + setLength("col-md-4 col-sm-12"); + setZoomedId(node.id); + } + }; + + return ( + <> +
    +
    + + + e.id} + labelTextColor={{ + from: "color", + modifiers: [["darker", 2.4]], + }} + borderColor={{ + from: "color", + modifiers: [["darker", 0.3]], + }} + animate={false} + /> + + +
    +
    {zoomedId && }
    +
    + + ); +}; + +export default BubbleChart; diff --git a/frontend/src/components/DashboardTabs/dashboardTabs.css b/frontend/src/components/DashboardTabs/dashboardTabs.css new file mode 100644 index 0000000..f2b7661 --- /dev/null +++ b/frontend/src/components/DashboardTabs/dashboardTabs.css @@ -0,0 +1,44 @@ +.react-tabs__tab-list { + /* background: linear-gradient(180deg, rgb(27, 176, 161) 0%, rgba(27, 176, 161, 0.7) 100%); */ + background-color: linear-gradient( + 180deg, + rgba(210, 229, 98) 0%, + rgba(195, 213, 88, 0.7) 100% + ); + display: flex; + justify-content: space-between; /* or space-around */ + width: 100%; + list-style-type: none; + padding: 0; +} + +.react-tabs__tab:focus { + outline: none; +} + +.react-tabs__tab { + flex: 1; /* This makes each tab of equal width */ + text-align: center; /* Optional: to center the text inside each tab */ +} + +div { + color: linear-gradient( + 180deg, + rgb(27, 176, 161) 0%, + rgba(27, 176, 161, 0.7) 100% + ); +} + +.number-count { + color: #2e2b28; + font-family: "Karla-Bold", Helvetica; + font-size: 40px; + font-weight: 700; +} + +.title { + color: #8e8e8e; + font-family: "Karla-Regular", Helvetica; + font-size: 12.7px; + font-weight: 400; +} diff --git a/frontend/src/components/DashboardTabs/dashboardTabs.tsx b/frontend/src/components/DashboardTabs/dashboardTabs.tsx new file mode 100644 index 0000000..376acd8 --- /dev/null +++ b/frontend/src/components/DashboardTabs/dashboardTabs.tsx @@ -0,0 +1,248 @@ +import React, { useState } from "react"; +import "./dashboardTabs.css"; + +import { HiOutlineUserGroup } from "react-icons/hi2"; +import { HiOutlineNewspaper } from "react-icons/hi2"; + +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import { Grid, Paper, Stack, Typography, styled } from "@mui/material"; + +import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; +import "react-tabs/style/react-tabs.css"; +import ArticleCard from "../ArticleCard/ArticleCard"; +import BasicAccordion from "../Accordion/Accordion"; +import { Article } from "../../__generated__/graphql"; + +import "./dashboardTabs.css"; + +interface dashboardTabs { + articles: Article[]; +} + +const DashboardTabs: React.FC = ({ articles }) => { + const [activeTabIndex, setActiveTabIndex] = useState(0); + + const uniqueOpenaiLabels = new Set(); + articles.forEach((article) => { + article.openai_labels.forEach((label) => { + uniqueOpenaiLabels.add(label); + }); + }); + const uniqueTracts = new Set(); + + articles.forEach((article) => { + article.tracts.forEach((tracts) => { + uniqueTracts.add(tracts); + }); + }); + + function tab1(clicked: boolean) { + return ( + + + + + + + + + + + TOTAL CENSUS TRACTS + + + {uniqueTracts.size} + + + + + + ); + } + + function tab2(clicked: boolean) { + return ( + + + + + + + + + + + TOTAL TOPICS + + + {uniqueOpenaiLabels.size} + + + + + + ); + } + + function tab3(clicked: boolean) { + return ( + + + + + + + + + + + TOTAL ARTICLES + + + {articles.length} + + + + + + ); + } + + return ( + <> + + + setActiveTabIndex(0)}> + {tab1(activeTabIndex === 0)} + + setActiveTabIndex(1)}> + {tab2(activeTabIndex === 1)} + + setActiveTabIndex(2)}> + {tab3(activeTabIndex === 2)} + + + + + + + + + + + + + + ); +}; + +export default DashboardTabs; diff --git a/frontend/src/pages/DashboardPage/Dashboard.tsx b/frontend/src/pages/DashboardPage/Dashboard.tsx index 8c4cbab..ae062c8 100644 --- a/frontend/src/pages/DashboardPage/Dashboard.tsx +++ b/frontend/src/pages/DashboardPage/Dashboard.tsx @@ -12,6 +12,7 @@ import { useAuth0 } from "@auth0/auth0-react"; import dayjs from "dayjs"; import BasicAccordion from "../../components/Accordion/Accordion"; import { NeighborhoodContext } from "../../contexts/neighborhood_context"; +import DashboardTabs from "../../components/DashboardTabs/dashboardTabs"; export default function Dashboard() { const { user, isAuthenticated } = useAuth0(); @@ -19,6 +20,7 @@ export default function Dashboard() { const { neighborhoodMasterList, queryNeighborhoodDataType } = React.useContext(NeighborhoodContext)!; + console.log(user?.sub); React.useEffect(() => { queryNeighborhoodDataType("NEIGHBORHOOD_DATA"); }, []); @@ -69,19 +71,25 @@ export default function Dashboard() {
    -
    +
    + +
    +
    + +
    +

    Top 5 Topics

    -
    + {/*

    Articles

    -
    +
    */}
    -
    + {/*

    Active Labels

    @@ -91,7 +99,7 @@ export default function Dashboard() {
    -
    +
    */}
    );