Enhance StandardSection components with expandable functionality

- Added `expanded` and `close` props to StandardSection and its dialog components for better state management.
- Integrated expand/collapse button in various sections (Countries, Devices, Pages, Referrers) to improve user interaction.
- Updated layout to accommodate new button and ensure consistent styling across sections.
This commit is contained in:
Bill Yang 2025-05-04 09:21:47 -07:00
parent 4447e5d6e7
commit cdd40216ce
7 changed files with 177 additions and 61 deletions

View file

@ -3,6 +3,7 @@ import NumberFlow from "@number-flow/react";
import { round } from "lodash";
import {
AlertCircle,
Expand,
Info,
RefreshCcw,
SquareArrowOutUpRight,
@ -90,6 +91,8 @@ interface BaseStandardSectionProps {
getLink?: (item: SingleColResponse) => string;
countLabel?: string;
filterParameter: FilterParameter;
expanded: boolean;
close: () => void;
}
export function BaseStandardSection({
@ -105,6 +108,8 @@ export function BaseStandardSection({
getLink,
countLabel,
filterParameter,
expanded,
close,
}: BaseStandardSectionProps) {
const ratio = data?.data?.[0]?.percentage
? 100 / data?.data?.[0]?.percentage
@ -139,7 +144,7 @@ export function BaseStandardSection({
</Button>
</div>
) : (
<>
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-2 justify-between pr-1 text-xs text-neutral-400">
<div>{title}</div>
<div>{countLabel || "Sessions"}</div>
@ -165,27 +170,26 @@ export function BaseStandardSection({
No Data
</div>
)}
</>
</div>
)}
{!isLoading && !hasError && data?.data?.length && (
<div className="flex flex-row gap-2 justify-between items-center">
<BaseStandardSectionDialog
title={title}
data={data.data}
ratio={ratio}
getKey={getKey}
getLabel={getLabel}
getValue={getValue}
getFilterLabel={getFilterLabel}
getLink={getLink}
countLabel={countLabel}
filterParameter={filterParameter}
expanded={expanded}
close={close}
/>
</div>
)}
{isLoading ? (
// Skeleton for "View All" button when loading
<Button variant="outline" disabled className="opacity-50">
View All
</Button>
) : !hasError && data?.data?.length && data?.data?.length > 10 ? (
<BaseStandardSectionDialog
title={title}
data={data.data}
ratio={ratio}
getKey={getKey}
getLabel={getLabel}
getValue={getValue}
getFilterLabel={getFilterLabel}
getLink={getLink}
countLabel={countLabel}
filterParameter={filterParameter}
/>
) : null}
</div>
);
}

View file

@ -21,6 +21,7 @@ import {
SquareArrowOutUpRight,
ChevronUp,
ChevronDown,
Expand,
} from "lucide-react";
import { ReactNode, useMemo, useState } from "react";
import { SingleColResponse } from "../../../../../api/analytics/useSingleCol";
@ -38,6 +39,8 @@ interface BaseStandardSectionDialogProps {
getLink?: (item: SingleColResponse) => string;
countLabel?: string;
filterParameter: FilterParameter;
expanded?: boolean;
close: () => void;
}
const columnHelper = createColumnHelper<SingleColResponse>();
@ -53,6 +56,8 @@ export function BaseStandardSectionDialog({
getLink,
countLabel,
filterParameter,
expanded,
close,
}: BaseStandardSectionDialogProps) {
const [searchTerm, setSearchTerm] = useState("");
const debouncedSearchTerm = useDebounce(searchTerm, 200);
@ -179,10 +184,12 @@ export function BaseStandardSectionDialog({
});
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">View All</Button>
</DialogTrigger>
<Dialog open={expanded} onOpenChange={close}>
{/* <DialogTrigger asChild>
<Button variant="default" size="sm">
<Expand className="w-4 h-4" /> Expand
</Button>
</DialogTrigger> */}
<DialogContent className="max-w-[1000px] w-[calc(100vw-2rem)] p-2 sm:p-4">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>

View file

@ -18,6 +18,8 @@ export function StandardSection({
getLink,
countLabel,
filterParameter,
expanded,
close,
}: {
title: string;
getKey: (item: SingleColResponse) => string;
@ -27,6 +29,8 @@ export function StandardSection({
getLink?: (item: SingleColResponse) => string;
countLabel?: string;
filterParameter: FilterParameter;
expanded: boolean;
close: () => void;
}) {
const { data, isLoading, isFetching, error, refetch } = useSingleCol({
parameter: filterParameter,
@ -72,6 +76,8 @@ export function StandardSection({
getLink={getLink}
countLabel={countLabel}
filterParameter={filterParameter}
expanded={expanded}
close={close}
/>
</>
);

View file

@ -1,5 +1,5 @@
"use client";
import { ChevronRight, Globe } from "lucide-react";
import { ChevronRight, Expand, Globe } from "lucide-react";
import { useState } from "react";
import {
Tabs,
@ -13,6 +13,7 @@ import { StandardSection } from "../../../components/shared/StandardSection/Stan
import { CountryFlag } from "../../../components/shared/icons/CountryFlag";
import { useSubdivisions } from "../../../../../lib/geo";
import { MapComponent } from "../../../components/shared/Map";
import { Button } from "../../../../../components/ui/button";
type Tab = "countries" | "regions" | "languages" | "cities" | "map";
@ -27,24 +28,38 @@ const getCountryFromLanguage = (languageCode: string): string | null => {
export function Countries() {
const [tab, setTab] = useState<Tab>("countries");
const [expanded, setExpanded] = useState(false);
const close = () => {
setExpanded(false);
};
const { data: subdivisions } = useSubdivisions();
return (
<Card className="">
<Card className="h-[405px]">
<CardContent className="mt-2">
<Tabs
defaultValue="countries"
value={tab}
onValueChange={(value) => setTab(value as Tab)}
>
<TabsList>
<TabsTrigger value="countries">Countries</TabsTrigger>
<TabsTrigger value="regions">Regions</TabsTrigger>
<TabsTrigger value="cities">Cities</TabsTrigger>
<TabsTrigger value="languages">Languages</TabsTrigger>
<TabsTrigger value="map">Map</TabsTrigger>
</TabsList>
<div className="flex flex-row gap-2 justify-between items-start">
<TabsList>
<TabsTrigger value="countries">Countries</TabsTrigger>
<TabsTrigger value="regions">Regions</TabsTrigger>
<TabsTrigger value="cities">Cities</TabsTrigger>
<TabsTrigger value="languages">Languages</TabsTrigger>
<TabsTrigger value="map">Map</TabsTrigger>
</TabsList>
{tab !== "map" && (
<Button
variant="outline"
size="smIcon"
onClick={() => setExpanded(!expanded)}
>
<Expand className="w-4 h-4" />
</Button>
)}
</div>
<TabsContent value="countries">
<StandardSection
filterParameter="country"
@ -60,6 +75,8 @@ export function Countries() {
</div>
);
}}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="regions">
@ -94,6 +111,8 @@ export function Countries() {
</div>
);
}}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="cities">
@ -108,6 +127,8 @@ export function Countries() {
}
return <div className="flex gap-2 items-center">{e.value}</div>;
}}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="languages">
@ -127,10 +148,12 @@ export function Countries() {
{getLanguageName(e.value)}
</div>
)}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="map">
<MapComponent height="380px" />
<MapComponent height="340px" />
</TabsContent>
</Tabs>
</CardContent>

View file

@ -1,6 +1,6 @@
"use client";
import { Monitor, Smartphone, Tablet } from "lucide-react";
import { Expand, Monitor, Smartphone, Tablet } from "lucide-react";
import { useState } from "react";
import {
Tabs,
@ -12,26 +12,41 @@ import { Card, CardContent } from "../../../../../components/ui/card";
import { StandardSection } from "../../../components/shared/StandardSection/StandardSection";
import { Browser } from "../../../components/shared/icons/Browser";
import { OperatingSystem } from "../../../components/shared/icons/OperatingSystem";
import { Button } from "../../../../../components/ui/button";
type Tab = "devices" | "browsers" | "os" | "dimensions";
export function Devices() {
const [tab, setTab] = useState<Tab>("browsers");
const [expanded, setExpanded] = useState(false);
const close = () => {
setExpanded(false);
};
return (
<Card className="h-[445px]">
<Card className="h-[405px]">
<CardContent className="mt-2">
<Tabs
defaultValue="browsers"
value={tab}
onValueChange={(value) => setTab(value as Tab)}
>
<div className="overflow-x-auto pb-1">
<TabsList>
<TabsTrigger value="browsers">Browsers</TabsTrigger>
<TabsTrigger value="devices">Devices</TabsTrigger>
<TabsTrigger value="os">Operating Systems</TabsTrigger>
<TabsTrigger value="dimensions">Screen Dimensions</TabsTrigger>
</TabsList>
<div className="flex flex-row gap-2 justify-between items-start">
<div className="overflow-x-auto pb-1">
<TabsList>
<TabsTrigger value="browsers">Browsers</TabsTrigger>
<TabsTrigger value="devices">Devices</TabsTrigger>
<TabsTrigger value="os">Operating Systems</TabsTrigger>
<TabsTrigger value="dimensions">Screen Dimensions</TabsTrigger>
</TabsList>
</div>
<Button
variant="outline"
size="smIcon"
onClick={() => setExpanded(!expanded)}
>
<Expand />
</Button>
</div>
<TabsContent value="devices">
<StandardSection
@ -47,6 +62,8 @@ export function Devices() {
{e.value || "Other"}
</div>
)}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="browsers">
@ -61,6 +78,8 @@ export function Devices() {
{e.value || "Other"}
</div>
)}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="os">
@ -75,6 +94,8 @@ export function Devices() {
</div>
)}
filterParameter="operating_system"
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="dimensions">
@ -88,6 +109,8 @@ export function Devices() {
</div>
)}
filterParameter="dimensions"
expanded={expanded}
close={close}
/>
</TabsContent>
</Tabs>

View file

@ -11,25 +11,41 @@ import { Card, CardContent } from "../../../../../components/ui/card";
import { StandardSection } from "../../../components/shared/StandardSection/StandardSection";
import { useGetSite } from "../../../../../api/admin/sites";
import { truncateString } from "../../../../../lib/utils";
import { Button } from "../../../../../components/ui/button";
import { Expand } from "lucide-react";
type Tab = "pages" | "entry_pages" | "exit_pages";
export function Pages() {
const { data: siteMetadata } = useGetSite();
const [tab, setTab] = useState<Tab>("pages");
const [expanded, setExpanded] = useState(false);
const close = () => {
setExpanded(false);
};
return (
<Card className="h-[445px]">
<Card className="h-[405px]">
<CardContent className="mt-2">
<Tabs
defaultValue="pages"
value={tab}
onValueChange={(value) => setTab(value as Tab)}
>
<TabsList>
<TabsTrigger value="pages">Pages</TabsTrigger>
<TabsTrigger value="entry_pages">Entry Pages</TabsTrigger>
<TabsTrigger value="exit_pages">Exit Pages</TabsTrigger>
</TabsList>
<div className="flex flex-row gap-2 justify-between items-start">
<TabsList>
<TabsTrigger value="pages">Pages</TabsTrigger>
<TabsTrigger value="entry_pages">Entry Pages</TabsTrigger>
<TabsTrigger value="exit_pages">Exit Pages</TabsTrigger>
</TabsList>
<Button
variant="outline"
size="smIcon"
onClick={() => setExpanded(!expanded)}
>
<Expand className="w-4 h-4" />
</Button>
</div>
<TabsContent value="pages">
<StandardSection
filterParameter="pathname"
@ -38,6 +54,8 @@ export function Pages() {
getKey={(e) => e.value}
getLabel={(e) => truncateString(e.value, 30) || "Other"}
getLink={(e) => `https://${siteMetadata?.domain}${e.value}`}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="entry_pages">
@ -48,6 +66,8 @@ export function Pages() {
getKey={(e) => e.value}
getLabel={(e) => e.value || "Other"}
getLink={(e) => `https://${siteMetadata?.domain}${e.value}`}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="exit_pages">
@ -58,6 +78,8 @@ export function Pages() {
getKey={(e) => e.value}
getLabel={(e) => e.value || "Other"}
getLink={(e) => `https://${siteMetadata?.domain}${e.value}`}
expanded={expanded}
close={close}
/>
</TabsContent>
</Tabs>

View file

@ -8,6 +8,8 @@ import {
} from "../../../../../components/ui/basic-tabs";
import { Card, CardContent } from "../../../../../components/ui/card";
import { StandardSection } from "../../../components/shared/StandardSection/StandardSection";
import { Button } from "../../../../../components/ui/button";
import { Expand } from "lucide-react";
type Tab =
| "referrers"
@ -20,24 +22,39 @@ type Tab =
export function Referrers() {
const [tab, setTab] = useState<Tab>("referrers");
const [expanded, setExpanded] = useState(false);
const close = () => {
setExpanded(false);
};
return (
<Card className="h-[445px]">
<Card className="h-[405px]">
<CardContent className="mt-2">
<Tabs
defaultValue="referrers"
value={tab}
onValueChange={(value) => setTab(value as Tab)}
>
<TabsList>
<TabsTrigger value="referrers">Referrers</TabsTrigger>
<TabsTrigger value="channels">Channels</TabsTrigger>
<TabsTrigger value="utm_source">Source</TabsTrigger>
<TabsTrigger value="utm_medium">Medium</TabsTrigger>
<TabsTrigger value="utm_campaign">Campaign</TabsTrigger>
<TabsTrigger value="utm_term">Term</TabsTrigger>
<TabsTrigger value="utm_content">Content</TabsTrigger>
</TabsList>
<div className="flex flex-row gap-2 justify-between items-start">
<div className="overflow-x-auto pb-1">
<TabsList>
<TabsTrigger value="referrers">Referrers</TabsTrigger>
<TabsTrigger value="channels">Channels</TabsTrigger>
<TabsTrigger value="utm_source">Source</TabsTrigger>
<TabsTrigger value="utm_medium">Medium</TabsTrigger>
<TabsTrigger value="utm_campaign">Campaign</TabsTrigger>
<TabsTrigger value="utm_term">Term</TabsTrigger>
<TabsTrigger value="utm_content">Content</TabsTrigger>
</TabsList>
</div>
<Button
variant="outline"
size="smIcon"
onClick={() => setExpanded(!expanded)}
>
<Expand className="w-4 h-4" />
</Button>
</div>
<TabsContent value="referrers">
<StandardSection
filterParameter="referrer"
@ -54,6 +71,8 @@ export function Referrers() {
{e.value ? e.value : "Direct"}
</div>
)}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="channels">
@ -66,6 +85,8 @@ export function Referrers() {
getLabel={(e) => (
<div className="flex items-center">{e.value}</div>
)}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="utm_source">
@ -75,6 +96,8 @@ export function Referrers() {
getKey={(e) => e.value}
getLabel={(e) => e.value}
getValue={(e) => e.value}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="utm_medium">
@ -84,6 +107,8 @@ export function Referrers() {
getKey={(e) => e.value}
getLabel={(e) => e.value}
getValue={(e) => e.value}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="utm_campaign">
@ -93,6 +118,8 @@ export function Referrers() {
getKey={(e) => e.value}
getLabel={(e) => e.value}
getValue={(e) => e.value}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="utm_content">
@ -102,6 +129,8 @@ export function Referrers() {
getKey={(e) => e.value}
getLabel={(e) => e.value}
getValue={(e) => e.value}
expanded={expanded}
close={close}
/>
</TabsContent>
<TabsContent value="utm_term">
@ -111,6 +140,8 @@ export function Referrers() {
getKey={(e) => e.value}
getLabel={(e) => e.value}
getValue={(e) => e.value}
expanded={expanded}
close={close}
/>
</TabsContent>
</Tabs>