import { currentMonthlyPeriod } from '@documenso/lib/universal/monthly-period'; import { getQuotaUsagePercent, isQuotaExceeded, isQuotaNearing, normalizeCapacityLimit, } from '@documenso/lib/universal/quota-usage'; import { cn } from '@documenso/ui/lib/utils'; import type { BadgeProps } from '@documenso/ui/primitives/badge'; import { Badge } from '@documenso/ui/primitives/badge'; import { Progress } from '@documenso/ui/primitives/progress'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select'; import { Trans } from '@lingui/react/macro'; import type { OrganisationClaim, OrganisationMonthlyStat } from '@prisma/client'; import type { LucideIcon } from 'lucide-react'; import { FileIcon, MailIcon, MailOpenIcon, PlugIcon, UsersIcon, UsersRoundIcon } from 'lucide-react'; import type { ReactNode } from 'react'; import { useId, useState } from 'react'; import { OrganisationUsageResetButton } from './organisation-usage-reset-button'; type CapacityUsage = { members: number; teams: number; }; type UsageRow = { counter: 'document' | 'email' | 'api'; label: ReactNode; icon: LucideIcon; used: number; effectiveLimit: number | null; }; type OrganisationUsagePanelProps = { organisationId: string; monthlyStats: Pick< OrganisationMonthlyStat, 'period' | 'documentCount' | 'emailCount' | 'apiCount' | 'emailReports' >[]; organisationClaim: OrganisationClaim; capacityUsage?: CapacityUsage; }; type UsageCardState = { status: { label: ReactNode; variant: NonNullable; }; percent: number; hasFiniteLimit: boolean; progressClassName: string; subtext: ReactNode; }; type UsageCardStateOptions = { used: number; limit: number | null | undefined; footnote?: ReactNode; }; const getUsageCardState = ({ used, limit, footnote }: UsageCardStateOptions): UsageCardState => { const percent = getQuotaUsagePercent(used, limit ?? null); const hasFiniteLimit = Boolean(limit && limit > 0); if (limit === null || limit === undefined) { return { status: { label: Unlimited, variant: 'neutral' }, percent, hasFiniteLimit, progressClassName: '', subtext: footnote ?? null, }; } if (limit === 0) { return { status: { label: Blocked, variant: 'destructive' }, percent, hasFiniteLimit, progressClassName: '', subtext: footnote ?? Resource blocked, }; } if (used > limit) { return { status: { label: Exceeded, variant: 'destructive' }, percent, hasFiniteLimit, progressClassName: '[&>div]:bg-destructive', subtext: footnote ?? null, }; } if (isQuotaExceeded(limit, used)) { return { status: { label: Limit reached, variant: 'orange' }, percent, hasFiniteLimit, progressClassName: '[&>div]:bg-orange-500 dark:[&>div]:bg-orange-400', subtext: footnote ?? null, }; } if (isQuotaNearing(limit, used)) { return { status: { label: Near limit, variant: 'warning' }, percent, hasFiniteLimit, progressClassName: '[&>div]:bg-yellow-500 dark:[&>div]:bg-yellow-400', subtext: footnote ?? null, }; } return { status: { label: Within limit, variant: 'default' }, percent, hasFiniteLimit, progressClassName: '', subtext: footnote ?? null, }; }; type UsageStatCardProps = { label: ReactNode; icon: LucideIcon; used: number; limit: number | null | undefined; /** When true the card is a plain counter with no limit, status or progress. */ countOnly?: boolean; footnote?: ReactNode; action?: ReactNode; }; const UsageStatCard = ({ label, icon: Icon, used, limit, countOnly = false, footnote, action }: UsageStatCardProps) => { const { status, percent, hasFiniteLimit, progressClassName, subtext } = getUsageCardState({ used, limit, footnote }); return (
{label}
{!countOnly && ( {status.label} )}
{used.toLocaleString()} {hasFiniteLimit ? ( / {limit?.toLocaleString()} ) : null}
{hasFiniteLimit ? ( {percent}% ) : null}
{hasFiniteLimit ? : null} {subtext ?

{subtext}

: null}
{action ?
{action}
: null}
); }; export const OrganisationUsagePanel = ({ organisationId, monthlyStats, organisationClaim, capacityUsage, }: OrganisationUsagePanelProps) => { const monthlyUsagePeriodId = useId(); const [selectedPeriod, setSelectedPeriod] = useState(() => monthlyStats[0]?.period); const selectedStat = monthlyStats.find((stat) => stat.period === selectedPeriod) ?? monthlyStats[0]; // Resetting a counter only affects the current month (the server hardcodes the // current period), so only offer the reset action when viewing the current month. const isCurrentPeriod = selectedStat?.period === currentMonthlyPeriod(); const capacityRows = capacityUsage ? [ { key: 'members', label: Members, icon: UsersIcon, used: capacityUsage.members, limit: normalizeCapacityLimit(organisationClaim.memberCount), }, { key: 'teams', label: Teams, icon: UsersRoundIcon, used: capacityUsage.teams, limit: normalizeCapacityLimit(organisationClaim.teamCount), }, ] : []; const monthlyRows: UsageRow[] = [ { counter: 'document', label: Documents, icon: FileIcon, used: selectedStat?.documentCount ?? 0, effectiveLimit: organisationClaim.documentQuota, }, { counter: 'email', label: Emails, icon: MailIcon, used: selectedStat?.emailCount ?? 0, effectiveLimit: organisationClaim.emailQuota, }, { counter: 'api', label: API requests, icon: PlugIcon, used: selectedStat?.apiCount ?? 0, effectiveLimit: organisationClaim.apiQuota, }, ]; return (
{capacityRows.length > 0 ? (
{capacityRows.map((row) => ( ))}
) : null}

Monthly usage

{monthlyStats.length > 0 ? ( ) : null}
{monthlyRows.map((row) => ( ) : undefined } /> ))} Reports} icon={MailOpenIcon} used={selectedStat?.emailReports ?? 0} limit={null} countOnly footnote={Sent this period} />
); };