diff --git a/src/app/(private)/(dashboards)/data-sources/new/schema.ts b/src/app/(private)/(dashboards)/data-sources/new/schema.ts index b3747d24..112f9d3f 100644 --- a/src/app/(private)/(dashboards)/data-sources/new/schema.ts +++ b/src/app/(private)/(dashboards)/data-sources/new/schema.ts @@ -8,10 +8,12 @@ import { zetkinConfigSchema, } from "@/models/DataSource"; -export const newCSVConfigSchema = csvConfigSchema.extend({ - file: z.instanceof(File), - filename: z.string().min(1, "Filename is required"), -}).omit({ url: true }); +export const newCSVConfigSchema = csvConfigSchema + .extend({ + file: z.instanceof(File), + filename: z.string().min(1, "Filename is required"), + }) + .omit({ url: true }); export type NewCSVConfig = z.infer; diff --git a/src/app/(private)/map/[id]/components/BoundaryHoverInfo/AreasList.tsx b/src/app/(private)/map/[id]/components/BoundaryHoverInfo/AreasList.tsx index d81739fe..199bbf61 100644 --- a/src/app/(private)/map/[id]/components/BoundaryHoverInfo/AreasList.tsx +++ b/src/app/(private)/map/[id]/components/BoundaryHoverInfo/AreasList.tsx @@ -52,7 +52,7 @@ export function AreasList({ >
{area.name} diff --git a/src/app/(private)/map/[id]/components/InspectorPanel/InspectorDataTab.tsx b/src/app/(private)/map/[id]/components/InspectorPanel/InspectorDataTab.tsx index b6d43b0c..cb51f56a 100644 --- a/src/app/(private)/map/[id]/components/InspectorPanel/InspectorDataTab.tsx +++ b/src/app/(private)/map/[id]/components/InspectorPanel/InspectorDataTab.tsx @@ -105,6 +105,9 @@ export default function InspectorDataTab({ ); const isBoundary = type === LayerType.Boundary; + const hasChoroplethVisualisation = Boolean( + choroplethDataSource?.id && viewConfig.areaDataColumn, + ); const bivariateBucket = areaStats?.secondary && typeof areaStat?.primary === "number" && @@ -146,7 +149,7 @@ export default function InspectorDataTab({ return (
- {isBoundary ? ( + {isBoundary && hasChoroplethVisualisation ? (

diff --git a/src/app/(private)/map/[id]/components/Legend/Legend.tsx b/src/app/(private)/map/[id]/components/Legend/Legend.tsx index 95718d62..a8d0a606 100644 --- a/src/app/(private)/map/[id]/components/Legend/Legend.tsx +++ b/src/app/(private)/map/[id]/components/Legend/Legend.tsx @@ -25,6 +25,7 @@ import { DialogHeader, DialogTitle, } from "@/shadcn/ui/dialog"; +import { Popover, PopoverContent, PopoverTrigger } from "@/shadcn/ui/popover"; import { Select, SelectContent, @@ -35,6 +36,10 @@ import { import { cn } from "@/shadcn/utils"; import { useColorScheme } from "../../colors"; import { useAreaStats } from "../../data"; +import { + DEFAULT_SECONDARY_BOUNDARY_STROKE_COLOR, + mapColorPalette, +} from "../../styles"; import BivariateLegend from "../BivariateLagend"; import { dataRecordsWillAggregate, @@ -65,6 +70,20 @@ export default function Legend({ null, ); const [bivariatePickerOpen, setBivariatePickerOpen] = useState(false); + const [secondaryBoundariesOpen, setSecondaryBoundariesOpen] = useState( + Boolean(viewConfig.secondaryAreaSetCode), + ); + + useEffect(() => { + if (viewConfig.secondaryAreaSetCode) { + setSecondaryBoundariesOpen(true); + } + }, [viewConfig.secondaryAreaSetCode]); + + const secondaryBoundaryStrokeColor = + viewConfig.secondaryBoundaryStrokeColor || + DEFAULT_SECONDARY_BOUNDARY_STROKE_COLOR; + const secondaryBoundarySwatches = mapColorPalette.map((c) => c.color); const areaStatsQuery = useAreaStats(); const areaStats = areaStatsQuery?.data; @@ -335,17 +354,6 @@ export default function Legend({ )}

)} - {canSelectSecondaryColumn && ( - - )}
)}
@@ -370,15 +378,32 @@ export default function Legend({
) : null} - {hasDataSource && onClearRequest && ( -
- + {(canSelectSecondaryColumn || (hasDataSource && onClearRequest)) && ( +
+
+ {hasDataSource && onClearRequest ? ( + + ) : ( + + )} + {canSelectSecondaryColumn && ( + + )} +
)} @@ -455,7 +480,7 @@ export default function Legend({ {/* Aggregation */} {canSelectAggregation && ( -
+

Aggregation

@@ -484,46 +509,151 @@ export default function Legend({ {/* Secondary boundaries */} {viewConfig.mapType !== MapType.Hex && ( -
-

- Secondary boundaries -

- { + const nextValue = + value === NULL_UUID ? null : (value as AreaSetCode); + updateViewConfig({ secondaryAreaSetCode: nextValue }); + if (!nextValue) { + setSecondaryBoundariesOpen(false); + } + }} + > + + + {viewConfig.secondaryAreaSetCode + ? AreaSetCodeLabels[viewConfig.secondaryAreaSetCode] + : "No secondary boundaries"} + + + + + No secondary boundaries + + {CHOROPLETH_AREA_SET_CODES.map((code) => ( + +
+ {AreaSetCodeLabels[code]} + +
+
+ ))} +
+ + + + + + + +

Stroke colour

+
+ {secondaryBoundarySwatches.map((c) => ( +
- - ))} - - + {viewConfig.secondaryBoundaryStrokeColor && ( + + )} +
+
+
+ )}
)} diff --git a/src/app/(private)/map/[id]/components/SecondaryBoundaries.tsx b/src/app/(private)/map/[id]/components/SecondaryBoundaries.tsx index da36ff2b..fc9e2fc4 100644 --- a/src/app/(private)/map/[id]/components/SecondaryBoundaries.tsx +++ b/src/app/(private)/map/[id]/components/SecondaryBoundaries.tsx @@ -1,5 +1,7 @@ import { Layer, Source } from "react-map-gl/mapbox"; +import { useMapViews } from "../hooks/useMapViews"; import { useSecondaryAreaSetConfig } from "../hooks/useSecondaryAreaSet"; +import { DEFAULT_SECONDARY_BOUNDARY_STROKE_COLOR } from "../styles"; import type { ChoroplethLayerConfig } from "./Choropleth/configs"; const SECONDARY_TOP_LAYER_ID = "secondary-top"; @@ -32,10 +34,14 @@ export default function SecondaryBoundaries() { } function Boundaries({ config }: { config: ChoroplethLayerConfig }) { + const { viewConfig } = useMapViews(); const { mapbox: { sourceId, layerId, featureCodeProperty }, } = config; const secondarySourceId = `${sourceId}-secondary`; + const strokeColor = + viewConfig.secondaryBoundaryStrokeColor || + DEFAULT_SECONDARY_BOUNDARY_STROKE_COLOR; return ( { setShowControls(!showControls); @@ -64,12 +61,8 @@ export default function ControlPanel() { className="flex-1 overflow-y-auto / flex flex-col" style={{ width: `${CONTROL_PANEL_WIDTH}px` }} > - {viewConfig.mapType !== MapType.Hex && ( - <> - - - - )} + +
diff --git a/src/app/(private)/map/[id]/components/controls/LayerControlWrapper.tsx b/src/app/(private)/map/[id]/components/controls/LayerControlWrapper.tsx index 19ca1777..5d202cd9 100644 --- a/src/app/(private)/map/[id]/components/controls/LayerControlWrapper.tsx +++ b/src/app/(private)/map/[id]/components/controls/LayerControlWrapper.tsx @@ -1,12 +1,17 @@ +import { cn } from "@/shadcn/utils"; import type { ReactNode } from "react"; export default function LayerControlWrapper({ children, + className, }: { children: ReactNode; + className?: string; }) { return ( -
+
{children}
); diff --git a/src/app/(private)/map/[id]/components/controls/MarkersControl/MarkersControl.tsx b/src/app/(private)/map/[id]/components/controls/MarkersControl/MarkersControl.tsx index 9d5dacc6..88d4b1f6 100644 --- a/src/app/(private)/map/[id]/components/controls/MarkersControl/MarkersControl.tsx +++ b/src/app/(private)/map/[id]/components/controls/MarkersControl/MarkersControl.tsx @@ -21,7 +21,9 @@ import { useMembersDataSource, } from "@/hooks/useDataSources"; import { DataSourceRecordType } from "@/models/DataSource"; +import { MapType } from "@/models/MapView"; import { LayerType } from "@/types"; +import { useMapViews } from "../../../hooks/useMapViews"; import { CollectionIcon } from "../../Icons"; import LayerControlWrapper from "../LayerControlWrapper"; import LayerHeader from "../LayerHeader"; @@ -30,6 +32,7 @@ import MarkersList from "./MarkersList"; export default function MarkersControl() { const router = useRouter(); const { mapConfig, updateMapConfig } = useMapConfig(); + const { viewConfig } = useMapViews(); const { data: folders = [] } = useFoldersQuery(); const { isMutating: isPlacedMarkersMutating } = usePlacedMarkerMutations(); const { insertFolder, isMutating: isFoldersMutating } = useFolderMutations(); @@ -39,6 +42,7 @@ export default function MarkersControl() { const markerDataSources = useMarkerDataSources() || []; const membersDataSource = useMembersDataSource(); const [expanded, setExpanded] = useState(true); + const isHex = viewConfig.mapType === MapType.Hex; const createFolder = () => { const newFolder = { @@ -182,7 +186,9 @@ export default function MarkersControl() { const loading = isFoldersMutating || isPlacedMarkersMutating; return ( - + { return folders.filter((f) => f.type === "turf"); @@ -77,7 +81,9 @@ export default function AreasControl() { ]; return ( - + Area colour

) : ( <> +
+

Layout

+
+ {mapTypes.map((type) => { + const isDefault = !viewConfig.mapType && type === MapType.Geo; + const isChecked = viewConfig.mapType === type || isDefault; + return ( + + ); + })} +
+
+ {showStyle && (

@@ -318,7 +347,7 @@ export default function VisualisationPanel({