Skip to content

Commit 1c2ea65

Browse files
authored
Merge pull request #183 from CivicDataLab/130-data-analytics---data-visualizations
130 data analytics page - data visualizations
2 parents f1fd25e + e664694 commit 1c2ea65

File tree

9 files changed

+508
-150
lines changed

9 files changed

+508
-150
lines changed

app/[locale]/[state]/analytics/components/analytics-layout.tsx

Lines changed: 70 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
11
'use client';
22

3-
import React, { useCallback, useState } from 'react';
3+
import React, { useState } from 'react';
44
import { useParams, useSearchParams } from 'next/navigation';
5-
import { parseDate } from '@internationalized/date';
65
import { useQuery } from '@tanstack/react-query';
76
import { parseAsString, useQueryState } from 'next-usequerystate';
8-
import {
9-
MonthPicker,
10-
Select,
11-
Spinner,
12-
Tab,
13-
TabList,
14-
TabPanel,
15-
Tabs,
16-
Text,
17-
} from 'opub-ui';
7+
import { Spinner, Tab, TabList, TabPanel, Tabs, Text } from 'opub-ui';
188

199
import {
2010
ANALYTICS_DISTRICT_DATA,
@@ -28,29 +18,33 @@ import {
2818
PLATFORM_STATES_LIST,
2919
} from '@/config/graphql/analaytics-queries';
3020
import { GraphQL } from '@/lib/api';
31-
import { formatDate, toTitleCase } from '@/lib/utils';
3221
import { MediaRendering } from '@/components/media-rendering';
22+
import { getLatestDate } from '../utils/utils';
3323
import { AnalyticsMobileLayout } from './analytics-mobile-layout';
24+
import { ChartView } from './chart-view';
25+
import FilterDropdownOptions from './filter-dropdown-options';
3426
import { MapComponent } from './map-component';
3527
import { OutputWindow } from './output-window';
3628
import { TableComponent } from './table-component';
3729

30+
interface Option {
31+
disabled?: boolean;
32+
value: string;
33+
label: string;
34+
districtCode?: string;
35+
}
36+
3837
export function AnalyticsMainLayout() {
3938
const searchParams = useSearchParams();
4039
const indicator = searchParams.get('indicator') || '';
41-
const timePeriod = searchParams.get('time-period') || '';
4240

43-
interface Option {
44-
disabled?: boolean;
45-
value: string;
46-
label: string;
47-
districtCode?: string;
48-
}
41+
const timePeriod = getLatestDate(
42+
searchParams.getAll('time-period') || process.env.NEXT_PUBLIC_TIME_PERIOD
43+
)?.split('-');
4944

50-
const [timePeriodSelected, setTimePeriod] = useQueryState(
51-
'time-period',
52-
parseAsString.withDefault(timePeriod)
53-
);
45+
const timePeriodSelected = timePeriod
46+
? `${timePeriod[0]}_${timePeriod[1]}`
47+
: process.env.NEXT_PUBLIC_TIME_PERIOD;
5448

5549
const [districtCode, setDistrictCode] = useQueryState(
5650
'district-code',
@@ -102,6 +96,7 @@ export function AnalyticsMainLayout() {
10296
}
10397
),
10498
{
99+
enabled: Boolean(view === 'map'),
105100
refetchOnMount: false,
106101
refetchOnWindowFocus: false,
107102
refetchOnReconnect: false,
@@ -125,6 +120,7 @@ export function AnalyticsMainLayout() {
125120
}
126121
),
127122
{
123+
enabled: Boolean(view === 'map'),
128124
refetchOnMount: false,
129125
refetchOnWindowFocus: false,
130126
refetchOnReconnect: false,
@@ -196,6 +192,7 @@ export function AnalyticsMainLayout() {
196192
}
197193
),
198194
{
195+
enabled: Boolean(view === 'map'),
199196
refetchOnMount: false,
200197
refetchOnWindowFocus: false,
201198
refetchOnReconnect: false,
@@ -223,6 +220,7 @@ export function AnalyticsMainLayout() {
223220
}
224221
),
225222
{
223+
enabled: Boolean(view === 'table'),
226224
refetchOnMount: false,
227225
refetchOnWindowFocus: false,
228226
refetchOnReconnect: false,
@@ -233,24 +231,6 @@ export function AnalyticsMainLayout() {
233231
tableData.data?.tableData
234232
);
235233

236-
let minDate, maxDate;
237-
if (timePeriods.data) {
238-
const datesArray = timePeriods?.data?.getDataTimePeriods.map(
239-
(date: any) => {
240-
const [year, month] = date.value.split('_');
241-
return new Date(parseInt(year), parseInt(month));
242-
}
243-
);
244-
const timestamps = datesArray.map((date: any) => date.getTime());
245-
// Find the minimum and maximum timestamps
246-
const minTimestamp = Math.min(...timestamps);
247-
const maxTimestamp = Math.max(...timestamps);
248-
249-
// Convert the timestamps back to dates
250-
minDate = formatDate(minTimestamp, true);
251-
maxDate = formatDate(maxTimestamp, true);
252-
}
253-
254234
let RevCircleDropdownOptions: Option[] = [{ label: '', value: '' }];
255235
let DistrictDropDownOption: Option[] = [{ label: '', value: '' }];
256236

@@ -307,69 +287,13 @@ export function AnalyticsMainLayout() {
307287
}
308288
}, [revenueCode, tableData.data?.tableData]);
309289

310-
const getRevenueCircleOptions = () => {
311-
const filterRevenueCircles = RevCircleDropdownOptions.filter(
312-
(option) => option.districtCode === districtCode
313-
);
314-
315-
filterRevenueCircles.unshift({ label: '', value: '' });
316-
317-
return filterRevenueCircles;
318-
};
319-
320-
const handleDistrictChange = (districtCode: string) => {
321-
setDistrictCode(districtCode, { shallow: false });
322-
};
323-
324-
function SelectOptions() {
325-
return (
326-
<React.Fragment>
327-
<Select
328-
label="Select District"
329-
value={districtCode || ''}
330-
name="district-select"
331-
className=" flex-grow"
332-
onChange={(e) => {
333-
handleDistrictChange(e);
334-
}}
335-
options={DistrictDropDownOption}
336-
/>
337-
<Select
338-
label={`Select ${toTitleCase(currentSelectedState.child_type)}`}
339-
value={revenueCode || ''}
340-
placeholder={
341-
!districtCode
342-
? 'Select a district to enable'
343-
: `Select a ${toTitleCase(currentSelectedState.child_type)}`
344-
}
345-
name="revenue-circle-select"
346-
className=" flex-grow"
347-
disabled={!districtCode}
348-
onChange={(e) => {
349-
setRevenueCode(e, { shallow: false });
350-
}}
351-
options={getRevenueCircleOptions()}
352-
/>
353-
</React.Fragment>
354-
);
355-
}
356-
357-
if (mapData?.isFetching && revenueMapData?.isFetching) {
358-
return (
359-
<div className="flex h-full flex-col place-content-center items-center">
360-
<Spinner color="highlight" />
361-
<Text>Loading...</Text>
362-
</div>
363-
);
364-
}
365-
366290
const region = searchParams.get('district-code') || '';
367291

368292
return (
369293
<>
370294
<MediaRendering minWidth={null} maxWidth="1023">
371295
<AnalyticsMobileLayout
372-
timePeriod={timePeriod}
296+
timePeriod={timePeriodSelected || ''}
373297
indicator={indicator}
374298
mapData={mapData}
375299
revenueMapData={revenueMapData}
@@ -395,46 +319,37 @@ export function AnalyticsMainLayout() {
395319
Map View
396320
</Tab>
397321
<div
398-
className={`ml-4 h-14 border-l-1 border-solid border-baseGraySlateSolid8 ${view === 'map' ? 'hidden' : ''}`}
322+
className={`h-14 border-l-1 border-solid border-baseGraySlateSolid8 ${view === 'map' || view === 'chart' ? 'hidden' : ''}`}
399323
/>
400-
<Tab
401-
theme="climate"
402-
title="coming soon"
403-
className=" cursor-not-allowed"
404-
disabled
405-
value="chart"
406-
>
324+
<Tab theme="climate" value="chart">
407325
Chart View
408326
</Tab>
409327
<div
410-
className={`ml-4 h-14 border-l-1 border-solid border-baseGraySlateSolid8 ${view === 'table' ? 'hidden' : ''}`}
411-
/>{' '}
328+
className={`h-14 border-l-1 border-solid border-baseGraySlateSolid8 ${view === 'chart' || view === 'table' ? 'hidden' : ''}`}
329+
/>
412330
<Tab theme="climate" value="table">
413331
Table View
414332
</Tab>
415333
</TabList>
416334
<TabPanel value="map">
417-
{revenueMapData?.data && mapData?.data && (
418-
<div className=" mt-2 h-[calc(100dvh_-_140px)]">
419-
<div className="mb-2 flex items-start justify-evenly gap-3 p-4 pb-0 pt-0">
420-
<SelectOptions />
421-
<MonthPicker
422-
name="time-period-select"
423-
defaultValue={parseDate(
424-
`${timePeriodSelected.split('_')[0]}-${timePeriodSelected.split('_')[1]}-01` ||
425-
'23-08-01'
426-
)}
427-
label="Select Month"
428-
minValue={parseDate(minDate || '2023-01-04')}
429-
maxValue={parseDate(maxDate || '2023-01-04')}
430-
onChange={(date) => {
431-
setTimePeriod(
432-
`${date.year}_${date.month < 10 ? `0${date.month}` : `${date.month}`}`,
433-
{ shallow: false }
434-
);
435-
}}
436-
/>
335+
<div className=" mt-2 h-[calc(100dvh_-_140px)]">
336+
<div>
337+
<FilterDropdownOptions
338+
currentSelectedState={currentSelectedState}
339+
RevCircleDropdownOptions={RevCircleDropdownOptions}
340+
DistrictDropDownOption={DistrictDropDownOption}
341+
timeLimits={timePeriods}
342+
/>
343+
</div>
344+
345+
{mapData?.isFetching && revenueMapData?.isFetching && (
346+
<div className="flex h-full flex-col place-content-center items-center">
347+
<Spinner color="highlight" />
348+
<Text>Loading...</Text>
437349
</div>
350+
)}
351+
352+
{revenueMapData?.data && mapData?.data && (
438353
<MapComponent
439354
indicator={indicator}
440355
mapDataloading={mapData?.isFetching}
@@ -446,17 +361,23 @@ export function AnalyticsMainLayout() {
446361
mapData={mapData?.data?.districtMapData}
447362
currentSelectedState={currentSelectedState}
448363
/>
449-
{region !== null && region.length > 0 && view === 'map' && (
450-
<OutputWindowComponent
451-
currentState={currentSelectedState}
452-
/>
453-
)}
454-
</div>
455-
)}
364+
)}
365+
{region !== null && region.length > 0 && view === 'map' && (
366+
<OutputWindowComponent
367+
currentState={currentSelectedState}
368+
time_period={timePeriodSelected}
369+
/>
370+
)}
371+
</div>
456372
</TabPanel>
457373
<TabPanel value="table">
458-
<div className="mb-2 mt-2 flex items-start justify-evenly gap-3 p-4 pb-0 pt-0">
459-
<SelectOptions />
374+
<div>
375+
<FilterDropdownOptions
376+
currentSelectedState={currentSelectedState}
377+
RevCircleDropdownOptions={RevCircleDropdownOptions}
378+
DistrictDropDownOption={DistrictDropDownOption}
379+
timeLimits={timePeriods}
380+
/>
460381
</div>
461382
<TableComponent
462383
data={
@@ -467,17 +388,26 @@ export function AnalyticsMainLayout() {
467388
isLoading={tableData.isLoading}
468389
/>
469390
</TabPanel>
391+
<TabPanel value="chart">
392+
<div className=" mt-2 h-[calc(100dvh_-_140px)]">
393+
<ChartView
394+
currentSelectedState={currentSelectedState}
395+
RevCircleDropdownOptions={RevCircleDropdownOptions}
396+
DistrictDropDownOption={DistrictDropDownOption}
397+
timeLimits={timePeriods}
398+
/>
399+
</div>
400+
</TabPanel>
470401
</Tabs>
471402
</React.Fragment>
472403
</MediaRendering>
473404
</>
474405
);
475406
}
476407

477-
export function OutputWindowComponent({ currentState }: any) {
408+
export function OutputWindowComponent({ currentState, time_period }: any) {
478409
const searchParams = useSearchParams();
479410
const indicator = searchParams.get('indicator');
480-
const time_period = searchParams.get('time-period');
481411
const region =
482412
searchParams.get('revenue-code') || searchParams.get('district-code');
483413
const boundary = searchParams.get('revenue-code')

app/[locale]/[state]/analytics/components/analytics-mobile-layout.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
formatDate,
1414
} from '@/lib/utils';
1515
import Icons from '@/components/icons';
16+
import { getLatestDate } from '../utils/utils';
1617
import { OutputWindowComponent } from './analytics-layout';
1718
import { FactorList } from './factor-list';
1819
import { FilterComp } from './filter-component';
@@ -163,7 +164,14 @@ export function AnalyticsMobileLayout({
163164
// Sync time period from URL on component mount
164165
React.useEffect(() => {
165166
const params = new URLSearchParams(window.location.search);
166-
const timePeriod = params.get('time-period');
167+
let processedTime = getLatestDate(
168+
params.get('time-period')?.split(',') || []
169+
)?.split('-');
170+
171+
const timePeriod = processedTime
172+
? `${processedTime[0]}_${processedTime[1]}`
173+
: process.env.NEXT_PUBLIC_TIME_PERIOD;
174+
167175
if (timePeriod) {
168176
setTimePeriod(timePeriod);
169177
}

0 commit comments

Comments
 (0)