minor ui improvements for profile
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
30
apps/dashboard/src/components/click-to-copy.tsx
Normal file
30
apps/dashboard/src/components/click-to-copy.tsx
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user