minor ui improvements for profile

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-05-13 16:08:40 +02:00
parent b91e0d0aa9
commit c4b02108ac
7 changed files with 96 additions and 72 deletions

View File

@@ -1,29 +1,25 @@
import { Suspense, useMemo } from 'react';
import { Suspense } from 'react';
import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import ClickToCopy from '@/components/click-to-copy';
import { ListPropertiesIcon } from '@/components/events/list-properties-icon';
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
import { ProfileAvatar } from '@/components/profiles/profile-avatar';
import { ChartSwitch } from '@/components/report/chart';
import { SerieIcon } from '@/components/report/chart/SerieIcon';
import { Tooltiper } from '@/components/ui/tooltip';
import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
import {
eventQueryFiltersParser,
eventQueryNamesFilter,
} from '@/hooks/useEventQueryFilters';
import { clipboard } from '@/utils/clipboard';
import { getProfileName } from '@/utils/getters';
import { CopyIcon } from 'lucide-react';
import { notFound } from 'next/navigation';
import { parseAsInteger, parseAsString } from 'nuqs';
import { toast } from 'sonner';
import type { GetEventListOptions } from '@openpanel/db';
import {
getConversionEventNames,
getEventList,
getEventsCount,
getProfileById,
getProfileMetrics,
} from '@openpanel/db';
import type { IChartEvent, IChartInput } from '@openpanel/validation';
import { getEventList, getEventsCount, getProfileById } from '@openpanel/db';
import type { IChartInput } from '@openpanel/validation';
import { EventList } from '../../events/event-list';
import { StickyBelowHeader } from '../../layout-sticky-below-header';
@@ -147,10 +143,12 @@ export default async function Page({
<div className="flex flex-1 gap-4">
<ProfileAvatar {...profile} size={'lg'} />
<div className="min-w-0">
<h1 className="max-w-full overflow-hidden text-ellipsis break-words text-lg font-semibold md:max-w-sm md:whitespace-nowrap md:text-2xl">
{getProfileName(profile)}
</h1>
<div className="flex items-center gap-4">
<ClickToCopy value={profile.id}>
<h1 className="max-w-full overflow-hidden text-ellipsis break-words text-lg font-semibold md:max-w-sm md:whitespace-nowrap md:text-2xl">
{getProfileName(profile)}
</h1>
</ClickToCopy>
<div className="mt-1 flex items-center gap-4">
<ListPropertiesIcon {...profile.properties} />
</div>
</div>

View File

@@ -50,6 +50,7 @@ export function ProfileList({ data, count, limit = 50 }: ProfileListProps) {
<Link
href={`/${organizationSlug}/${projectId}/profiles/${profile.id}`}
className="flex items-center gap-2 font-medium"
title={getProfileName(profile, false)}
>
<ProfileAvatar size="sm" {...profile} />
{getProfileName(profile)}

View File

@@ -0,0 +1,30 @@
'use client';
import { clipboard } from '@/utils/clipboard';
import { toast } from 'sonner';
import { Tooltiper } from './ui/tooltip';
type Props = {
children: React.ReactNode;
className?: string;
value: string;
};
const ClickToCopy = ({ children, value }: Props) => {
return (
<Tooltiper
content="Click to copy"
asChild
className="cursor-pointer"
onClick={() => {
clipboard(value);
toast('Copied to clipboard');
}}
>
{children}
</Tooltiper>
);
};
export default ClickToCopy;

View File

@@ -1,5 +1,10 @@
import { SerieIcon } from '../report/chart/SerieIcon';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
import {
Tooltip,
TooltipContent,
Tooltiper,
TooltipTrigger,
} from '../ui/tooltip';
interface Props {
country?: string;
@@ -23,46 +28,26 @@ export function ListPropertiesIcon({
referrer_type,
}: Props) {
return (
<div className="flex gap-1">
<div className="flex gap-1.5">
{country && (
<Tooltip>
<TooltipTrigger>
<SerieIcon name={country} />
</TooltipTrigger>
<TooltipContent>
{country}, {city}
</TooltipContent>
</Tooltip>
<Tooltiper content={[country, city].filter(Boolean).join(', ')}>
<SerieIcon name={country} />
</Tooltiper>
)}
{os && (
<Tooltip>
<TooltipTrigger>
<SerieIcon name={os} />
</TooltipTrigger>
<TooltipContent>
{os} ({os_version})
</TooltipContent>
</Tooltip>
<Tooltiper content={`${os} (${os_version})`}>
<SerieIcon name={os} />
</Tooltiper>
)}
{browser && (
<Tooltip>
<TooltipTrigger>
<SerieIcon name={browser} />
</TooltipTrigger>
<TooltipContent>
{browser} ({browser_version})
</TooltipContent>
</Tooltip>
<Tooltiper content={`${browser} (${browser_version})`}>
<SerieIcon name={browser} />
</Tooltiper>
)}
{referrer_name && (
<Tooltip>
<TooltipTrigger>
<SerieIcon name={referrer_name} />
</TooltipTrigger>
<TooltipContent>
{referrer_name} ({referrer_type})
</TooltipContent>
</Tooltip>
<Tooltiper content={`${referrer_name} (${referrer_type})`}>
<SerieIcon name={referrer_name} />
</Tooltiper>
)}
</div>
);

View File

@@ -38,20 +38,22 @@ interface TooltiperProps {
content: string;
children: React.ReactNode;
className?: string;
onClick?: () => void;
}
export function Tooltiper({
asChild,
content,
children,
className,
onClick,
}: TooltiperProps) {
return (
<Tooltip>
<TooltipTrigger asChild={asChild} className={className}>
<Tooltip delayDuration={0}>
<TooltipTrigger asChild={asChild} className={className} onClick={onClick}>
{children}
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>{content}</TooltipContent>
<TooltipContent sideOffset={10}>{content}</TooltipContent>
</TooltipPortal>
</Tooltip>
);

View File

@@ -36,26 +36,28 @@ export function WidgetTable<T>({
keyExtractor,
}: Props<T>) {
return (
<table className={cn('w-full', className)}>
<WidgetTableHead>
<tr>
{columns.map((column) => (
<th key={column.name}>{column.name}</th>
))}
</tr>
</WidgetTableHead>
<tbody>
{data.map((item) => (
<tr
key={keyExtractor(item)}
className="border-b border-border text-right text-sm last:border-0 [&_td:first-child]:text-left [&_td]:p-4"
>
<div className="w-full overflow-x-auto">
<table className={cn('w-full', className)}>
<WidgetTableHead>
<tr>
{columns.map((column) => (
<td key={column.name}>{column.render(item)}</td>
<th key={column.name}>{column.name}</th>
))}
</tr>
))}
</tbody>
</table>
</WidgetTableHead>
<tbody>
{data.map((item) => (
<tr
key={keyExtractor(item)}
className="border-b border-border text-right text-sm last:border-0 [&_td:first-child]:text-left [&_td]:p-4"
>
{columns.map((column) => (
<td key={column.name}>{column.render(item)}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}

View File

@@ -1,9 +1,15 @@
import type { IServiceProfile } from '@openpanel/db';
export function getProfileName(profile: IServiceProfile | undefined | null) {
export function getProfileName(
profile: IServiceProfile | undefined | null,
short = true
) {
if (!profile) return 'Unknown';
if (!profile.isExternal) {
if (short) {
return profile.id.slice(0, 4) + '...' + profile.id.slice(-4);
}
return profile.id;
}