feat: dashboard v2, esm, upgrades (#211)
* esm * wip * wip * wip * wip * wip * wip * subscription notice * wip * wip * wip * fix envs * fix: update docker build * fix * esm/types * delete dashboard :D * add patches to dockerfiles * update packages + catalogs + ts * wip * remove native libs * ts * improvements * fix redirects and fetching session * try fix favicon * fixes * fix * order and resize reportds within a dashboard * improvements * wip * added userjot to dashboard * fix * add op * wip * different cache key * improve date picker * fix table * event details loading * redo onboarding completely * fix login * fix * fix * extend session, billing and improve bars * fix * reduce price on 10M
This commit is contained in:
committed by
GitHub
parent
436e81ecc9
commit
81a7e5d62e
155
apps/start/src/components/projects/project-card.tsx
Normal file
155
apps/start/src/components/projects/project-card.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { shortNumber } from '@/hooks/use-numer-formatter';
|
||||
import { useTRPC } from '@/integrations/trpc/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
|
||||
import type { IServiceProject } from '@openpanel/db';
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import { SettingsIcon, TrendingDownIcon, TrendingUpIcon } from 'lucide-react';
|
||||
import { ChartSSR } from '../chart-ssr';
|
||||
import { FadeIn } from '../fade-in';
|
||||
import { SerieIcon } from '../report-chart/common/serie-icon';
|
||||
import { Skeleton } from '../skeleton';
|
||||
import { LinkButton } from '../ui/button';
|
||||
|
||||
export function ProjectCardRoot({
|
||||
children,
|
||||
className,
|
||||
}: { children: React.ReactNode; className?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative card hover:-translate-y-px hover:shadow-sm',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProjectCardSkeleton() {
|
||||
return (
|
||||
<ProjectCardRoot className="aspect-[340/116.25] p-4 col">
|
||||
<Skeleton className="h-5 w-full" />
|
||||
<div className="row mt-auto gap-4 w-1/2 ml-auto">
|
||||
<Skeleton className="h-3 w-full" />
|
||||
<Skeleton className="h-3 w-full" />
|
||||
<Skeleton className="h-3 w-full" />
|
||||
</div>
|
||||
</ProjectCardRoot>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectCard({ id, domain, name, organizationId }: IServiceProject) {
|
||||
return (
|
||||
<ProjectCardRoot>
|
||||
<Link
|
||||
to="/$organizationId/$projectId"
|
||||
params={{
|
||||
organizationId,
|
||||
projectId: id,
|
||||
}}
|
||||
className="col p-4 transition-transform"
|
||||
>
|
||||
<div className="font-medium flex items-center gap-2 text-lg pb-2">
|
||||
<div className="row gap-2 flex-1">
|
||||
{domain && <SerieIcon name={domain ?? ''} />}
|
||||
{name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mx-4 aspect-[8/1] mb-4">
|
||||
<ProjectChart id={id} />
|
||||
</div>
|
||||
<div className="flex flex-1 gap-4 h-9 md:h-4">
|
||||
<ProjectMetrics id={id} />
|
||||
</div>
|
||||
</Link>
|
||||
<LinkButton
|
||||
variant="ghost"
|
||||
href={`/${organizationId}/${id}/settings`}
|
||||
className="text-muted-foreground absolute top-2 right-2"
|
||||
>
|
||||
<SettingsIcon size={16} />
|
||||
</LinkButton>
|
||||
</ProjectCardRoot>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectChart({ id }: { id: string }) {
|
||||
const trpc = useTRPC();
|
||||
const { data } = useQuery(
|
||||
trpc.chart.projectCard.queryOptions({
|
||||
projectId: id,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<FadeIn className="h-full w-full">
|
||||
<ChartSSR data={data?.chart || []} color={'blue'} />
|
||||
</FadeIn>
|
||||
);
|
||||
}
|
||||
|
||||
function Metric({ value, label }: { value: React.ReactNode; label: string }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 md:flex-row items-center">
|
||||
<div className="text-muted-foreground text-xs">{label}</div>
|
||||
<span className="font-semibold">{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectMetrics({ id }: { id: string }) {
|
||||
const trpc = useTRPC();
|
||||
const { data } = useQuery(
|
||||
trpc.chart.projectCard.queryOptions({
|
||||
projectId: id,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<FadeIn className="flex gap-8 flex-1">
|
||||
<div className="flex-1 items-center gap-2 row">
|
||||
{typeof data?.trend?.percentage === 'number' && (
|
||||
<Metric
|
||||
label="3M DIFF"
|
||||
value={
|
||||
<span
|
||||
className={cn(
|
||||
'font-semibold',
|
||||
'row gap-1 items-center',
|
||||
data?.trend?.direction === 'up'
|
||||
? 'text-emerald-300'
|
||||
: data?.trend?.direction === 'down'
|
||||
? 'text-orange-300'
|
||||
: 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{data.trend.direction === 'up' && (
|
||||
<TrendingUpIcon className="size-4" />
|
||||
)}
|
||||
{data.trend.direction === 'down' && (
|
||||
<TrendingDownIcon className="size-4" />
|
||||
)}
|
||||
{Math.abs(data.trend.percentage)}%
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Metric
|
||||
label="3M"
|
||||
value={shortNumber('en')(data?.metrics?.months_3 ?? 0)}
|
||||
/>
|
||||
<Metric
|
||||
label="30D"
|
||||
value={shortNumber('en')(data?.metrics?.month ?? 0)}
|
||||
/>
|
||||
<Metric label="24H" value={shortNumber('en')(data?.metrics?.day ?? 0)} />
|
||||
</FadeIn>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectCard;
|
||||
Reference in New Issue
Block a user