From a126b41140e3bc4c438814d5bb9d6d883d97ed0d Mon Sep 17 00:00:00 2001 From: Arbyhisenaj <41119392+Arbyhisenaj@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:18:42 -0700 Subject: [PATCH 01/20] mild --- nextjs/src/app/reports/MapNavbar.tsx | 79 ++++ .../app/reports/[id]/ConstituenciesPanel.tsx | 20 +- nextjs/src/app/reports/[id]/context.tsx | 4 +- nextjs/src/app/reports/[id]/layout.tsx | 14 +- nextjs/src/app/reports/[id]/page.tsx | 149 +++---- nextjs/src/components/TopConstituencies.tsx | 77 +++- nextjs/src/components/dataConfig.tsx | 396 +++++++++--------- nextjs/src/components/navbar.tsx | 9 +- .../components/report/AddMapLayerButton.tsx | 2 +- nextjs/src/components/report/ReportMap.tsx | 4 +- .../components/reportsConstituencyItem.tsx | 3 +- .../components/ui/ConstituenciesDropdown.tsx | 100 +++++ nextjs/src/lib/map.tsx | 2 +- 13 files changed, 552 insertions(+), 307 deletions(-) create mode 100644 nextjs/src/app/reports/MapNavbar.tsx create mode 100644 nextjs/src/components/ui/ConstituenciesDropdown.tsx diff --git a/nextjs/src/app/reports/MapNavbar.tsx b/nextjs/src/app/reports/MapNavbar.tsx new file mode 100644 index 000000000..06f006c52 --- /dev/null +++ b/nextjs/src/app/reports/MapNavbar.tsx @@ -0,0 +1,79 @@ +'use client' + +import React from 'react' +import Link from 'next/link' +import { MappedIcon } from '@/components/navbar' +import { Provider as JotaiProvider, atom, useAtom, useAtomValue } from "jotai"; +import { MAP_REPORT_FRAGMENT, isConstituencyPanelOpenAtom, isDataConfigOpenAtom } from "@/lib/map"; +import { MAP_REPORT_LAYER_ANALYTICS, ReportMap, selectedConstituencyAtom } from "@/components/report/ReportMap"; + +import { BarChart3, Layers, MoreVertical, RefreshCcw, Trash } from "lucide-react" +import { twMerge } from "tailwind-merge"; + + + + + + + + +export default function MapNavbar() { + // const[isDataConfigOpen, setDataConfigOpen] = useAtom(isDataConfigOpenAtom); + // const toggleDataConfig = () => setDataConfigOpen(b => !b); + // const [isConstituencyPanelOpen, setConstituencyPanelOpen] = useAtom(isConstituencyPanelOpenAtom); + // const [selectedConstituency, setSelectedConstituency] = useAtom(selectedConstituencyAtom); + // const toggleConsData = () => { + // setConstituencyPanelOpen(b => { + // if (b) { + // setSelectedConstituency(null) + // } + // return !b + // }) + // } + + // const toggles = [ + // { + // icon: Layers, + // label: "Map layers", + // enabled: isDataConfigOpen, + // toggle: toggleDataConfig + // }, + // { + // icon: BarChart3, + // label: "Constituency data", + // enabled: isConstituencyPanelOpen, + // toggle: toggleConsData + // } + // ] + + return ( + + ) +} diff --git a/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx b/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx index cef941302..808308c4d 100644 --- a/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx +++ b/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx @@ -1,6 +1,7 @@ import { selectedConstituencyAtom } from "@/components/report/ReportMap" import { atom, useAtom } from "jotai" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { X } from "lucide-react" import { Card, CardContent, @@ -15,7 +16,8 @@ import { useReportContext } from "./context"; export const constituencyPanelTabAtom = atom("list") -export function ConstituenciesPanel () { + +export function ConstituenciesPanel() { const [ selectedConstituencyId, setSelectedConstituency, @@ -33,12 +35,16 @@ export function ConstituenciesPanel () { }, [selectedConstituencyId, setTab]) return ( - - - - All Constituencies +
+
+

Constituency Data

+ {/* { setOpen(false) }} /> */} +
+ + + All {!!selectedConstituencyId && ( - + Selected )} @@ -55,6 +61,6 @@ export function ConstituenciesPanel () { )}
-
+ ) } \ No newline at end of file diff --git a/nextjs/src/app/reports/[id]/context.tsx b/nextjs/src/app/reports/[id]/context.tsx index d92761144..ac4396dde 100644 --- a/nextjs/src/app/reports/[id]/context.tsx +++ b/nextjs/src/app/reports/[id]/context.tsx @@ -5,8 +5,8 @@ import { QueryResult } from "@apollo/client"; import { createContext, useContext, useState } from "react"; export const defaultDisplayOptions = { - showLastElectionData: false, - showMPs: false, + showLastElectionData: true, + showMPs: true, showStreetDetails: false, analyticalAreaType: AnalyticalAreaType.ParliamentaryConstituency_2025 } diff --git a/nextjs/src/app/reports/[id]/layout.tsx b/nextjs/src/app/reports/[id]/layout.tsx index 10888c4b0..75ab4d99c 100644 --- a/nextjs/src/app/reports/[id]/layout.tsx +++ b/nextjs/src/app/reports/[id]/layout.tsx @@ -1,6 +1,8 @@ + import Navbar from "@/components/navbar"; import { useAuth } from "@/hooks/auth"; import { Toaster } from "sonner"; + import { GetMapReportNameQuery, GetMapReportNameQueryVariables } from "@/__generated__/graphql"; import { Metadata } from "next"; import { getClient } from "@/services/apollo-client"; @@ -18,10 +20,18 @@ export default async function Layout({ const user = await useAuth(); const isLoggedIn = Boolean(user); + + + + return (
- -
+ +
{children}
diff --git a/nextjs/src/app/reports/[id]/page.tsx b/nextjs/src/app/reports/[id]/page.tsx index 1fa787cd9..ec639a054 100644 --- a/nextjs/src/app/reports/[id]/page.tsx +++ b/nextjs/src/app/reports/[id]/page.tsx @@ -18,7 +18,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { BarChart3, Layers, MoreVertical, RefreshCcw, Trash } from "lucide-react" +import { ArrowLeft, BarChart3, Layers, MoreVertical, RefreshCcw, Trash } from "lucide-react" import { Toggle } from "@/components/ui/toggle" import DataConfigPanel from "@/components/dataConfig"; import { FetchResult, gql, useApolloClient, useQuery } from "@apollo/client"; @@ -49,6 +49,8 @@ import { MapProvider } from "react-map-gl"; import { twMerge } from "tailwind-merge"; import { merge } from 'lodash' import { currentOrganisationIdAtom } from "@/data/organisation"; +import Link from "next/link"; +import { MappedIcon } from "@/components/navbar"; type Params = { id: string @@ -57,7 +59,7 @@ type Params = { export default function Page({ params: { id } }: { params: Params }) { const client = useApolloClient(); const router = useRouter(); - + const report = useQuery(GET_MAP_REPORT, { variables: { id }, }); @@ -79,7 +81,7 @@ export default function Page({ params: { id } }: { params: Params }) { return ( - ) - function refreshReportDataQueries () { + function refreshReportDataQueries() { toastPromise( client.refetchQueries({ include: [ @@ -115,7 +117,7 @@ export default function Page({ params: { id } }: { params: Params }) { ) } - function updateMutation (input: MapReportInput) { + function updateMutation(input: MapReportInput) { const update = client.mutate({ mutation: UPDATE_MAP_REPORT, variables: { @@ -146,7 +148,7 @@ export default function Page({ params: { id } }: { params: Params }) { }); } - function del () { + function del() { const deleteMutation = client.mutate({ mutation: DELETE_MAP_REPORT, variables: { @@ -227,78 +229,80 @@ function ReportPage() { return ( <> -
-
- -
- {/* Layer card */} - +
+ +
+ {/* Layer card */} {report?.data?.mapReport && isConstituencyPanelOpen && ( - -
-
- {toggles.map(({ icon: Icon, label, enabled, toggle }) => ( -
-
- - {label} -
-
- ))} -
- +
+ {report?.data?.mapReport && isNavigatorPanelOpen && ( + + )}
- {/* Layer card */} - {report?.data?.mapReport && isConstituencyPanelOpen && ( - - )}
setDeleteOpen(false)}> diff --git a/nextjs/src/components/ConstituenciesDropdown.tsx b/nextjs/src/components/ConstituenciesDropdown.tsx index ca8e90b61..115b913f5 100644 --- a/nextjs/src/components/ConstituenciesDropdown.tsx +++ b/nextjs/src/components/ConstituenciesDropdown.tsx @@ -55,11 +55,11 @@ export default function ConstituenciesDropdown({ variant="outline" role="combobox" aria-expanded={open} - className=" justify-between bg-meepGray-700" + className=" justify-between bg-meepGray-600" > {value ? constituencies.find((constituency) => constituency.gss === value)?.gssArea.name - : "Select constituency..."} + : "Search constituency..."} diff --git a/nextjs/src/components/MembersList.tsx b/nextjs/src/components/MembersList.tsx index 351e3a465..4aa24f2ac 100644 --- a/nextjs/src/components/MembersList.tsx +++ b/nextjs/src/components/MembersList.tsx @@ -1,12 +1,21 @@ import { useQuery, gql } from '@apollo/client'; import { ReportContext } from "@/app/reports/[id]/context"; -import { useContext } from "react"; +import { useContext, useState, useEffect } from "react"; +import { useMap } from 'react-map-gl'; import { - MapReportLayerAnalyticsQuery, - MapReportLayerAnalyticsQueryVariables, -} from "@/__generated__/graphql"; + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Input } from './ui/input'; +import { Search } from 'lucide-react'; +// GraphQL queries export const MAP_REPORT_LAYER_ANALYTICS = gql` query MapReportLayerAnalytics($reportID: ID!) { mapReport(pk: $reportID) { @@ -23,35 +32,111 @@ export const MAP_REPORT_LAYER_ANALYTICS = gql` } } } -` - +`; +export const MEMBER_DATA = gql` + query genericDataByExternalDataSource($externalDataSourceId: String!) { + genericDataByExternalDataSource(externalDataSourceId: $externalDataSourceId) { + id + fullName + email + postcodeData { + latitude + longitude + postcode + } + } + } +`; const MembersList = () => { const { id } = useContext(ReportContext); // Assuming ReportContext provides `id` - - const { loading, error, data } = useQuery(MAP_REPORT_LAYER_ANALYTICS, { + const [sourceId, setSourceId] = useState(null); + const [searchQuery, setSearchQuery] = useState(""); // State for search query + const [selectedMember, setSelectedMember] = useState(null); // State for selected member + + const { current: map } = useMap(); // Assuming you have a Mapbox instance using react-map-gl + + // First query to get source.id + const { loading: loadingLayers, error: errorLayers, data: dataLayers } = useQuery(MAP_REPORT_LAYER_ANALYTICS, { variables: { reportID: id, }, + onCompleted: (data) => { + if (data?.mapReport?.layers?.length > 0) { + // Set the sourceId from the first layer as an example + setSourceId(String(data.mapReport.layers[0].source.id)); + } + }, }); - if (loading) return

Loading...

; - if (error) return

Error: {error.message}

; + // Second query to get member data using sourceId + const { loading: loadingMembers, error: errorMembers, data: dataMembers } = useQuery(MEMBER_DATA, { + variables: { + externalDataSourceId: sourceId ?? "", // Provide a default empty string if sourceId is null + }, + skip: !sourceId, // Skip the query until sourceId is available + }); + + useEffect(() => { + if (selectedMember && map) { + map.flyTo({ + center: [selectedMember.postcodeData.longitude, selectedMember.postcodeData.latitude], + zoom: 12, + essential: true // This animation is considered essential with respect to prefers-reduced-motion + }); + } + }, [selectedMember, map]); + + if (loadingLayers || (sourceId && loadingMembers)) return

Loading...

; + if (errorLayers) return

Error: {errorLayers.message}

; + if (errorMembers) return

Error: {errorMembers.message}

; + + // Filter members based on search query + const filteredMembers = dataMembers?.genericDataByExternalDataSource?.filter((member: any) => + member.fullName.toLowerCase().includes(searchQuery.toLowerCase()) + ); return ( -
-

Data

- {data.mapReport.layers.map((layer, index) => ( -
-

Layer ID: {layer.id}

-

Layer Name: {layer.name}

-

Source ID: {layer.source.id}

-

Organisation Name: {layer.source.organisation.name}

-
- ))} +
+
+

Members in this map

+ setSearchQuery(e.target.value)} + className='w-1/3 bg-meepGray-600 text-meepGray-200' + /> +
+ + + + + Name + Email + Postcode + + + + {filteredMembers?.map((member: any) => ( + { + console.log('Selected member:', member); + setSelectedMember(member); + }} + > + {member.fullName} + {member.email} + {member.postcodeData?.postcode} + + + ))} + +
); }; -export default MembersList; \ No newline at end of file +export default MembersList; diff --git a/nextjs/src/components/TopConstituencies.tsx b/nextjs/src/components/TopConstituencies.tsx index 38108dd5a..47e907850 100644 --- a/nextjs/src/components/TopConstituencies.tsx +++ b/nextjs/src/components/TopConstituencies.tsx @@ -10,6 +10,8 @@ import { LoadingIcon } from "./ui/loadingIcon" import { useLoadedMap } from "@/lib/map" import { constituencyPanelTabAtom } from "@/app/reports/[id]/ConstituenciesPanel" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" +import { ScrollArea } from "@/components/ui/scroll-area" + import { twMerge } from "tailwind-merge" @@ -71,7 +73,7 @@ export function TopConstituencies() { return ( // List of them here -
+
@@ -83,7 +85,7 @@ export function TopConstituencies() {
+ {constituencies?.map((constituency) => (
-

{constituency.name}

+
+

{constituency.name}

{!!constituency.mp?.name && displayOptions.showMPs && (
)} - {!!constituency.lastElection?.stats && displayOptions.showLastElectionData && ( + {/* {!!constituency.lastElection?.stats && displayOptions.showLastElectionData && (

@@ -175,12 +180,12 @@ export function ConstituencySummaryCard({ count, constituency }: {

- )} + )} */}
diff --git a/nextjs/src/components/navigator/Navigator-Tabs.tsx b/nextjs/src/components/navigator/Navigator-Tabs.tsx new file mode 100644 index 000000000..6676bc661 --- /dev/null +++ b/nextjs/src/components/navigator/Navigator-Tabs.tsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/nextjs/src/components/navigator/Navigator.tsx b/nextjs/src/components/navigator/Navigator.tsx new file mode 100644 index 000000000..13dfebd15 --- /dev/null +++ b/nextjs/src/components/navigator/Navigator.tsx @@ -0,0 +1,25 @@ +import React from 'react' + +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/navigator/Navigator-Tabs" +import { LocateFixed, User } from 'lucide-react' +import { ConstituenciesPanel } from '@/app/reports/[id]/ConstituenciesPanel' +import MembersList from '../MembersList' + + +export default function Navigator() { + return ( +
+ + +

Navigator

+ Members + Constituencies +
+ + +
+ + +
+ ) +} diff --git a/nextjs/src/components/report/AddMapLayerButton.tsx b/nextjs/src/components/report/AddMapLayerButton.tsx index 15d2d4aaf..2f488df0b 100644 --- a/nextjs/src/components/report/AddMapLayerButton.tsx +++ b/nextjs/src/components/report/AddMapLayerButton.tsx @@ -58,7 +58,7 @@ export function AddMapLayerButton({ addLayer, filter }: { addLayer(layer: Source setOpen(o)}> diff --git a/nextjs/src/components/report/ReportMap.tsx b/nextjs/src/components/report/ReportMap.tsx index 3557e288f..7a14158de 100644 --- a/nextjs/src/components/report/ReportMap.tsx +++ b/nextjs/src/components/report/ReportMap.tsx @@ -291,6 +291,7 @@ export function ReportMap () { } return { url }; }} + // padding={{ top: 0, right: 0, bottom: 0, left: 0 }} > {mapbox.loaded && ( <> @@ -352,6 +353,8 @@ export function ReportMap () { const SOURCE_LABEL = `${tileset.name}_SOURCE_LABEL`; const SOURCE_POINTS = `${tileset.name}_SOURCE_POINTS`; + + return ( diff --git a/nextjs/src/components/reportsConstituencyItem.tsx b/nextjs/src/components/reportsConstituencyItem.tsx index ba61ce43c..f4d0d67ee 100644 --- a/nextjs/src/components/reportsConstituencyItem.tsx +++ b/nextjs/src/components/reportsConstituencyItem.tsx @@ -55,7 +55,7 @@ export const ConstituencyElectionDeepDive = ({ gss, analyticalAreaType = Analyti .filter(l => !!l.source.importedDataCountForConstituency?.count) return ( -
+

{data.constituency.name}

{data.constituency.mp && displayOptions.showMPs && (
diff --git a/nextjs/src/components/ui/table.tsx b/nextjs/src/components/ui/table.tsx index 5cedd50a7..84455ac1c 100644 --- a/nextjs/src/components/ui/table.tsx +++ b/nextjs/src/components/ui/table.tsx @@ -9,7 +9,7 @@ const Table = React.forwardRef<
@@ -20,7 +20,7 @@ const TableHeader = React.forwardRef< HTMLTableSectionElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( - + )); TableHeader.displayName = "TableHeader"; @@ -73,7 +73,7 @@ const TableHead = React.forwardRef<
| null | undefined; From 3a6e0e14d24b65af319b6e2890c3ed6dec6ef4f9 Mon Sep 17 00:00:00 2001 From: Joaquim d'Souza Date: Tue, 18 Jun 2024 21:23:56 +0200 Subject: [PATCH 08/20] fix: move selectedConstituencyAtom to parent page to fix hot reload crash --- nextjs/src/app/reports/MapNavbar.tsx | 3 ++- nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx | 2 +- nextjs/src/app/reports/[id]/page.tsx | 4 +++- nextjs/src/components/TopConstituencies.tsx | 3 ++- nextjs/src/components/report/ReportMap.tsx | 3 +-- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/nextjs/src/app/reports/MapNavbar.tsx b/nextjs/src/app/reports/MapNavbar.tsx index 06f006c52..ffceb145b 100644 --- a/nextjs/src/app/reports/MapNavbar.tsx +++ b/nextjs/src/app/reports/MapNavbar.tsx @@ -5,7 +5,8 @@ import Link from 'next/link' import { MappedIcon } from '@/components/navbar' import { Provider as JotaiProvider, atom, useAtom, useAtomValue } from "jotai"; import { MAP_REPORT_FRAGMENT, isConstituencyPanelOpenAtom, isDataConfigOpenAtom } from "@/lib/map"; -import { MAP_REPORT_LAYER_ANALYTICS, ReportMap, selectedConstituencyAtom } from "@/components/report/ReportMap"; +import { MAP_REPORT_LAYER_ANALYTICS, ReportMap } from "@/components/report/ReportMap"; +import { selectedConstituencyAtom } from './[id]/page'; import { BarChart3, Layers, MoreVertical, RefreshCcw, Trash } from "lucide-react" import { twMerge } from "tailwind-merge"; diff --git a/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx b/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx index 243f631fa..d4960ea6e 100644 --- a/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx +++ b/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx @@ -1,4 +1,3 @@ -import { selectedConstituencyAtom } from "@/components/report/ReportMap" import { atom, useAtom } from "jotai" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { MoreVertical } from "lucide-react" @@ -22,6 +21,7 @@ import { useEffect, useRef, useState } from "react"; import { useReportContext } from "./context"; import MembersList from "@/components/MembersList" import ConsSettings from "@/components/ConsSettings" +import { selectedConstituencyAtom } from "./page" export const constituencyPanelTabAtom = atom("list") diff --git a/nextjs/src/app/reports/[id]/page.tsx b/nextjs/src/app/reports/[id]/page.tsx index d790604dc..83848c9ee 100644 --- a/nextjs/src/app/reports/[id]/page.tsx +++ b/nextjs/src/app/reports/[id]/page.tsx @@ -48,7 +48,7 @@ import { Button, buttonVariants } from "@/components/ui/button"; import { useRouter } from "next/navigation"; import spaceCase from 'to-space-case' import { toastPromise } from "@/lib/toast"; -import { MAP_REPORT_LAYER_ANALYTICS, ReportMap, selectedConstituencyAtom } from "@/components/report/ReportMap"; +import { MAP_REPORT_LAYER_ANALYTICS, ReportMap } from "@/components/report/ReportMap"; import { MAP_REPORT_FRAGMENT, isConstituencyPanelOpenAtom, isDataConfigOpenAtom, isNavigatorPanelOpenAtom } from "@/lib/map"; import { DisplayOptionsType, ReportContext, defaultDisplayOptions } from "./context"; import { LoadingIcon } from "@/components/ui/loadingIcon"; @@ -67,6 +67,8 @@ type Params = { id: string } +export const selectedConstituencyAtom = atom(null) + export default function Page({ params: { id } }: { params: Params }) { const client = useApolloClient(); const router = useRouter(); diff --git a/nextjs/src/components/TopConstituencies.tsx b/nextjs/src/components/TopConstituencies.tsx index 47e907850..f27ef5fc8 100644 --- a/nextjs/src/components/TopConstituencies.tsx +++ b/nextjs/src/components/TopConstituencies.tsx @@ -5,7 +5,7 @@ import { useContext, useState } from "react" import { MemberElectoralInsights, Person } from "./reportsConstituencyItem" import { getYear } from "date-fns" import { useAtom } from "jotai" -import { MAX_CONSTITUENCY_ZOOM, selectedConstituencyAtom } from "./report/ReportMap" +import { MAX_CONSTITUENCY_ZOOM } from "./report/ReportMap" import { LoadingIcon } from "./ui/loadingIcon" import { useLoadedMap } from "@/lib/map" import { constituencyPanelTabAtom } from "@/app/reports/[id]/ConstituenciesPanel" @@ -32,6 +32,7 @@ import { PopoverTrigger, } from "@/components/ui/popover" import ConstituenciesDropdown from "./ConstituenciesDropdown" +import { selectedConstituencyAtom } from "@/app/reports/[id]/page" export function TopConstituencies() { const sortOptions = { diff --git a/nextjs/src/components/report/ReportMap.tsx b/nextjs/src/components/report/ReportMap.tsx index 7a14158de..5c94babb8 100644 --- a/nextjs/src/components/report/ReportMap.tsx +++ b/nextjs/src/components/report/ReportMap.tsx @@ -28,6 +28,7 @@ import { z } from "zod"; import { layerColour, useLoadedMap, isConstituencyPanelOpenAtom, MAP_REPORT_LAYERS_SUMMARY, layerIdColour, useMapIcons, PlaceholderLayer } from "@/lib/map"; import { constituencyPanelTabAtom } from "@/app/reports/[id]/ConstituenciesPanel"; import { authenticationHeaders } from "@/lib/auth"; +import { selectedConstituencyAtom } from "@/app/reports/[id]/page"; const MAX_REGION_ZOOM = 8 export const MAX_CONSTITUENCY_ZOOM = 10 @@ -41,8 +42,6 @@ const viewStateAtom = atom>({ const selectedSourceMarkerAtom = atom(null) -export const selectedConstituencyAtom = atom(null) - export function ReportMap () { const { id, displayOptions } = useContext(ReportContext) const analytics = useQuery(MAP_REPORT_LAYER_ANALYTICS, { From 9a58ddaec4492ad41e6c767cf1e9a7b8236f620b Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 19 Jun 2024 17:19:32 +0100 Subject: [PATCH 09/20] Commit to trigger rebuild --- nextjs/src/data/puck/config/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nextjs/src/data/puck/config/index.tsx b/nextjs/src/data/puck/config/index.tsx index f7fd0cd08..b01cae7db 100644 --- a/nextjs/src/data/puck/config/index.tsx +++ b/nextjs/src/data/puck/config/index.tsx @@ -59,9 +59,7 @@ export const conf: UserConfig = { defaultProps: { title: "My Page", }, - render: (props) => , - // TODO: fields - // fields: {} + render: (props) => }, categories: { specialLayout: { From dd7617232a7de4fc3db396c335b5736b5899ce64 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 19 Jun 2024 17:42:46 +0100 Subject: [PATCH 10/20] Fix build issue due to nextjs export vars --- nextjs/src/app/reports/MapNavbar.tsx | 1 - nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx | 3 +-- nextjs/src/app/reports/[id]/context.tsx | 5 ++++- nextjs/src/app/reports/[id]/page.tsx | 2 -- nextjs/src/components/TopConstituencies.tsx | 2 +- nextjs/src/components/report/ReportMap.tsx | 3 +-- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/nextjs/src/app/reports/MapNavbar.tsx b/nextjs/src/app/reports/MapNavbar.tsx index ffceb145b..cb32df4cd 100644 --- a/nextjs/src/app/reports/MapNavbar.tsx +++ b/nextjs/src/app/reports/MapNavbar.tsx @@ -6,7 +6,6 @@ import { MappedIcon } from '@/components/navbar' import { Provider as JotaiProvider, atom, useAtom, useAtomValue } from "jotai"; import { MAP_REPORT_FRAGMENT, isConstituencyPanelOpenAtom, isDataConfigOpenAtom } from "@/lib/map"; import { MAP_REPORT_LAYER_ANALYTICS, ReportMap } from "@/components/report/ReportMap"; -import { selectedConstituencyAtom } from './[id]/page'; import { BarChart3, Layers, MoreVertical, RefreshCcw, Trash } from "lucide-react" import { twMerge } from "tailwind-merge"; diff --git a/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx b/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx index d4960ea6e..c6f868043 100644 --- a/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx +++ b/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx @@ -18,10 +18,9 @@ import { import { ConstituencyElectionDeepDive } from "@/components/reportsConstituencyItem"; import { TopConstituencies } from "@/components/TopConstituencies"; import { useEffect, useRef, useState } from "react"; -import { useReportContext } from "./context"; +import { selectedConstituencyAtom, useReportContext } from "@/app/reports/[id]/context"; import MembersList from "@/components/MembersList" import ConsSettings from "@/components/ConsSettings" -import { selectedConstituencyAtom } from "./page" export const constituencyPanelTabAtom = atom("list") diff --git a/nextjs/src/app/reports/[id]/context.tsx b/nextjs/src/app/reports/[id]/context.tsx index 19c2579cc..1b8477e75 100644 --- a/nextjs/src/app/reports/[id]/context.tsx +++ b/nextjs/src/app/reports/[id]/context.tsx @@ -2,6 +2,7 @@ import { AnalyticalAreaType, Exact, GetMapReportQuery, MapReportInput } from "@/__generated__/graphql"; import { QueryResult } from "@apollo/client"; +import { atom } from "jotai"; import { createContext, useContext, useState } from "react"; export const defaultDisplayOptions = { @@ -41,4 +42,6 @@ export const useReportContext = () => { }; return { ...context, updateDisplayOptions }; -}; \ No newline at end of file +}; + +export const selectedConstituencyAtom = atom(null) \ No newline at end of file diff --git a/nextjs/src/app/reports/[id]/page.tsx b/nextjs/src/app/reports/[id]/page.tsx index 83848c9ee..8e321d8d1 100644 --- a/nextjs/src/app/reports/[id]/page.tsx +++ b/nextjs/src/app/reports/[id]/page.tsx @@ -67,8 +67,6 @@ type Params = { id: string } -export const selectedConstituencyAtom = atom(null) - export default function Page({ params: { id } }: { params: Params }) { const client = useApolloClient(); const router = useRouter(); diff --git a/nextjs/src/components/TopConstituencies.tsx b/nextjs/src/components/TopConstituencies.tsx index f27ef5fc8..ba1d24c7c 100644 --- a/nextjs/src/components/TopConstituencies.tsx +++ b/nextjs/src/components/TopConstituencies.tsx @@ -32,7 +32,7 @@ import { PopoverTrigger, } from "@/components/ui/popover" import ConstituenciesDropdown from "./ConstituenciesDropdown" -import { selectedConstituencyAtom } from "@/app/reports/[id]/page" +import { selectedConstituencyAtom } from "@/app/reports/[id]/context"; export function TopConstituencies() { const sortOptions = { diff --git a/nextjs/src/components/report/ReportMap.tsx b/nextjs/src/components/report/ReportMap.tsx index 5c94babb8..431f7e59d 100644 --- a/nextjs/src/components/report/ReportMap.tsx +++ b/nextjs/src/components/report/ReportMap.tsx @@ -17,7 +17,7 @@ import { import { Fragment, useContext, useEffect, useState } from "react"; import Map, { Layer, Source, LayerProps, Popup, ViewState, MapboxGeoJSONFeature } from "react-map-gl"; import { gql, useFragment, useQuery } from "@apollo/client"; -import { ReportContext } from "@/app/reports/[id]/context"; +import { ReportContext, selectedConstituencyAtom } from "@/app/reports/[id]/context"; import { LoadingIcon } from "@/components/ui/loadingIcon"; import { scaleLinear, scaleSequential } from 'd3-scale' import { interpolateInferno } from 'd3-scale-chromatic' @@ -28,7 +28,6 @@ import { z } from "zod"; import { layerColour, useLoadedMap, isConstituencyPanelOpenAtom, MAP_REPORT_LAYERS_SUMMARY, layerIdColour, useMapIcons, PlaceholderLayer } from "@/lib/map"; import { constituencyPanelTabAtom } from "@/app/reports/[id]/ConstituenciesPanel"; import { authenticationHeaders } from "@/lib/auth"; -import { selectedConstituencyAtom } from "@/app/reports/[id]/page"; const MAX_REGION_ZOOM = 8 export const MAX_CONSTITUENCY_ZOOM = 10 From aeda0c160d4bb703014029c2b819a2a965c77a7e Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 19 Jun 2024 17:44:39 +0100 Subject: [PATCH 11/20] Fix another type error in the markup --- nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx b/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx index c6f868043..9fe1d60b9 100644 --- a/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx +++ b/nextjs/src/app/reports/[id]/ConstituenciesPanel.tsx @@ -60,7 +60,9 @@ export function ConstituenciesPanel() { - + {!!selectedConstituencyId && ( + + )} ) From baca6bfdf2cb3a9e60a7af1b9e1654c75af01360 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 19 Jun 2024 17:47:26 +0100 Subject: [PATCH 12/20] Fix missing import --- nextjs/src/app/reports/[id]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextjs/src/app/reports/[id]/page.tsx b/nextjs/src/app/reports/[id]/page.tsx index 8e321d8d1..916b07b77 100644 --- a/nextjs/src/app/reports/[id]/page.tsx +++ b/nextjs/src/app/reports/[id]/page.tsx @@ -50,7 +50,7 @@ import spaceCase from 'to-space-case' import { toastPromise } from "@/lib/toast"; import { MAP_REPORT_LAYER_ANALYTICS, ReportMap } from "@/components/report/ReportMap"; import { MAP_REPORT_FRAGMENT, isConstituencyPanelOpenAtom, isDataConfigOpenAtom, isNavigatorPanelOpenAtom } from "@/lib/map"; -import { DisplayOptionsType, ReportContext, defaultDisplayOptions } from "./context"; +import { DisplayOptionsType, ReportContext, defaultDisplayOptions, selectedConstituencyAtom } from "./context"; import { LoadingIcon } from "@/components/ui/loadingIcon"; import { contentEditableMutation } from "@/lib/html"; import { Provider as JotaiProvider, atom, useAtom, useAtomValue } from "jotai"; From 6916b8c3580a3e3dc727a53734e801c92012a52d Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 19 Jun 2024 22:41:29 +0100 Subject: [PATCH 13/20] Fix build --- nextjs/src/components/ConsSettings.tsx | 2 +- .../src/components/ConstituenciesDropdown.tsx | 10 +++++--- nextjs/src/components/TopConstituencies.tsx | 25 ++++++++++++------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/nextjs/src/components/ConsSettings.tsx b/nextjs/src/components/ConsSettings.tsx index 6af5609e1..8261e9ad4 100644 --- a/nextjs/src/components/ConsSettings.tsx +++ b/nextjs/src/components/ConsSettings.tsx @@ -15,7 +15,7 @@ export default function ConsSettings() { setDisplayOptions({ showMPs: !displayOptions.showMPs }); }; - const handleAnalyticalAreaTypeChange = (value) => { + const handleAnalyticalAreaTypeChange = (value: AnalyticalAreaType) => { setDisplayOptions({ analyticalAreaType: value }); }; diff --git a/nextjs/src/components/ConstituenciesDropdown.tsx b/nextjs/src/components/ConstituenciesDropdown.tsx index 115b913f5..93fcf3923 100644 --- a/nextjs/src/components/ConstituenciesDropdown.tsx +++ b/nextjs/src/components/ConstituenciesDropdown.tsx @@ -18,10 +18,11 @@ import { PopoverTrigger, } from "@/components/ui/popover" -interface Constituency { +export interface SelectableConstituency { gss: string count: number gssArea: { + id: string fitBounds: number[] name: string } @@ -31,7 +32,7 @@ import { MAX_CONSTITUENCY_ZOOM } from "./report/ReportMap" interface ConstituenciesDropdownProps { - constituencies: Constituency[] + constituencies: SelectableConstituency[] setSelectedConstituency: (gss: string) => void setTab: (tab: string) => void map: any // Replace with the correct type if available @@ -47,6 +48,7 @@ export default function ConstituenciesDropdown({ }: ConstituenciesDropdownProps) { const [open, setOpen] = React.useState(false) const [value, setValue] = React.useState("") + const selectedConstituency = constituencies.find((constituency) => constituency.gss === value) return ( @@ -57,8 +59,8 @@ export default function ConstituenciesDropdown({ aria-expanded={open} className=" justify-between bg-meepGray-600" > - {value - ? constituencies.find((constituency) => constituency.gss === value)?.gssArea.name + {selectedConstituency + ? selectedConstituency.gssArea.name : "Search constituency..."} diff --git a/nextjs/src/components/TopConstituencies.tsx b/nextjs/src/components/TopConstituencies.tsx index ba1d24c7c..2d2071969 100644 --- a/nextjs/src/components/TopConstituencies.tsx +++ b/nextjs/src/components/TopConstituencies.tsx @@ -31,7 +31,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover" -import ConstituenciesDropdown from "./ConstituenciesDropdown" +import ConstituenciesDropdown, { SelectableConstituency } from "./ConstituenciesDropdown" import { selectedConstituencyAtom } from "@/app/reports/[id]/context"; export function TopConstituencies() { @@ -54,8 +54,14 @@ export function TopConstituencies() { const [tab, setTab] = useAtom(constituencyPanelTabAtom) const map = useLoadedMap() + type RequiredConstituencyType = ConstituencyStatsOverviewQuery["mapReport"]["importedDataCountByConstituency"][number] & SelectableConstituency + + function isSelectable(constituency: ConstituencyStatsOverviewQuery["mapReport"]["importedDataCountByConstituency"][number]): constituency is RequiredConstituencyType { + return !!constituency.gssArea && !!constituency.gss + } + const constituencies = constituencyAnalytics.data?.mapReport.importedDataCountByConstituency - .filter(constituency => constituency.gssArea) + .filter(isSelectable) .sort((a, b) => { if (sortBy === "totalCount") { return b.count - a.count @@ -78,17 +84,18 @@ export function TopConstituencies() {
- + {!!constituencies &&( + + )} setSortBy(value as keyof typeof sortOptions)} - > -
- - Sort by: - - - + {!!constituencies && ( + + )} + -
+ + +
+ +
+
+ + + {constituencies?.map((constituency) => ( +
{ + setSelectedConstituency(constituency.gss!) + map.loadedMap?.fitBounds(constituency.gssArea?.fitBounds, { + maxZoom: MAX_CONSTITUENCY_ZOOM - 0.1 + }) + }} + className='cursor-pointer group hover:bg-meepGray-600 border-b border-meepGray-700' + > + +
+ ))}
- - {constituencies?.map((constituency) => ( -
{ - setSelectedConstituency(constituency.gss!) - map.loadedMap?.fitBounds(constituency.gssArea?.fitBounds, { - maxZoom: MAX_CONSTITUENCY_ZOOM - 0.1 - }) - }} - className='cursor-pointer text-lg border border-meepGray-600 group hover:bg-meepGray-600 rounded' - > - -
- ))} ) } @@ -149,8 +133,13 @@ export function ConstituencySummaryCard({ count, constituency }: { return (
-

{constituency.name}

- {!!constituency.mp?.name && displayOptions.showMPs && ( +

{constituency.name}

+ + {/* {!!constituency.mp?.name && displayOptions.showMPs && (
- )} + )} */} {/* {!!constituency.lastElection?.stats && displayOptions.showLastElectionData && (
@@ -189,13 +178,7 @@ export function ConstituencySummaryCard({ count, constituency }: {
)} */} -
- -
+
) } diff --git a/nextjs/src/components/reportsConstituencyItem.tsx b/nextjs/src/components/SelectedConstituency.tsx similarity index 93% rename from nextjs/src/components/reportsConstituencyItem.tsx rename to nextjs/src/components/SelectedConstituency.tsx index cb885bbec..51158e96b 100644 --- a/nextjs/src/components/reportsConstituencyItem.tsx +++ b/nextjs/src/components/SelectedConstituency.tsx @@ -10,6 +10,16 @@ import { CardHeader, CardTitle, } from "@/components/ui/card" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import ConsSettings from "@/components/ConsSettings" +import { MoreVertical } from "lucide-react" import { gql, useQuery } from "@apollo/client"; import { getYear } from "date-fns"; import { format } from 'd3-format' @@ -39,7 +49,7 @@ type DeepNullable = { [K in keyof T]: DeepNullable | null; }; -export const ConstituencyElectionDeepDive = ({ gss, analyticalAreaType = AnalyticalAreaType.ParliamentaryConstituency_2025 }: { gss: string, analyticalAreaType: AnalyticalAreaType }) => { +export const SelectedConstituency = ({ gss, analyticalAreaType = AnalyticalAreaType.ParliamentaryConstituency_2025 }: { gss: string, analyticalAreaType: AnalyticalAreaType }) => { const { id, displayOptions } = useContext(ReportContext) const { data, loading, error } = useQuery(CONSTITUENCY_DATA, { variables: { gss, reportID: id, analyticalAreaType }, @@ -56,8 +66,19 @@ export const ConstituencyElectionDeepDive = ({ gss, analyticalAreaType = Analyti .filter(l => !!l.source.importedDataCountForConstituency?.count) return ( -
-

{data.constituency.name}

+
+
+ + +

{data.constituency.name}

+ + + + + + +
+
{data.constituency.mp && displayOptions.showMPs && (
@@ -177,9 +198,9 @@ export function MemberElectoralInsights({ return ( <> -
- {format(",")(totalCount)} {pluralize("member", totalCount)} in this report -
+
+ {format(",")(totalCount)} {pluralize("member", totalCount)} in this report +
{/*
diff --git a/nextjs/src/components/navigator/Navigator-Tabs.tsx b/nextjs/src/components/navigator/Navigator-Tabs.tsx index 6676bc661..2757dba83 100644 --- a/nextjs/src/components/navigator/Navigator-Tabs.tsx +++ b/nextjs/src/components/navigator/Navigator-Tabs.tsx @@ -29,7 +29,7 @@ const TabsTrigger = React.forwardRef< - - -

Navigator

- Members - Constituencies +
+ + + + Members + Geography - - + + {!!selectedConstituencyId && ( + + )} + + + + {!!selectedConstituencyId && ( + + )} +