ComputeMetric.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { ChevronRight } from 'lucide-react'
  2. import Link from 'next/link'
  3. import { useMemo } from 'react'
  4. import { HoverCard, HoverCardContent, HoverCardTrigger } from 'ui'
  5. import { formatUsage } from '../helpers'
  6. import { Metric } from './BillingBreakdown.constants'
  7. import { ComputeUsageMetric, PricingMetric } from '@/data/analytics/org-daily-stats-query'
  8. import type { OrgUsageResponse } from '@/data/usage/org-usage-query'
  9. import { DOCS_URL } from '@/lib/constants'
  10. import { formatCurrency } from '@/lib/helpers'
  11. export interface ComputeMetricProps {
  12. slug?: string
  13. metric: Metric
  14. usage: OrgUsageResponse
  15. relativeToSubscription: boolean
  16. className?: string
  17. }
  18. export const ComputeMetric = ({
  19. slug,
  20. metric,
  21. usage,
  22. relativeToSubscription,
  23. className,
  24. }: ComputeMetricProps) => {
  25. const usageMeta = usage.usages.find((x) => x.metric === metric.key)
  26. const usageLabel = useMemo(() => {
  27. if (usageMeta?.pricing_free_units) {
  28. return `${usageMeta?.usage?.toLocaleString() ?? 0} / ${
  29. usageMeta?.pricing_free_units ?? 0
  30. } hours`
  31. } else {
  32. return `${usageMeta?.usage?.toLocaleString() ?? 0} hours`
  33. }
  34. }, [usageMeta])
  35. const sortedProjectAllocations = useMemo(() => {
  36. if (!usageMeta || !usageMeta.project_allocations) return []
  37. return usageMeta.project_allocations.sort((a, b) => b.usage - a.usage)
  38. }, [usageMeta])
  39. return (
  40. <HoverCard openDelay={50} closeDelay={200}>
  41. <HoverCardTrigger asChild>
  42. <div className={className}>
  43. <Link href={`/org/${slug}/usage#${metric.anchor}`}>
  44. <div className="group flex items-center space-x-2">
  45. <p className="text-sm text-foreground-light group-hover:text-foreground transition cursor-pointer">
  46. {metric.name}
  47. </p>
  48. <ChevronRight strokeWidth={1.5} size={16} className="transition" />
  49. </div>
  50. </Link>
  51. <span className="text-sm">{usageLabel}</span>&nbsp;
  52. {relativeToSubscription && usageMeta?.cost && usageMeta.cost > 0 ? (
  53. <span className="text-sm" translate="no">
  54. ({formatCurrency(usageMeta?.cost)})
  55. </span>
  56. ) : null}
  57. </div>
  58. </HoverCardTrigger>
  59. <HoverCardContent side="bottom" align="end" className="w-[500px]" animate="slide-in">
  60. <div className="text-sm text-foreground space-y-2">
  61. <p className="font-medium" translate="no">
  62. {usageMeta?.unit_price_desc}
  63. </p>
  64. <div className="my-2">
  65. {usageMeta?.metric === ComputeUsageMetric.COMPUTE_HOURS_BRANCH ? (
  66. <p className="text-sm">
  67. Each Preview branch is a separate environment with all Briven services (Database,
  68. Auth, Storage, etc.).{' '}
  69. <Link
  70. href={`${DOCS_URL}/guides/platform/manage-your-usage/branching`}
  71. target="_blank"
  72. className="transition text-brand hover:text-brand-600 underline"
  73. >
  74. Read more
  75. </Link>
  76. </p>
  77. ) : (
  78. <p className="text-sm">
  79. Every project is a dedicated server and database. For every hour your project is
  80. active, it incurs compute costs based on the compute size of your project. Paused
  81. projects do not incur compute costs.{' '}
  82. <Link
  83. href={`${DOCS_URL}/guides/platform/manage-your-usage/compute`}
  84. target="_blank"
  85. className="transition text-brand hover:text-brand-600 underline"
  86. >
  87. Read more
  88. </Link>
  89. </p>
  90. )}
  91. </div>
  92. {usageMeta && sortedProjectAllocations.length > 0 && (
  93. <table className="list-disc w-full">
  94. <thead>
  95. <tr>
  96. <th className="text-left">Project</th>
  97. <th className="text-right">Usage</th>
  98. </tr>
  99. </thead>
  100. <tbody>
  101. {sortedProjectAllocations.map((allocation) => (
  102. <tr key={`${usageMeta.metric}_${allocation.ref}`}>
  103. <td>{allocation.name}</td>
  104. <td className="text-right">
  105. {formatUsage(usageMeta.metric as PricingMetric, allocation)}
  106. </td>
  107. </tr>
  108. ))}
  109. <tr></tr>
  110. </tbody>
  111. <tfoot>
  112. <tr>
  113. <td className="py-2 border-t text-left">Total (Hours)</td>
  114. <td className="py-2 border-t text-right">
  115. {formatUsage(usageMeta.metric as PricingMetric, {
  116. usage: usageMeta.usage_original,
  117. })}
  118. </td>
  119. </tr>
  120. </tfoot>
  121. </table>
  122. )}
  123. </div>
  124. </HoverCardContent>
  125. </HoverCard>
  126. )
  127. }