Skip to content

Commit

Permalink
Persist user maps (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
nofurtherinformation authored Oct 21, 2024
1 parent 2421bd0 commit 401341f
Show file tree
Hide file tree
Showing 7 changed files with 538 additions and 256 deletions.
53 changes: 30 additions & 23 deletions app/src/app/components/sidebar/GerryDBViewSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import { useState } from "react";
import { Select } from "@radix-ui/themes";
import { getAvailableDistrictrMaps } from "../../utils/api/apiHandlers";
import { Flex, Select } from "@radix-ui/themes";
import { useMapStore } from "../../store/mapStore";
import { useQuery } from "@tanstack/react-query";
import { document } from "@/app/utils/api/mutations";
import { RecentMapsModal } from "./RecentMapsModal";

export function GerryDBViewSelector() {
const [limit, setLimit] = useState<number>(30);
const [offset, setOffset] = useState<number>(0);
const mapDocument = useMapStore((state) => state.mapDocument);
const mapViews = useMapStore((state) => state.mapViews);
const { isPending, isError, data, error } = mapViews || {};

const { isPending, isError, data, error } = useQuery({
queryKey: ["views", limit, offset],
queryFn: () => getAvailableDistrictrMaps(limit, offset),
});

const selectedView = data?.find(
(view) => view.gerrydb_table_name === mapDocument?.gerrydb_table,
(view) => view.gerrydb_table_name === mapDocument?.gerrydb_table
);

const handleValueChange = (value: string) => {
Expand All @@ -36,21 +32,32 @@ export function GerryDBViewSelector() {

if (isPending) return <div>Loading geographies... 🌎</div>;

if (isError) return <div>Error loading geographies: {error.message}</div>;
if (isError) return <div>Error loading geographies: {error?.message}</div>;

return (
<Select.Root size="3" onValueChange={handleValueChange} value={selectedView?.name}>
<Select.Trigger placeholder="Select a geography" />
<Select.Content>
<Select.Group>
<Select.Label>Districtr map options</Select.Label>
{data.map((view, index) => (
<Select.Item key={index} value={view.name}>
{view.name}
</Select.Item>
))}
</Select.Group>
</Select.Content>
</Select.Root>
<Flex direction={"row"} width="100%" gap="3" align="center">
<Select.Root
size="3"
onValueChange={handleValueChange}
value={selectedView?.name}
>
<Select.Trigger
placeholder="Select a geography"
style={{ flexGrow: 1 }}
className="mr-1"
/>
<Select.Content>
<Select.Group>
<Select.Label>Districtr map options</Select.Label>
{data?.map((view, index) => (
<Select.Item key={index} value={view.name}>
{view.name}
</Select.Item>
))}
</Select.Group>
</Select.Content>
</Select.Root>
<RecentMapsModal />
</Flex>
);
}
157 changes: 157 additions & 0 deletions app/src/app/components/sidebar/RecentMapsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { useMapStore } from "@/app/store/mapStore";
import React from "react";
import { Cross2Icon } from "@radix-ui/react-icons";
import {
Button,
Flex,
Text,
Table,
Dialog,
Box,
TextField,
IconButton,
} from "@radix-ui/themes";
import { usePathname, useSearchParams, useRouter } from "next/navigation";
import { DocumentObject } from "../../utils/api/apiHandlers";
type NamedDocumentObject = DocumentObject & { name?: string };
export const RecentMapsModal = () => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const mapDocument = useMapStore((store) => store.mapDocument);
const userMaps = useMapStore((store) => store.userMaps);
const upcertUserMap = useMapStore((store) => store.upcertUserMap);
const setMapDocument = useMapStore((store) => store.setMapDocument);
const [dialogOpen, setDialogOpen] = React.useState(false);

const handleMapDocument = (data: NamedDocumentObject) => {
setMapDocument(data);
const urlParams = new URLSearchParams(searchParams.toString());
urlParams.set("document_id", data.document_id);
router.push(pathname + "?" + urlParams.toString());
// close dialog
setDialogOpen(false);
};

if (!userMaps?.length) {
return null;
}

return (
<Dialog.Root open={dialogOpen} onOpenChange={setDialogOpen}>
<Dialog.Trigger>
<Button
variant="ghost"
size="3"
// weird negative margin happening.
style={{ margin: 0 }}
>
Recent
</Button>
</Dialog.Trigger>
<Dialog.Content className="max-w-[75vw]">
<Flex align="center" className="mb-4">
<Dialog.Title className="m-0 text-xl font-bold flex-1">
Recent Maps
</Dialog.Title>

<Dialog.Close
className="rounded-full size-[24px] hover:bg-red-100 p-1"
aria-label="Close"
>
<Cross2Icon />
</Dialog.Close>
</Flex>
<Table.Root size="3" variant="surface">
<Table.Header>
<Table.Row>
<Table.ColumnHeaderCell pl=".5rem">
Map Name
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell>Last Updated</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell>{/* load */}</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell>{/* delete */}</Table.ColumnHeaderCell>
</Table.Row>
</Table.Header>

<Table.Body>
{userMaps.map((userMap, i) => (
<RecentMapsRow
key={i}
active={mapDocument?.document_id === userMap.document_id}
onChange={(userMapData) =>
upcertUserMap({
userMapData,
userMapDocumentId: userMap.document_id,
})
}
data={userMap}
onSelect={handleMapDocument}
/>
))}
</Table.Body>
</Table.Root>
</Dialog.Content>
</Dialog.Root>
);
};

const RecentMapsRow: React.FC<{
data: NamedDocumentObject;
onSelect: (data: NamedDocumentObject) => void;
active: boolean;
onChange?: (data?: NamedDocumentObject) => void;
}> = ({ data, onSelect, active, onChange }) => {
const updatedDate = new Date(data.updated_at as string);
const formattedData = updatedDate.toLocaleDateString();
const name = data?.name || data.gerrydb_table;

const handleChangeName = (name?: string) => {
name?.length && onChange?.({ ...data, name });
};

return (
<Table.Row align="center" className={`${active ? "bg-yellow-100" : ""}`}>
<Table.Cell pl=".5rem">
{!!(active && onChange) ? (
<Box maxWidth="200px">
<TextField.Root
placeholder={name}
size="3"
value={name}
onChange={(e) => handleChangeName(e.target.value)}
></TextField.Root>
</Box>
) : (
<Text>{name}</Text>
)}
</Table.Cell>
<Table.Cell>
<Text>{formattedData}</Text>
</Table.Cell>
<Table.Cell py=".5rem">
{!active && (
<Button
onClick={() => onSelect(data)}
variant="outline"
className="box-content size-full rounded-xl hover:bg-blue-200 inline-flex transition-colors"
>
Load
</Button>
)}
</Table.Cell>
<Table.Cell py=".5rem">
{!active && (
<IconButton
onClick={() => onChange?.()}
variant="ghost"
color="ruby"
className="size-full"
>
<Cross2Icon />
</IconButton>
)}
</Table.Cell>
</Table.Row>
);
};
19 changes: 14 additions & 5 deletions app/src/app/store/mapEditSubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,21 @@ const zoneUpdates = ({
};
const debouncedZoneUpdate = debounce(zoneUpdates, 25);

type zoneSubState = [
MapStore['getMapRef'],
MapStore['zoneAssignments'],
MapStore['appLoadingState'],
MapStore['mapRenderingState']
]
export const getMapEditSubs = (useMapStore: typeof _useMapStore) => {
const sendZonesOnMapRefSub = useMapStore.subscribe(
(state) => [state.getMapRef, state.zoneAssignments],
() => {
const { getMapRef, zoneAssignments, appLoadingState } =
useMapStore.getState();
const sendZonesOnMapRefSub = useMapStore.subscribe<zoneSubState>(
(state) => [state.getMapRef, state.zoneAssignments, state.appLoadingState, state.mapRenderingState],
([getMapRef, zoneAssignments, appLoadingState, mapRenderingState], [ _prevMapRef, _prevZoneAssignments, prevAppLoadingState, prevMapRenderingState]) => {
const previousNotLoaded = [appLoadingState, mapRenderingState, prevAppLoadingState, prevMapRenderingState].some(state => state !== 'loaded')
if (!getMapRef() || previousNotLoaded) {
return
}
console.log("!!!SENDING UPDATES", appLoadingState, mapRenderingState, prevAppLoadingState, prevMapRenderingState)
debouncedZoneUpdate({ getMapRef, zoneAssignments, appLoadingState });
},
{ equalityFn: shallowCompareArray}
Expand Down
Loading

0 comments on commit 401341f

Please sign in to comment.