wip
This commit is contained in:
@@ -1,11 +1,3 @@
|
||||
import {
|
||||
useXAxisProps,
|
||||
useYAxisProps,
|
||||
} from '@/components/report-chart/common/axis';
|
||||
import { Widget, WidgetBody } from '@/components/widget';
|
||||
import { WidgetHead, WidgetTitle } from '../overview/overview-widget';
|
||||
import { useNumber } from '@/hooks/use-numer-formatter';
|
||||
import { useFormatDateInterval } from '@/hooks/use-format-date-interval';
|
||||
import { TrendingUpIcon } from 'lucide-react';
|
||||
import {
|
||||
Area,
|
||||
@@ -16,6 +8,14 @@ import {
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
import { WidgetHead, WidgetTitle } from '../overview/overview-widget';
|
||||
import {
|
||||
useXAxisProps,
|
||||
useYAxisProps,
|
||||
} from '@/components/report-chart/common/axis';
|
||||
import { Widget, WidgetBody } from '@/components/widget';
|
||||
import { useFormatDateInterval } from '@/hooks/use-format-date-interval';
|
||||
import { useNumber } from '@/hooks/use-numer-formatter';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
|
||||
type Props = {
|
||||
@@ -37,10 +37,16 @@ function Tooltip(props: any) {
|
||||
{formatDate(new Date(payload.timestamp))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-10 w-1 rounded-full" style={{ background: getChartColor(0) }} />
|
||||
<div
|
||||
className="h-10 w-1 rounded-full"
|
||||
style={{ background: getChartColor(0) }}
|
||||
/>
|
||||
<div className="col gap-1">
|
||||
<div className="text-muted-foreground text-sm">Total members</div>
|
||||
<div className="font-semibold text-lg" style={{ color: getChartColor(0) }}>
|
||||
<div
|
||||
className="font-semibold text-lg"
|
||||
style={{ color: getChartColor(0) }}
|
||||
>
|
||||
{number.format(payload.cumulative)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,7 +93,7 @@ export function GroupMemberGrowth({ data }: Props) {
|
||||
<ResponsiveContainer>
|
||||
<AreaChart data={chartData}>
|
||||
<defs>
|
||||
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
||||
<linearGradient id={gradientId} x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="5%" stopColor={color} stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor={color} stopOpacity={0.02} />
|
||||
</linearGradient>
|
||||
@@ -97,17 +103,18 @@ export function GroupMemberGrowth({ data }: Props) {
|
||||
cursor={{ stroke: color, strokeOpacity: 0.3 }}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="cumulative"
|
||||
dot={false}
|
||||
fill={`url(#${gradientId})`}
|
||||
isAnimationActive={false}
|
||||
stroke={color}
|
||||
strokeWidth={2}
|
||||
fill={`url(#${gradientId})`}
|
||||
dot={false}
|
||||
isAnimationActive={false}
|
||||
type="monotone"
|
||||
/>
|
||||
<XAxis {...xAxisProps} />
|
||||
<YAxis {...yAxisProps} domain={[0, 'dataMax']} />
|
||||
<YAxis {...yAxisProps} />
|
||||
<CartesianGrid
|
||||
className="stroke-border"
|
||||
horizontal={true}
|
||||
strokeDasharray="3 3"
|
||||
strokeOpacity={0.5}
|
||||
|
||||
@@ -242,6 +242,7 @@ export default function BillingUsage({ organization }: Props) {
|
||||
<XAxis {...xAxisProps} dataKey="date" />
|
||||
<YAxis {...yAxisProps} domain={[0, 'dataMax']} />
|
||||
<CartesianGrid
|
||||
className="stroke-border"
|
||||
horizontal={true}
|
||||
strokeDasharray="3 3"
|
||||
strokeOpacity={0.5}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Widget } from '@/components/widget';
|
||||
import { ZapIcon } from 'lucide-react';
|
||||
import { Widget, WidgetEmptyState } from '@/components/widget';
|
||||
import { WidgetHead, WidgetTitle } from '../overview/overview-widget';
|
||||
|
||||
type Props = {
|
||||
@@ -6,28 +7,32 @@ type Props = {
|
||||
};
|
||||
|
||||
export const MostEvents = ({ data }: Props) => {
|
||||
const max = Math.max(...data.map((item) => item.count));
|
||||
const max = data.length > 0 ? Math.max(...data.map((item) => item.count)) : 0;
|
||||
return (
|
||||
<Widget className="w-full">
|
||||
<WidgetHead>
|
||||
<WidgetTitle>Popular events</WidgetTitle>
|
||||
</WidgetHead>
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
{data.slice(0, 5).map((item) => (
|
||||
<div key={item.name} className="relative px-3 py-2">
|
||||
<div
|
||||
className="absolute bottom-0 left-0 top-0 rounded bg-def-200"
|
||||
style={{
|
||||
width: `${(item.count / max) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<div className="relative flex justify-between ">
|
||||
<div>{item.name}</div>
|
||||
<div>{item.count}</div>
|
||||
{data.length === 0 ? (
|
||||
<WidgetEmptyState icon={ZapIcon} text="No events yet" />
|
||||
) : (
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
{data.slice(0, 5).map((item) => (
|
||||
<div key={item.name} className="relative px-3 py-2">
|
||||
<div
|
||||
className="absolute bottom-0 left-0 top-0 rounded bg-def-200"
|
||||
style={{
|
||||
width: `${(item.count / max) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<div className="relative flex justify-between ">
|
||||
<div>{item.name}</div>
|
||||
<div>{item.count}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Widget>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Widget } from '@/components/widget';
|
||||
import { RouteIcon } from 'lucide-react';
|
||||
import { Widget, WidgetEmptyState } from '@/components/widget';
|
||||
import { WidgetHead, WidgetTitle } from '../overview/overview-widget';
|
||||
|
||||
type Props = {
|
||||
@@ -6,28 +7,32 @@ type Props = {
|
||||
};
|
||||
|
||||
export const PopularRoutes = ({ data }: Props) => {
|
||||
const max = Math.max(...data.map((item) => item.count));
|
||||
const max = data.length > 0 ? Math.max(...data.map((item) => item.count)) : 0;
|
||||
return (
|
||||
<Widget className="w-full">
|
||||
<WidgetHead>
|
||||
<WidgetTitle>Most visted pages</WidgetTitle>
|
||||
</WidgetHead>
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
{data.slice(0, 5).map((item) => (
|
||||
<div key={item.path} className="relative px-3 py-2">
|
||||
<div
|
||||
className="absolute bottom-0 left-0 top-0 rounded bg-def-200"
|
||||
style={{
|
||||
width: `${(item.count / max) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<div className="relative flex justify-between ">
|
||||
<div>{item.path}</div>
|
||||
<div>{item.count}</div>
|
||||
{data.length === 0 ? (
|
||||
<WidgetEmptyState icon={RouteIcon} text="No pages visited yet" />
|
||||
) : (
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
{data.slice(0, 5).map((item) => (
|
||||
<div key={item.path} className="relative px-3 py-2">
|
||||
<div
|
||||
className="absolute bottom-0 left-0 top-0 rounded bg-def-200"
|
||||
style={{
|
||||
width: `${(item.count / max) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<div className="relative flex justify-between ">
|
||||
<div>{item.path}</div>
|
||||
<div>{item.count}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Widget>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -54,6 +54,19 @@ export function WidgetBody({ children, className }: WidgetBodyProps) {
|
||||
return <div className={cn('p-4', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
export interface WidgetEmptyStateProps {
|
||||
icon: LucideIcon;
|
||||
text: string;
|
||||
}
|
||||
export function WidgetEmptyState({ icon: Icon, text }: WidgetEmptyStateProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-2 py-8 text-muted-foreground">
|
||||
<Icon size={28} strokeWidth={1.5} />
|
||||
<p className="text-sm">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface WidgetProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { MostEvents } from '@/components/profiles/most-events';
|
||||
import { PopularRoutes } from '@/components/profiles/popular-routes';
|
||||
import { ProfileActivity } from '@/components/profiles/profile-activity';
|
||||
import { KeyValueGrid } from '@/components/ui/key-value-grid';
|
||||
import { Widget, WidgetBody } from '@/components/widget';
|
||||
import { Widget, WidgetBody, WidgetEmptyState } from '@/components/widget';
|
||||
import { WidgetTable } from '@/components/widget-table';
|
||||
import { useTRPC } from '@/integrations/trpc/react';
|
||||
import { formatDateTime, formatTimeAgoOrDateTime } from '@/utils/date';
|
||||
@@ -64,7 +64,7 @@ function Component() {
|
||||
);
|
||||
|
||||
const g = group.data;
|
||||
const m = metrics.data?.[0];
|
||||
const m = metrics.data;
|
||||
|
||||
if (!g) {
|
||||
return null;
|
||||
@@ -177,9 +177,7 @@ function Component() {
|
||||
</WidgetHead>
|
||||
<WidgetBody className="p-0">
|
||||
{members.data.length === 0 ? (
|
||||
<p className="py-4 text-center text-muted-foreground text-sm">
|
||||
No members found
|
||||
</p>
|
||||
<WidgetEmptyState icon={UsersIcon} text="No members yet" />
|
||||
) : (
|
||||
<WidgetTable
|
||||
columnClassName="px-2"
|
||||
|
||||
Reference in New Issue
Block a user