Skip to content

Commit

Permalink
feat: add data to rudimentary results grid
Browse files Browse the repository at this point in the history
  • Loading branch information
stdavis committed Nov 29, 2024
1 parent 824001a commit 7aaddc1
Show file tree
Hide file tree
Showing 10 changed files with 403 additions and 37 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"noopener",
"noreferrer",
"oidc",
"overscan",
"packagejson",
"sgid",
"sitla",
Expand Down
69 changes: 37 additions & 32 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import { useOverlayTrigger } from 'react-aria';
import { ErrorBoundary } from 'react-error-boundary';
import { useOverlayTriggerState } from 'react-stately';
import { MapContainer } from './components';
import { FilterProvider } from './components/contexts/FilterProvider';
import { useFilter } from './components/contexts/FilterProvider';
import Filter from './components/Filter';
import { useMap } from './components/hooks';
import { DnrLogo } from './components/Logo';
import ResultsGrid from './components/ResultsGrid';
import config from './config';
import ErrorFallback from './ErrorFallback';

Expand Down Expand Up @@ -168,6 +169,12 @@ export default function App() {
}
}, [currentUser]);

// open tray if filter is set
const { filter } = useFilter();
useEffect(() => {
trayState.setOpen(Object.keys(filter).length > 0);
}, [filter, trayState]);

return (
<>
<main className="flex h-screen flex-col md:gap-2">
Expand All @@ -181,42 +188,40 @@ export default function App() {
</Header>
{currentUser ? (
<section className="relative flex min-h-0 flex-1 gap-2 overflow-x-hidden md:mr-2">
<FilterProvider>
<Drawer main state={sideBarState} {...sideBarTriggerProps}>
<div className="mx-2 mb-2 grid grid-cols-1 gap-2">
<Filter />
<h2 className="text-xl font-bold">Map controls</h2>
<div className="flex flex-col gap-4 rounded border border-zinc-200 p-3 dark:border-zinc-700">
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Sherlock {...streamsSherlockOptions} label="Find a stream" />
</ErrorBoundary>
</div>
<div className="flex flex-col gap-4 rounded border border-zinc-200 p-3 dark:border-zinc-700">
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Sherlock {...lakesSherlockOptions} label="Find a lake" />
</ErrorBoundary>
</div>
<Drawer main state={sideBarState} {...sideBarTriggerProps}>
<div className="mx-2 mb-2 grid grid-cols-1 gap-2">
<Filter />
<h2 className="text-xl font-bold">Map controls</h2>
<div className="flex flex-col gap-4 rounded border border-zinc-200 p-3 dark:border-zinc-700">
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Sherlock {...streamsSherlockOptions} label="Find a stream" />
</ErrorBoundary>
</div>
</Drawer>
<div className="relative mb-2 flex flex-1 flex-col overflow-hidden rounded border border-zinc-200 dark:border-zinc-700">
<div className="relative flex-1 overflow-hidden dark:rounded">
<div className="flex flex-col gap-4 rounded border border-zinc-200 p-3 dark:border-zinc-700">
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MapContainer />
<Sherlock {...lakesSherlockOptions} label="Find a lake" />
</ErrorBoundary>
<Drawer
type="tray"
className="shadow-inner dark:shadow-white/20"
allowFullScreen
state={trayState}
{...trayTriggerProps}
>
<section className="grid gap-2 px-7 pt-2">
<h2 className="text-center">Selected records</h2>
</section>
</Drawer>
</div>
</div>
</FilterProvider>
</Drawer>
<div className="relative mb-2 flex flex-1 flex-col overflow-hidden rounded border border-zinc-200 dark:border-zinc-700">
<div className="relative flex-1 overflow-hidden dark:rounded">
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MapContainer />
</ErrorBoundary>
<Drawer
type="tray"
className="shadow-inner dark:shadow-white/20"
allowFullScreen
state={trayState}
{...trayTriggerProps}
>
<section className="grid gap-2 px-7 pt-2">
<ResultsGrid />
</section>
</Drawer>
</div>
</div>
</section>
) : (
<section className="flex flex-1 items-center justify-center">
Expand Down
185 changes: 185 additions & 0 deletions src/components/ResultsGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { useQuery } from '@tanstack/react-query';
import { useFirebaseAuth } from '@ugrc/utah-design-system';
import { User } from 'firebase/auth';
import ky from 'ky';
import { TableBody } from 'react-aria-components';
import config from '../config';
import { useFilter } from './contexts/FilterProvider';
import { getGridQuery, removeIrrelevantWhiteSpace } from './queryHelpers';
import { Cell, Column, Row, Table, TableHeader } from './Table';

const STATION_NAME = 'STATION_NAME';
type Result = Record<string, string | number | null>;
async function getData(where: string, currentUser: User): Promise<Result[]> {
if (where === '') {
return [];
}

const query = `
SELECT DISTINCT
se.${config.fieldNames.EVENT_ID},
${config.fieldNames.EVENT_DATE},
${config.fieldNames.OBSERVERS},
l.${config.fieldNames.WaterName} as ${config.fieldNames.WaterName}_Lake,
l.${config.fieldNames.DWR_WaterID} as ${config.fieldNames.DWR_WaterID}_Lake,
l.${config.fieldNames.ReachCode} as ${config.fieldNames.ReachCode}_Lake,
st.${config.fieldNames.WaterName} as ${config.fieldNames.WaterName}_Stream,
st.${config.fieldNames.DWR_WaterID} as ${config.fieldNames.DWR_WaterID}_Stream,
st.${config.fieldNames.ReachCode} as ${config.fieldNames.ReachCode}_Stream,
s.${config.fieldNames.NAME} as ${STATION_NAME},
SPECIES = STUFF((SELECT DISTINCT ', ' + f.${config.fieldNames.SPECIES_CODE}
FROM ${config.databaseSecrets.databaseName}.${config.databaseSecrets.user}.Fish_evw as f
WHERE se.${config.fieldNames.EVENT_ID} = f.${config.fieldNames.EVENT_ID}
FOR XML PATH ('')),
1, 1, ''),
TYPES = STUFF((SELECT DISTINCT ', ' + eq.TYPE
FROM ${config.databaseSecrets.databaseName}.${config.databaseSecrets.user}.Equipment_evw as eq
WHERE se.${config.fieldNames.EVENT_ID} = eq.${config.fieldNames.EVENT_ID}
FOR XML PATH ('')),
1, 1, '')
FROM ${config.databaseSecrets.databaseName}.${config.databaseSecrets.user}.SamplingEvents_evw as se
LEFT OUTER JOIN ${config.databaseSecrets.databaseName}.${config.databaseSecrets.user}.Fish_evw as f
ON se.${config.fieldNames.EVENT_ID} = f.${config.fieldNames.EVENT_ID}
INNER JOIN ${config.databaseSecrets.databaseName}.${config.databaseSecrets.user}.Stations_evw as s
ON s.${config.fieldNames.STATION_ID} = se.${config.fieldNames.STATION_ID}
LEFT OUTER JOIN ${config.databaseSecrets.databaseName}.${config.databaseSecrets.user}.UDWRLakes_evw as l
ON l.${config.fieldNames.Permanent_Identifier} = s.${config.fieldNames.WATER_ID}
LEFT OUTER JOIN ${config.databaseSecrets.databaseName}.${config.databaseSecrets.user}.UDWRStreams_evw as st
ON st.${config.fieldNames.Permanent_Identifier} = s.${config.fieldNames.WATER_ID}
WHERE ${where}
`;

const params = {
f: 'json',
layer: JSON.stringify({
id: 1,
source: {
type: 'dataLayer',
dataSource: {
type: 'queryTable',
workspaceId: config.dynamicWorkspaceId,
query: removeIrrelevantWhiteSpace(query),
oidFields: config.fieldNames.EVENT_ID,
},
},
}),
outFields: '*',
where: '1 = 1',
returnGeometry: false,
orderByFields: `${config.fieldNames.EVENT_DATE} DESC`,
};

const url = `${config.urls.featureService}/dynamicLayer/query`;

type Response = { error?: { message: string }; features: Array<{ attributes: Result }> };
const urlEncoded = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
urlEncoded.append(key, value as string);
}
const responseJson: Response = await ky
.post(url, {
body: urlEncoded,
headers: {
Authorization: `Bearer ${await currentUser?.getIdToken()}`,
},
})
.json();

if (responseJson.error) {
throw new Error(responseJson.error.message);
}

return responseJson.features.map((feature) => ({
...feature.attributes,
id: feature.attributes.ESRI_OID,
}));
}

export default function ResultsGrid() {
const { filter } = useFilter();

const { currentUser } = useFirebaseAuth();
const gridQuery = getGridQuery(Object.values(filter));
const { data, isPending, error } = useQuery({
queryKey: ['grid', gridQuery],
queryFn: () => {
if (currentUser) {
return getData(gridQuery, currentUser);
}
return null;
},
});

if (isPending) {
return <span>loading...</span>;
}
if (error) {
return <span>{error.message}</span>;
}

return (
<Table aria-label="query results" className="h-full w-full">
<TableHeader>
<Column id={config.fieldNames.EVENT_DATE} minWidth={120}>
Event Date
</Column>
<Column id={config.fieldNames.OBSERVERS} minWidth={120}>
Observers
</Column>
<Column id={`${config.fieldNames.WaterName}_Stream`} minWidth={170}>
Stream
</Column>
<Column id={`${config.fieldNames.DWR_WaterID}_Stream`} minWidth={120}>
Stream ID
</Column>
<Column id={`${config.fieldNames.ReachCode}_Stream`} minWidth={200}>
Stream Reach Code
</Column>
<Column id={`${config.fieldNames.WaterName}_Lake`} minWidth={150}>
Lake
</Column>
<Column id={`${config.fieldNames.DWR_WaterID}_Lake`} minWidth={120}>
Lake ID
</Column>
<Column id={`${config.fieldNames.ReachCode}_Lake`} minWidth={200}>
Lake Reach Code
</Column>
<Column id={STATION_NAME} minWidth={180}>
Station Name
</Column>
<Column id={config.fieldNames.SPECIES} minWidth={180}>
Species Codes
</Column>
<Column id={config.fieldNames.TYPES} minWidth={150}>
Equipment
</Column>
<Column id={config.fieldNames.EVENT_ID} isRowHeader minWidth={350}>
Event ID
</Column>
</TableHeader>
<TableBody items={data}>
{(row) => (
<Row>
<Cell>{new Date(row[config.fieldNames.EVENT_DATE] as number).toLocaleDateString()}</Cell>
<Cell>{row[config.fieldNames.OBSERVERS]}</Cell>
<Cell>{row[`${config.fieldNames.WaterName}_Stream`]}</Cell>
<Cell>{row[`${config.fieldNames.DWR_WaterID}_Stream`]}</Cell>
<Cell>{row[`${config.fieldNames.ReachCode}_Stream`]}</Cell>
<Cell>{row[`${config.fieldNames.WaterName}_Lake`]}</Cell>
<Cell>{row[`${config.fieldNames.DWR_WaterID}_Lake`]}</Cell>
<Cell>{row[`${config.fieldNames.ReachCode}_Lake`]}</Cell>
<Cell>{row[STATION_NAME]}</Cell>
<Cell>{row[config.fieldNames.SPECIES]}</Cell>
<Cell>{row[config.fieldNames.TYPES]}</Cell>
<Cell>{row[config.fieldNames.EVENT_ID]}</Cell>
</Row>
)}
</TableBody>
</Table>
);
}
Loading

0 comments on commit 7aaddc1

Please sign in to comment.