241 lines
5.8 KiB
TypeScript
241 lines
5.8 KiB
TypeScript
import Link from 'next/link';
|
|
import React from 'react';
|
|
|
|
import BaseIcon from '../BaseIcon';
|
|
|
|
export const actionButtonClassName =
|
|
'inline-flex items-center justify-center rounded-[8px] border px-4 py-2 text-sm font-medium';
|
|
|
|
export const inputClassName =
|
|
'w-full rounded-[10px] border border-slate-200 bg-white px-4 py-3 text-[14px] text-slate-900 outline-none transition-colors placeholder:text-slate-400 focus:border-slate-300';
|
|
|
|
export const textAreaClassName = `${inputClassName} min-h-[132px] resize-y leading-6`;
|
|
|
|
export function formatDateTime(value?: string | null) {
|
|
if (!value) {
|
|
return 'No date';
|
|
}
|
|
|
|
const date = new Date(value);
|
|
|
|
if (Number.isNaN(date.getTime())) {
|
|
return value;
|
|
}
|
|
|
|
return date.toLocaleString('en-US', {
|
|
day: 'numeric',
|
|
hour: 'numeric',
|
|
minute: '2-digit',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
});
|
|
}
|
|
|
|
export function formatShortDate(value?: string | null) {
|
|
if (!value) {
|
|
return 'No activity yet';
|
|
}
|
|
|
|
const date = new Date(value);
|
|
|
|
if (Number.isNaN(date.getTime())) {
|
|
return value;
|
|
}
|
|
|
|
return date.toLocaleString('en-US', {
|
|
day: 'numeric',
|
|
hour: 'numeric',
|
|
minute: '2-digit',
|
|
month: 'short',
|
|
});
|
|
}
|
|
|
|
export function formatRole(value?: string | null) {
|
|
if (!value) {
|
|
return 'Unknown';
|
|
}
|
|
|
|
return value
|
|
.replace(/_/g, ' ')
|
|
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
}
|
|
|
|
export function formatName(person: any) {
|
|
if (!person) {
|
|
return 'No user';
|
|
}
|
|
|
|
const fullName = [person.firstName, person.lastName].filter(Boolean).join(' ').trim();
|
|
|
|
if (fullName) {
|
|
return fullName;
|
|
}
|
|
|
|
if (person.email) {
|
|
return person.email;
|
|
}
|
|
|
|
if (person.name) {
|
|
return person.name;
|
|
}
|
|
|
|
if (person.title) {
|
|
return person.title;
|
|
}
|
|
|
|
return 'Unnamed record';
|
|
}
|
|
|
|
export function formatMoney(value: any) {
|
|
const number = Number(value || 0);
|
|
|
|
if (!number) {
|
|
return '$0.00';
|
|
}
|
|
|
|
return `$${number.toFixed(2)}`;
|
|
}
|
|
|
|
export function formatTokens(value: any) {
|
|
const number = Number(value || 0);
|
|
|
|
if (!number) {
|
|
return '0';
|
|
}
|
|
|
|
return number.toLocaleString('en-US');
|
|
}
|
|
|
|
export function EntityIntro({
|
|
backHref,
|
|
backLabel,
|
|
description,
|
|
kicker,
|
|
title,
|
|
}: {
|
|
backHref: string;
|
|
backLabel: string;
|
|
description: string;
|
|
kicker: string;
|
|
title: string;
|
|
}) {
|
|
return (
|
|
<div className="rounded-[12px] border border-slate-200 bg-white px-6 py-6">
|
|
<Link className="inline-flex items-center gap-2 text-[12px] font-medium text-slate-500" href={backHref}>
|
|
{backLabel}
|
|
</Link>
|
|
<p className="mt-4 text-[11px] font-medium uppercase tracking-[0.28em] text-slate-400">{kicker}</p>
|
|
<h1 className="mt-3 text-[2rem] font-semibold tracking-[-0.04em] text-slate-900">{title}</h1>
|
|
<p className="mt-3 max-w-3xl text-sm leading-6 text-slate-500">{description}</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function EntitySection({
|
|
children,
|
|
description,
|
|
icon,
|
|
title,
|
|
}: {
|
|
children: React.ReactNode;
|
|
description: string;
|
|
icon: string;
|
|
title: string;
|
|
}) {
|
|
return (
|
|
<div className="rounded-[12px] border border-slate-200 bg-white px-6 py-6">
|
|
<div className="mb-5 flex items-start gap-3">
|
|
<div className="mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-[10px] border border-slate-200 bg-slate-50 text-slate-600">
|
|
<BaseIcon path={icon} size={20} />
|
|
</div>
|
|
<div>
|
|
<p className="text-[15px] font-medium text-slate-900">{title}</p>
|
|
<p className="mt-1 text-sm leading-6 text-slate-500">{description}</p>
|
|
</div>
|
|
</div>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function EntityValueCard({
|
|
label,
|
|
value,
|
|
}: {
|
|
label: string;
|
|
value: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="rounded-[10px] border border-slate-200 px-4 py-3">
|
|
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">{label}</p>
|
|
<div className="mt-2 text-[15px] leading-6 text-slate-900">{value}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function EntityLinkCard({
|
|
description,
|
|
href,
|
|
icon,
|
|
label,
|
|
value,
|
|
}: {
|
|
description: string;
|
|
href?: string;
|
|
icon: string;
|
|
label: string;
|
|
value: string;
|
|
}) {
|
|
const content = (
|
|
<div className="rounded-[10px] border border-slate-200 px-4 py-3 transition-colors hover:border-slate-300">
|
|
<div className="flex items-start gap-3">
|
|
<div className="mt-0.5 inline-flex h-9 w-9 items-center justify-center rounded-[10px] border border-slate-200 bg-slate-50 text-slate-600">
|
|
<BaseIcon path={icon} size={18} />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-slate-400">{label}</p>
|
|
<p className="mt-2 text-[15px] font-medium text-slate-900">{value}</p>
|
|
<p className="mt-1 text-[13px] leading-6 text-slate-500">{description}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
if (!href) {
|
|
return content;
|
|
}
|
|
|
|
return <Link href={href}>{content}</Link>;
|
|
}
|
|
|
|
export function EntityAsideCard({
|
|
children,
|
|
title,
|
|
}: {
|
|
children: React.ReactNode;
|
|
title: string;
|
|
}) {
|
|
return (
|
|
<div className="rounded-[12px] border border-slate-200 bg-white px-5 py-5">
|
|
<p className="text-[11px] font-medium uppercase tracking-[0.28em] text-slate-400">{title}</p>
|
|
<div className="mt-4">{children}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function EntityEmptyState({
|
|
description,
|
|
title,
|
|
}: {
|
|
description: string;
|
|
title: string;
|
|
}) {
|
|
return (
|
|
<div className="rounded-[12px] border border-dashed border-slate-200 bg-slate-50 px-6 py-12 text-center">
|
|
<p className="text-[11px] font-medium uppercase tracking-[0.2em] text-slate-400">No data</p>
|
|
<h2 className="mt-3 text-[22px] font-semibold tracking-[-0.03em] text-slate-900">{title}</h2>
|
|
<p className="mt-3 text-[14px] leading-6 text-slate-500">{description}</p>
|
|
</div>
|
|
);
|
|
}
|