Skip to content

Commit 2f74ad2

Browse files
authored
Merge pull request #534 from CivicDataLab/quicklinks-fix
refactor(analytics): Update component styles and enhance functionality
2 parents 0498dfe + 0237855 commit 2f74ad2

8 files changed

Lines changed: 768 additions & 672 deletions

File tree

app/[locale]/components/analytics-quick-links.tsx

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,20 @@ export const QuickLinks = () => {
5050
</div>
5151
<Carousel
5252
aria-roledescription="carousel"
53-
className="flex w-full items-center justify-center gap-2 px-2 md:px-14"
53+
className="flex w-full items-center gap-2 px-2 md:px-14"
5454
opts={{ align: 'start' }}
5555
>
5656
<div className="shrink-0 rounded-1 bg-surfaceDefault">
5757
<CarouselPrevious />
5858
</div>
59-
<CarouselContent
60-
aria-live="polite"
61-
className={`w-full ${
62-
stateLinks.length === 1 ? 'flex justify-center' : ''
63-
}`}
64-
>
59+
<div className="min-w-0 flex-1">
60+
<CarouselContent
61+
aria-live="polite"
62+
className={cn(
63+
'w-full',
64+
stateLinks.length === 1 && 'flex justify-center'
65+
)}
66+
>
6567
{stateLinks.map((item) => {
6668
const modules = item.modules ?? [];
6769
const multiModule = modules.length > 1;
@@ -139,31 +141,34 @@ export const QuickLinks = () => {
139141
</div>
140142
</Link>
141143
) : (
142-
<div className="relative flex h-40 w-full flex-col items-center gap-4 rounded-2 bg-surfaceSubdued p-4 shadow-elementCard">
144+
<div className="flex h-40 w-full flex-col rounded-2 bg-surfaceSubdued p-4 text-center shadow-elementCard">
143145
<Text
144146
variant="bodyMd"
145147
fontWeight="semibold"
146-
className="absolute left-3 top-3 rounded-2 bg-basePureBlack px-3 py-1 text-surfaceDefault"
148+
className="w-fit rounded-2 bg-basePureBlack px-3 py-1 text-surfaceDefault"
147149
>
148150
{t('comingSoon')}
149151
</Text>
150-
<Image
151-
src={item.icon}
152-
alt=""
153-
className={cn(
154-
'h-20 w-20 shrink-0 object-contain',
155-
styles.inactiveStateIcon
156-
)}
157-
/>
158-
<Text variant="headingLg" fontWeight="bold">
159-
{stateName(item.slug, item.name)}
160-
</Text>
152+
<div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3">
153+
<Image
154+
src={item.icon}
155+
alt=""
156+
className={cn(
157+
'h-16 w-16 shrink-0 object-contain',
158+
styles.inactiveStateIcon
159+
)}
160+
/>
161+
<Text variant="headingLg" fontWeight="bold">
162+
{stateName(item.slug, item.name)}
163+
</Text>
164+
</div>
161165
</div>
162166
)}
163167
</CarouselItem>
164168
);
165169
})}
166-
</CarouselContent>
170+
</CarouselContent>
171+
</div>
167172
<div className="shrink-0 rounded-1 bg-surfaceDefault">
168173
<CarouselNext />
169174
</div>

components/analytics/analytics-layout.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -607,9 +607,11 @@ export function AnalyticsMainLayout() {
607607
timePeriodSelected,
608608
]);
609609

610+
// Use the lockstep slug/metadata pair so the header never shows the raw slug
611+
// of a newly selected indicator before its name has loaded.
610612
const indicatorName = getFactorNameBySlug(
611613
renderedIndicatorsData ?? mapIndicatorsData?.data?.indicators,
612-
indicator
614+
renderedIndicator
613615
);
614616

615617
const viewTabs = ANALYTICS_VIEWS.filter((tabValue) =>
@@ -672,7 +674,11 @@ export function AnalyticsMainLayout() {
672674
}
673675
>
674676
<div className={styles.analyticsHeader}>
675-
<Text as="h2" className={styles.analyticsIndicatorTitle}>
677+
<Text
678+
as="h2"
679+
className={styles.analyticsIndicatorTitle}
680+
title={indicatorName}
681+
>
676682
{indicatorName}
677683
</Text>
678684
<TabList className={styles.analyticsViewTabs}>

components/analytics/output-window.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { docsLink } from '@/config/site';
1818
import { getFactorIcon } from '@/lib/analytics/factor-icon';
1919
import { getFactorRole, isScoreIndicator } from '@/lib/analytics/factor-role';
2020
import { isRootRiskIndicator } from '@/lib/analytics/root-indicator';
21-
import { getFactorNameBySlug, getLatestDate } from '@/lib/analytics/utils';
21+
import { getFactorNameBySlug, getLatestDate, filterSubIndicatorsForView } from '@/lib/analytics/utils';
2222
import { GraphQL } from '@/lib/api';
2323
import { type JsonScalar } from '@/lib/types';
2424
import { cn, parsePeriodString } from '@/lib/utils';
@@ -304,6 +304,7 @@ export function OutputWindow({
304304
data={data}
305305
boundary={boundary}
306306
indicator={indicator}
307+
view={view}
307308
getDescription={getDescription}
308309
/>
309310
</div>
@@ -512,6 +513,7 @@ export function OutputWindow({
512513
data={data}
513514
boundary={boundary}
514515
indicator={indicator}
516+
view={view}
515517
getDescription={getDescription}
516518
/>
517519
</div>
@@ -532,12 +534,14 @@ function OtherFactorScores({
532534
data,
533535
boundary,
534536
indicator,
537+
view,
535538
getDescription,
536539
}: {
537540
factorData: Indicator[] | undefined;
538541
data: JsonScalar;
539542
boundary: string;
540543
indicator: string;
544+
view: string;
541545
getDescription: (slug: string) => string | null | undefined;
542546
}) {
543547
const clonedData = structuredClone(data);
@@ -547,12 +551,15 @@ function OtherFactorScores({
547551
delete clonedData['district'];
548552
delete clonedData['district-code'];
549553

550-
const FactorVariables = Object.keys(clonedData);
554+
const FactorVariables = filterSubIndicatorsForView(
555+
Object.keys(clonedData).filter(
556+
(scoreType) => typeof data[scoreType] === 'object'
557+
),
558+
view,
559+
indicator
560+
);
551561

552-
// TODO: Change the filteration to the factor specific structure for it to work with data having objects
553-
return FactorVariables.filter(
554-
(scoreType) => typeof data[scoreType] === 'object'
555-
).map((scoreType) => (
562+
return FactorVariables.map((scoreType) => (
556563
<div key={scoreType} className=" flex items-center gap-4">
557564
{/* //change */}
558565
{getFactorRole(scoreType) && (

components/analytics/styles.module.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,11 @@
191191
}
192192

193193
.analyticsIndicatorTitle {
194-
flex: none;
194+
flex: 1 1 auto;
195+
min-width: 0;
196+
overflow: hidden;
197+
text-overflow: ellipsis;
198+
white-space: nowrap;
195199
font-weight: 600;
196200
font-size: 20px;
197201
line-height: 1.2;

components/main-nav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function MainNav({ prefLangCookie }: { prefLangCookie: string }) {
4242
</div>
4343
</NavLink>
4444
</div>
45-
<div className="flex">
45+
<div className="flex items-center gap-3 sm:gap-5">
4646
{mainNav.length > 0 && (
4747
<div className="flex shrink-0 flex-wrap items-center gap-3 sm:gap-5">
4848
{mainNav.map((link) => (

lib/analytics/utils.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type Indicator } from '@/config/graphql/analaytics-queries';
2+
import { getFactorRole } from '@/lib/analytics/factor-role';
23

34
export function getFactorNameBySlug(
45
factorData: Indicator[] | undefined,
@@ -8,6 +9,39 @@ export function getFactorNameBySlug(
89
return factorName?.[0]?.name ?? slug;
910
}
1011

12+
/**
13+
* Government-response sub-indicators can arrive as both monthly and FY-cumsum
14+
* variants. Map/table should show only cumulative; chart only monthly.
15+
* Other parent indicators (e.g. risk-score) are left unchanged.
16+
*/
17+
export function filterSubIndicatorsForView(
18+
scoreTypes: string[],
19+
view: string,
20+
parentIndicator: string
21+
): string[] {
22+
if (getFactorRole(parentIndicator) !== 'government-response') {
23+
return scoreTypes;
24+
}
25+
26+
const isMapLike = !view || view === 'map' || view === 'table';
27+
const isChart = view === 'chart';
28+
29+
return scoreTypes.filter((scoreType) => {
30+
const isCumsum = scoreType.endsWith('-fy-cumsum');
31+
32+
if (isMapLike) {
33+
return !(!isCumsum && scoreTypes.includes(`${scoreType}-fy-cumsum`));
34+
}
35+
36+
if (isChart) {
37+
const monthlySlug = scoreType.replace(/-fy-cumsum$/, '');
38+
return !(isCumsum && scoreTypes.includes(monthlySlug));
39+
}
40+
41+
return true;
42+
});
43+
}
44+
1145
export function getUnitsBySlug(
1246
factorData: Indicator[] | undefined,
1347
slug: string
@@ -31,9 +65,7 @@ export const getLatestDate = (dateStrings: string[]) => {
3165
return new Date(parseInt(year, 10), parseInt(month, 10) - 1);
3266
});
3367

34-
const latestDate = new Date(
35-
Math.max(...dates.map((date) => date.getTime()))
36-
);
68+
const latestDate = new Date(Math.max(...dates.map((date) => date.getTime())));
3769

3870
const year = latestDate.getFullYear();
3971
const month = String(latestDate.getMonth() + 1).padStart(2, '0');

0 commit comments

Comments
 (0)