mirror of
https://github.com/rybbit-io/rybbit.git
synced 2025-05-11 20:35:39 +02:00
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:
parent
4447e5d6e7
commit
cdd40216ce
7 changed files with 177 additions and 61 deletions
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue