diff --git a/apps/web/modules/insights/insights-view.tsx b/apps/web/modules/insights/insights-view.tsx index fd70af8627bcad..68e7357d4e0e58 100644 --- a/apps/web/modules/insights/insights-view.tsx +++ b/apps/web/modules/insights/insights-view.tsx @@ -22,7 +22,6 @@ import { RecentFeedbackTable, TimezoneBadge, } from "@calcom/features/insights/components/booking"; -import "@calcom/features/insights/components/tremor.css"; import { InsightsOrgTeamsProvider } from "@calcom/features/insights/context/InsightsOrgTeamsProvider"; import { Download } from "@calcom/features/insights/filters/Download"; import { OrgTeamsFilter } from "@calcom/features/insights/filters/OrgTeamsFilter"; diff --git a/apps/web/modules/settings/billing/components/BillingCredits.tsx b/apps/web/modules/settings/billing/components/BillingCredits.tsx index 6a83fb8022b910..36ae8b24453d02 100644 --- a/apps/web/modules/settings/billing/components/BillingCredits.tsx +++ b/apps/web/modules/settings/billing/components/BillingCredits.tsx @@ -1,6 +1,5 @@ "use client"; -import { ProgressBar } from "@tremor/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useState, useMemo } from "react"; @@ -16,6 +15,7 @@ import { trpc } from "@calcom/trpc/react"; import { Button } from "@calcom/ui/components/button"; import { Select } from "@calcom/ui/components/form"; import { TextField, Label, InputError } from "@calcom/ui/components/form"; +import { ProgressBar } from "@calcom/ui/components/progress-bar"; import { showToast } from "@calcom/ui/toast"; import { BillingCreditsSkeleton } from "./BillingCreditsSkeleton"; diff --git a/apps/web/package.json b/apps/web/package.json index 9587c1d2254db5..3513ce1d7992e2 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -76,7 +76,6 @@ "@stripe/stripe-js": "^1.35.0", "@tanstack/react-query": "^5.17.15", "@team-plain/typescript-sdk": "^5.9.0", - "@tremor/react": "^2.11.0", "@types/turndown": "^5.0.1", "@unkey/ratelimit": "^0.1.1", "@upstash/redis": "^1.21.0", diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index a1facd6724b796..e9e7a07c8d1e41 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -3402,6 +3402,7 @@ "license_key_saved": "License key saved successfully", "timezone_mismatch_tooltip": "You are viewing the report based on your profile timezone ({{userTimezone}}), while your browser is set to timezone ({{browserTimezone}})", "failed_bookings_by_field": "Failed Bookings By Field", + "failed_bookings": "Failed bookings", "event_type_no_hosts": "No hosts are assigned to event type", "cache_status": "Cache Status", "cache_last_updated": "Last updated: {{timestamp}}", @@ -3429,6 +3430,7 @@ "usage_based_expiration_description": "This link can be used for {{count}} booking", "usage_based_generic_expiration_description": "This link can be configured to expire after a set number of bookings", "usage_based_expiration_description_plural": "This link can be used for {{count}} bookings", + "stats": "Stats", "booking_status": "Booking status", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index 93c21d259788bf..542bfcb66386f7 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -2,10 +2,6 @@ const base = require("@calcom/config/tailwind-preset"); /** @type {import('tailwindcss').Config} */ module.exports = { ...base, - content: [ - ...base.content, - "../../packages/app-store/routing-forms/**/*.{js,ts,jsx,tsx}", - "../../node_modules/@tremor/**/*.{js,ts,jsx,tsx}", - ], + content: [...base.content, "../../packages/app-store/routing-forms/**/*.{js,ts,jsx,tsx}"], plugins: [...base.plugins, require("tailwindcss-animate")], }; diff --git a/packages/features/insights/components/Card.tsx b/packages/features/insights/components/Card.tsx deleted file mode 100644 index 33b33fa9f1dd0e..00000000000000 --- a/packages/features/insights/components/Card.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Card } from "@tremor/react"; - -import classNames from "@calcom/ui/classNames"; - -interface ICardProps { - children: React.ReactNode; - className?: string; -} - -export const CardInsights = (props: ICardProps) => { - const { children, className = "", ...rest } = props; - - return ( - - {children} - - ); -}; diff --git a/packages/features/insights/components/ChartCard.tsx b/packages/features/insights/components/ChartCard.tsx index d2ae63fd791a9b..63ca947923dec9 100644 --- a/packages/features/insights/components/ChartCard.tsx +++ b/packages/features/insights/components/ChartCard.tsx @@ -21,7 +21,7 @@ export function ChartCard({ legendSize, children, }: { - title: string; + title: string | React.ReactNode; subtitle?: string; cta?: { label: string; onClick: () => void }; legend?: Array; @@ -31,7 +31,11 @@ export function ChartCard({ return (
-

{title}

+ {typeof title === "string" ? ( +

{title}

+ ) : ( + title + )}
{legend && legend.length > 0 && } {cta && ( @@ -80,13 +84,13 @@ function Legend({ items, size = "default" }: { items: LegendItem[]; size?: Legen {items.map((item, index) => (

{item.label} diff --git a/packages/features/insights/components/KPICard.tsx b/packages/features/insights/components/KPICard.tsx index 6f1d1ed444fa6f..04150a4e7b3849 100644 --- a/packages/features/insights/components/KPICard.tsx +++ b/packages/features/insights/components/KPICard.tsx @@ -1,11 +1,11 @@ "use client"; -import { Flex, Text, Metric, BadgeDelta } from "@tremor/react"; - import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Badge } from "@calcom/ui/components/badge"; +import { Icon } from "@calcom/ui/components/icon"; import { Tooltip } from "@calcom/ui/components/tooltip"; -import { calculateDeltaType, colors, valueFormatter } from "../lib"; +import { calculateDeltaType, valueFormatter } from "../lib"; export const KPICard = ({ title, @@ -13,7 +13,6 @@ export const KPICard = ({ previousDateRange, }: { title: string; - value: number; previousMetricData: { count: number; deltaPrevious: number; @@ -21,22 +20,54 @@ export const KPICard = ({ previousDateRange: { startDate: string; endDate: string }; }) => { const { t } = useLocale(); + + const deltaType = calculateDeltaType(previousMetricData.deltaPrevious - previousMetricData.count); + const deltaValue = Number(previousMetricData.deltaPrevious).toFixed(0); + + const getBadgeVariant = (type: string) => { + switch (type) { + case "increase": + case "moderateIncrease": + return "success" as const; + case "decrease": + case "moderateDecrease": + return "error" as const; + case "unchanged": + default: + return "gray" as const; + } + }; + + // Get appropriate icon for delta + const getDeltaIcon = (type: string) => { + switch (type) { + case "increase": + case "moderateIncrease": + return "arrow-up" as const; + case "decrease": + case "moderateDecrease": + return "arrow-down" as const; + case "unchanged": + default: + return null; + } + }; + + const badgeVariant = getBadgeVariant(deltaType); + const deltaIcon = getDeltaIcon(deltaType); + return (

- {title} - - {valueFormatter(previousMetricData.count)} - - - - - - {Number(previousMetricData.deltaPrevious).toFixed(0)}% - - +
{title}
+
+
{valueFormatter(previousMetricData.count)}
+
+
+ + {deltaIcon && } + {deltaValue}% + +
- - +
+
); }; diff --git a/packages/features/insights/components/LineChart.tsx b/packages/features/insights/components/LineChart.tsx deleted file mode 100644 index 165a9df811b415..00000000000000 --- a/packages/features/insights/components/LineChart.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { LineChart as ExternalLineChart } from "@tremor/react"; - -import type { LineChartProps } from "./tremor.types"; - -// Honestly this is a mess. Why are all chart libraries in existance horrible to theme -export const LineChart = (props: LineChartProps) => { - return ; -}; diff --git a/packages/features/insights/components/LoadingInsights.tsx b/packages/features/insights/components/LoadingInsights.tsx index 0a937154104a11..a2b549c0a7322e 100644 --- a/packages/features/insights/components/LoadingInsights.tsx +++ b/packages/features/insights/components/LoadingInsights.tsx @@ -1,26 +1,25 @@ -import classNames from "@calcom/ui/classNames"; import { SkeletonText } from "@calcom/ui/components/skeleton"; -import { CardInsights } from "./Card"; +import { ChartCard } from "./ChartCard"; export const LoadingInsight = () => { return ( - - + }>
- - + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + className="animate-spin"> +
-
+ ); }; diff --git a/packages/features/insights/components/UserStatsTable.tsx b/packages/features/insights/components/UserStatsTable.tsx index 6783a651fb1d5a..d61f4911810ba0 100644 --- a/packages/features/insights/components/UserStatsTable.tsx +++ b/packages/features/insights/components/UserStatsTable.tsx @@ -2,25 +2,24 @@ import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import type { User } from "@calcom/prisma/client"; import { Avatar } from "@calcom/ui/components/avatar"; import { ChartCardItem } from "./ChartCard"; -export const UserStatsTable = ({ - data, -}: { - data: - | { - userId: number | null; - user: Pick; - emailMd5?: string; - count?: number; - averageRating?: number | null; - username?: string; - }[] - | undefined; -}) => { +type UserStatsData = { + userId: number; + user: { + id: number; + username: string | null; + name: string | null; + email: string; + avatarUrl: string; + }; + emailMd5: string; + count: number; +}[]; + +export const UserStatsTable = ({ data }: { data: UserStatsData }) => { const { t } = useLocale(); // Filter out items without user data @@ -32,7 +31,7 @@ export const UserStatsTable = ({ filteredData.map((item) => (
{ + if (!minutes) return "0m"; + + const hours = Math.floor(minutes / 60); + const remainingMinutes = minutes % 60; + const remainingMinutesStr = remainingMinutes.toFixed(1); + + if (hours > 0 && remainingMinutes > 0) { + return `${hours}h ${remainingMinutesStr}m`; + } else if (hours > 0) { + return `${hours}h`; + } else { + return `${remainingMinutesStr}m`; + } +}; + +// Custom Tooltip component +const CustomTooltip = ({ + active, + payload, + label, +}: { + active?: boolean; + payload?: Array<{ + value: number; + dataKey: string; + name: string; + color: string; + payload: AverageEventDurationData; + }>; + label?: string; +}) => { + if (!active || !payload?.length) { + return null; + } + + return ( +
+

{label}

+ {payload.map((entry, index: number) => ( +

+ {entry.name}: {formatDuration(entry.value)} +

+ ))} +
+ ); +}; + export const AverageEventDurationChart = () => { const { t } = useLocale(); const insightsBookingParams = useInsightsBookingParameters(); @@ -36,14 +92,32 @@ export const AverageEventDurationChart = () => {
)} {data && data.length > 0 && !isNoData && ( - +
+ + + + + + } /> + + + +
)} ); diff --git a/packages/features/insights/components/booking/BookingKPICards.tsx b/packages/features/insights/components/booking/BookingKPICards.tsx index 43d24f85230b3d..3bfcd174b31853 100644 --- a/packages/features/insights/components/booking/BookingKPICards.tsx +++ b/packages/features/insights/components/booking/BookingKPICards.tsx @@ -84,7 +84,6 @@ export const BookingKPICards = () => { @@ -99,7 +98,6 @@ export const BookingKPICards = () => { diff --git a/packages/features/insights/components/booking/BookingsByHourChart.tsx b/packages/features/insights/components/booking/BookingsByHourChart.tsx index d3b1f27b98a1e8..d839afbc67b942 100644 --- a/packages/features/insights/components/booking/BookingsByHourChart.tsx +++ b/packages/features/insights/components/booking/BookingsByHourChart.tsx @@ -13,15 +13,13 @@ import { import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; +import type { RouterOutputs } from "@calcom/trpc/react"; import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters"; import { ChartCard } from "../ChartCard"; import { LoadingInsight } from "../LoadingInsights"; -type BookingsByHourData = { - hour: number; - count: number; -}; +type BookingsByHourData = RouterOutputs["viewer"]["insights"]["bookingsByHourStats"][number]; export const BookingsByHourChartContent = ({ data }: { data: BookingsByHourData[] }) => { const { t } = useLocale(); diff --git a/packages/features/insights/components/booking/EventTrendsChart.tsx b/packages/features/insights/components/booking/EventTrendsChart.tsx index a15cc380c59bb2..9d9f2f95078c7f 100644 --- a/packages/features/insights/components/booking/EventTrendsChart.tsx +++ b/packages/features/insights/components/booking/EventTrendsChart.tsx @@ -1,14 +1,68 @@ "use client"; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"; + import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; +import type { RouterOutputs } from "@calcom/trpc/react"; import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters"; import { valueFormatter } from "../../lib/valueFormatter"; import { ChartCard } from "../ChartCard"; -import { LineChart } from "../LineChart"; import { LoadingInsight } from "../LoadingInsights"; +const COLOR = { + CREATED: "#a855f7", + COMPLETED: "#22c55e", + RESCHEDULED: "#3b82f6", + CANCELLED: "#ef4444", + NO_SHOW_HOST: "#64748b", + NO_SHOW_GUEST: "#f97316", +}; + +export const legend = [ + { label: "Created", color: COLOR.CREATED }, + { label: "Completed", color: COLOR.COMPLETED }, + { label: "Rescheduled", color: COLOR.RESCHEDULED }, + { label: "Cancelled", color: COLOR.CANCELLED }, + { label: "No-Show (Host)", color: COLOR.NO_SHOW_HOST }, + { label: "No-Show (Guest)", color: COLOR.NO_SHOW_GUEST }, +]; + +type EventTrendsData = RouterOutputs["viewer"]["insights"]["eventTrends"][number]; + +// Custom Tooltip component +const CustomTooltip = ({ + active, + payload, + label, +}: { + active?: boolean; + payload?: Array<{ + value: number; + dataKey: string; + name: string; + color: string; + payload: EventTrendsData; + }>; + label?: string; +}) => { + if (!active || !payload?.length) { + return null; + } + + return ( +
+

{payload[0].payload.formattedDateFull}

+ {payload.map((entry, index: number) => ( +

+ {entry.name}: {valueFormatter ? valueFormatter(entry.value) : entry.value} +

+ ))} +
+ ); +}; + export const EventTrendsChart = () => { const { t } = useLocale(); const insightsBookingParams = useInsightsBookingParameters(); @@ -30,15 +84,36 @@ export const EventTrendsChart = () => { if (!isSuccess) return null; return ( - - + +
+ + + + + + } /> + {legend.map((item) => ( + + ))} + + +
); }; diff --git a/packages/features/insights/components/booking/RecentFeedbackTable.tsx b/packages/features/insights/components/booking/RecentFeedbackTable.tsx index 69d7fca3ba87e6..cc7a3246464637 100644 --- a/packages/features/insights/components/booking/RecentFeedbackTable.tsx +++ b/packages/features/insights/components/booking/RecentFeedbackTable.tsx @@ -5,8 +5,8 @@ import { trpc } from "@calcom/trpc"; import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters"; import { ChartCard } from "../ChartCard"; -import { FeedbackTable } from "../FeedbackTable"; import { LoadingInsight } from "../LoadingInsights"; +import { RecentFeedbackTableContent } from "./RecentFeedbackTableContent"; export const RecentFeedbackTable = () => { const { t } = useLocale(); @@ -26,7 +26,7 @@ export const RecentFeedbackTable = () => { return ( - + ); }; diff --git a/packages/features/insights/components/FeedbackTable.tsx b/packages/features/insights/components/booking/RecentFeedbackTableContent.tsx similarity index 81% rename from packages/features/insights/components/FeedbackTable.tsx rename to packages/features/insights/components/booking/RecentFeedbackTableContent.tsx index af48a82ca8bf7a..3e2e5101edf8d6 100644 --- a/packages/features/insights/components/FeedbackTable.tsx +++ b/packages/features/insights/components/booking/RecentFeedbackTableContent.tsx @@ -2,31 +2,20 @@ import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import type { User } from "@calcom/prisma/client"; +import type { RouterOutputs } from "@calcom/trpc/react"; import { Avatar } from "@calcom/ui/components/avatar"; import { Button } from "@calcom/ui/components/button"; import { EmptyScreen } from "@calcom/ui/components/empty-screen"; import { Tooltip } from "@calcom/ui/components/tooltip"; -export const FeedbackTable = ({ - data, -}: { - data: - | { - userId: number | null; - user: Pick; - emailMd5?: string; - username?: string; - rating: number | null; - feedback: string | null; - }[] - | undefined; -}) => { +type FeedbackData = RouterOutputs["viewer"]["insights"]["recentRatings"]; + +export const RecentFeedbackTableContent = ({ data }: { data: FeedbackData }) => { const { t } = useLocale(); return (
- {data && data?.length > 0 ? ( - data?.map((item) => ( + {data && data.length > 0 ? ( + data.map((item) => (
diff --git a/packages/features/insights/components/index.ts b/packages/features/insights/components/index.ts index 23a7b02fee64fd..c94b682f22c1a6 100644 --- a/packages/features/insights/components/index.ts +++ b/packages/features/insights/components/index.ts @@ -1,10 +1,8 @@ export { BookedByCell } from "./BookedByCell"; export { BookingAtCell } from "./BookingAtCell"; export { BookingStatusBadge } from "./BookingStatusBadge"; -export { CardInsights } from "./Card"; export { CellWithOverflowX } from "./CellWithOverflowX"; export { ChartCard } from "./ChartCard"; -export { FeedbackTable } from "./FeedbackTable"; export { KPICard } from "./KPICard"; export { LineChart } from "./LineChart"; export { LoadingInsight } from "./LoadingInsights"; diff --git a/packages/features/insights/components/routing/FailedBookingsByField.tsx b/packages/features/insights/components/routing/FailedBookingsByField.tsx index 022d34b2c713c8..c8dd1d8d398202 100644 --- a/packages/features/insights/components/routing/FailedBookingsByField.tsx +++ b/packages/features/insights/components/routing/FailedBookingsByField.tsx @@ -1,6 +1,16 @@ "use client"; import { useState } from "react"; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Rectangle, +} from "recharts"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; @@ -8,7 +18,39 @@ import { ToggleGroup } from "@calcom/ui/components/form"; import { useInsightsParameters } from "../../hooks/useInsightsParameters"; import { ChartCard } from "../ChartCard"; -import { BarList } from "../tremor/BarList"; + +// Custom Tooltip component +const CustomTooltip = ({ + active, + payload, + label, +}: { + active?: boolean; + payload?: Array<{ + value: number; + dataKey: string; + name: string; + color: string; + payload: { name: string; value: number }; + }>; + label?: string; +}) => { + const { t } = useLocale(); + if (!active || !payload?.length) { + return null; + } + + return ( +
+

{label}

+ {payload.map((entry, index: number) => ( +

+ {t("failed_bookings")}: {entry.value} +

+ ))} +
+ ); +}; interface FormCardProps { formName: string; @@ -16,6 +58,7 @@ interface FormCardProps { } function FormCard({ formName, fields }: FormCardProps) { + const { t } = useLocale(); const fieldNames = Object.keys(fields); const [selectedField, setSelectedField] = useState(fieldNames[0]); @@ -29,6 +72,17 @@ function FormCard({ formName, fields }: FormCardProps) { value: option.count, })); + const maxCount = Math.max(...(selectedFieldData?.map((item) => item.value) || [0])); + const isEmpty = maxCount === 0; + + if (isEmpty) { + return ( +
+

{t("insights_no_data_found_for_filter")}

+
+ ); + } + return (
@@ -38,13 +92,36 @@ function FormCard({ formName, fields }: FormCardProps) { className="w-fit" onValueChange={(value) => value && setSelectedField(value)} /> - {selectedFieldData && ( -
- value.toString()} - className="mt-2" - /> + {selectedFieldData && selectedFieldData.length > 0 ? ( +
+ + + + + + } /> + } + /> + + +
+ ) : ( +
+ No data available for selected field +
Data: {JSON.stringify(selectedFieldData, null, 2)}
)}
@@ -66,8 +143,9 @@ export function FailedBookingsByField() { // routingFormId is always set, meaning data has only one entry. const [formName, fields] = Object.entries(data)[0]; + return ( - + ); diff --git a/packages/features/insights/components/routing/RoutedToPerPeriod.tsx b/packages/features/insights/components/routing/RoutedToPerPeriod.tsx index a3ee01fa8ffba0..4e202aa3865ce7 100644 --- a/packages/features/insights/components/routing/RoutedToPerPeriod.tsx +++ b/packages/features/insights/components/routing/RoutedToPerPeriod.tsx @@ -9,6 +9,7 @@ import { downloadAsCsv } from "@calcom/lib/csvUtils"; import { useInViewObserver } from "@calcom/lib/hooks/useInViewObserver"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; +import type { RouterOutputs } from "@calcom/trpc/react"; import classNames from "@calcom/ui/classNames"; import { Avatar } from "@calcom/ui/components/avatar"; import { Badge } from "@calcom/ui/components/badge"; @@ -165,14 +166,7 @@ function FormCard({ ); } -type RoutedToTableRow = { - id: number; - name: string; - avatarUrl: string | null; - stats: { [key: string]: number }; - performance: "above_average" | "at_average" | "below_average" | "median" | "no_data"; - totalBookings: number; -}; +type RoutedToTableRow = RouterOutputs["viewer"]["insights"]["routedToPerPeriod"]["users"]["data"][number]; const getPerformanceBadge = (performance: RoutedToTableRow["performance"], t: TFunction) => { switch (performance) { @@ -292,7 +286,7 @@ export function RoutedToPerPeriod() { .sort((a, b) => a.getTime() - b.getTime()); }, [data?.pages]); - const processedData: RoutedToTableRow[] = useMemo(() => { + const processedData = useMemo(() => { if (!data?.pages) return []; // Create a map for quick lookup of stats @@ -419,7 +413,7 @@ export function RoutedToPerPeriod() {
- +
{row.name}
diff --git a/packages/features/insights/components/routing/RoutingFormResponsesTable.tsx b/packages/features/insights/components/routing/RoutingFormResponsesTable.tsx index 6ed7a5f70aa700..2f242bbedd72ff 100644 --- a/packages/features/insights/components/routing/RoutingFormResponsesTable.tsx +++ b/packages/features/insights/components/routing/RoutingFormResponsesTable.tsx @@ -38,16 +38,13 @@ export function RoutingFormResponsesTable() { const { isAll, teamId, userId, memberUserIds, routingFormId, startDate, endDate, columnFilters } = useInsightsParameters(); - const { - data: headers, - isLoading: isHeadersLoading, - isSuccess: isHeadersSuccess, - } = trpc.viewer.insights.routingFormResponsesHeaders.useQuery({ - userId, - teamId, - isAll, - routingFormId, - }); + const { data: headers, isSuccess: isHeadersSuccess } = + trpc.viewer.insights.routingFormResponsesHeaders.useQuery({ + userId, + teamId, + isAll, + routingFormId, + }); const getInsightsFacetedUniqueValues = useInsightsRoutingFacetedUniqueValues({ headers, @@ -114,10 +111,6 @@ export function RoutingFormResponsesTable() { } }, [table, getInsightsFacetedUniqueValues, routingFormId]); - if (isHeadersLoading && !headers) { - return ; - } - return ( <>
diff --git a/packages/features/insights/components/routing/RoutingKPICards.tsx b/packages/features/insights/components/routing/RoutingKPICards.tsx index 87f180ce75969d..536c9bb3d5dead 100644 --- a/packages/features/insights/components/routing/RoutingKPICards.tsx +++ b/packages/features/insights/components/routing/RoutingKPICards.tsx @@ -1,14 +1,13 @@ "use client"; -import { Grid } from "@tremor/react"; -import { Flex, Text, Metric } from "@tremor/react"; - import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; +import classNames from "@calcom/ui/classNames"; +import { SkeletonText } from "@calcom/ui/components/skeleton"; import { useInsightsParameters } from "../../hooks/useInsightsParameters"; import { valueFormatter } from "../../lib"; -import { CardInsights } from "../Card"; +import { ChartCard } from "../ChartCard"; export const RoutingKPICards = () => { const { t } = useLocale(); @@ -61,35 +60,46 @@ export const RoutingKPICards = () => { } return ( - <> - + +
{categories.map((item) => ( - - {item.title} - - {valueFormatter(data[item.index])} - - +
+
{item.title}
+
+
{valueFormatter(data[item.index])}
+
+
))} - - +
+
); }; const LoadingKPICards = (props: { categories: { title: string; index: string }[] }) => { + const { t } = useLocale(); const { categories } = props; return ( - - {categories.map((item) => ( - -
-
-
-
+ +
+ {categories.map((item) => ( +
+
+ +
+
+
- - ))} - + ))} +
+
); }; diff --git a/packages/features/insights/components/tremor.css b/packages/features/insights/components/tremor.css deleted file mode 100644 index f8b5c307463de3..00000000000000 --- a/packages/features/insights/components/tremor.css +++ /dev/null @@ -1,107 +0,0 @@ -.custom-date > .tremor-DateRangePicker-root > .tremor-DateRangePicker-button { - box-shadow: none; - width: 100%; - background-color: transparent; -} - -/* Media query for screens larger than 768px */ -@media (max-width: 639) { - .custom-date > .tremor-DateRangePicker-root > .tremor-DateRangePicker-button { - max-width: 400px; - } -} - -.recharts-cartesian-grid-horizontal line { - @apply stroke-emphasis; -} - -.tremor-DateRangePicker-button button { - @apply border-default hover:border-emphasis !h-9 !max-h-9; -} - -.tremor-DateRangePicker-calendarButton, -.tremor-DateRangePicker-dropdownButton { - @apply border-subtle bg-default focus-within:ring-emphasis hover:border-subtle dark:focus-within:ring-emphasis hover:bg-subtle text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:ring-0; -} - -.tremor-DateRangePicker-dropdownModal { - @apply divide-none; -} - -.tremor-DropdownItem-root { - @apply bg-default hover:bg-subtle text-default hover:text-emphasis !h-9 !max-h-9; -} - -.tremor-DateRangePicker-calendarButtonText, -.tremor-DateRangePicker-dropdownButtonText { - @apply text-default; -} - -.tremor-DateRangePicker-calendarHeaderText { - @apply !text-default; -} - -.tremor-DateRangePicker-calendarHeader svg { - @apply text-default; -} - -.tremor-DateRangePicker-calendarHeader button { - @apply hover:bg-emphasis shadow-none focus:ring-0; -} - -.tremor-DateRangePicker-calendarHeader button:hover svg { - @apply text-emphasis; -} - -.tremor-DateRangePicker-calendarButtonIcon { - @apply text-default; -} - -.tremor-DateRangePicker-calendarModal, -.tremor-DateRangePicker-dropdownModal { - @apply bg-default border-subtle shadow-dropdown; -} - -.tremor-DateRangePicker-calendarBodyDate button { - @apply text-default hover:bg-emphasis; -} - -.tremor-DateRangePicker-calendarBodyDate button:disabled, -.tremor-DateRangePicker-calendarBodyDate button[disabled] { - @apply opacity-25; -} - -.tremor-DateRangePicker-calendarHeader button { - @apply border-default text-default; -} - -.tremor-DateRangePicker-calendarBodyDate .bg-gray-100 { - @apply bg-subtle; -} - -.tremor-DateRangePicker-calendarBodyDate .bg-gray-500 { - @apply !bg-brand-default text-inverted; -} - -.tremor-Card-root { - @apply bg-default p-5; -} - -.tremor-TableCell-root { - @apply pl-0; -} - -.recharts-responsive-container { - @apply -mx-4; -} -.tremor-Card-root > p { - @apply mb-2 text-base font-semibold; -} - -.tremor-Legend-legendItem { - @apply ml-2; -} - -.tremor-TableBody-root { - @apply divide-subtle; -} \ No newline at end of file diff --git a/packages/features/insights/components/tremor.types.ts b/packages/features/insights/components/tremor.types.ts deleted file mode 100644 index fc6710923d1a16..00000000000000 --- a/packages/features/insights/components/tremor.types.ts +++ /dev/null @@ -1,49 +0,0 @@ -type ValueFormatter = { - (value: number): string; -}; - -type Color = - | "slate" - | "gray" - | "zinc" - | "neutral" - | "stone" - | "red" - | "orange" - | "amber" - | "yellow" - | "lime" - | "green" - | "emerald" - | "teal" - | "cyan" - | "sky" - | "blue" - | "indigo" - | "violet" - | "purple" - | "fuchsia" - | "pink" - | "rose"; - -interface BaseChartProps extends React.HTMLAttributes { - data: any[]; - categories: string[]; - index: string; - colors?: Color[]; - valueFormatter?: ValueFormatter; - startEndOnly?: boolean; - showXAxis?: boolean; - showYAxis?: boolean; - yAxisWidth?: number; - showAnimation?: boolean; - showTooltip?: boolean; - showGradient?: boolean; - showLegend?: boolean; - showGridLines?: boolean; - autoMinValue?: boolean; - minValue?: number; - maxValue?: number; -} - -export type LineChartProps = BaseChartProps & React.RefAttributes; diff --git a/packages/features/insights/components/tremor/BarList.tsx b/packages/features/insights/components/tremor/BarList.tsx deleted file mode 100644 index c6b2d476ce9dd4..00000000000000 --- a/packages/features/insights/components/tremor/BarList.tsx +++ /dev/null @@ -1,167 +0,0 @@ -"use client"; - -// Tremor BarList [v0.1.1] -import React from "react"; - -import classNames from "@calcom/ui/classNames"; - -type Bar = T & { - key?: string; - href?: string; - value: number; - name: string; -}; - -const focusRing = [ - // base - "outline outline-offset-2 outline-0 focus-visible:outline-2", - // outline color - "outline-brand", -]; - -interface BarListProps extends React.HTMLAttributes { - data: Bar[]; - valueFormatter?: (value: number) => string; - showAnimation?: boolean; - onValueChange?: (payload: Bar) => void; - sortOrder?: "ascending" | "descending" | "none"; -} - -function BarListInner( - { - data = [], - valueFormatter = (value) => value.toString(), - showAnimation = false, - onValueChange, - sortOrder = "descending", - className, - ...props - }: BarListProps, - forwardedRef: React.ForwardedRef -) { - const Component = onValueChange ? "button" : "div"; - const sortedData = React.useMemo(() => { - if (sortOrder === "none") { - return data; - } - return [...data].sort((a, b) => { - return sortOrder === "ascending" ? a.value - b.value : b.value - a.value; - }); - }, [data, sortOrder]); - - const widths = React.useMemo(() => { - const maxValue = Math.max(...sortedData.map((item) => item.value), 0); - return sortedData.map((item) => (item.value === 0 ? 0 : Math.max((item.value / maxValue) * 100, 2))); - }, [sortedData]); - - const rowHeight = "h-8"; - - return ( -
-
- {sortedData.map((item, index) => ( - { - onValueChange?.(item); - }} - className={classNames( - // base - "group w-full rounded", - // focus - focusRing, - onValueChange - ? [ - "!-m-0 cursor-pointer", - // hover - "hover:bg-gray-50 hover:dark:bg-gray-900", - ] - : "" - )}> -
-
- {item.href ? ( - event.stopPropagation()}> - {item.name} - - ) : ( -

- {item.name} -

- )} -
-
-
- ))} -
-
- {sortedData.map((item, index) => ( -
-

- {valueFormatter(item.value)} -

-
- ))} -
-
- ); -} - -BarListInner.displayName = "BarList"; - -const BarList = React.forwardRef(BarListInner) as ( - p: BarListProps & { ref?: React.ForwardedRef } -) => ReturnType; - -export { BarList, type BarListProps }; diff --git a/packages/features/insights/lib/colors.ts b/packages/features/insights/lib/colors.ts deleted file mode 100644 index 3f96304f157e8c..00000000000000 --- a/packages/features/insights/lib/colors.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Color } from "@tremor/react"; - -export const colors: { [key: string]: Color } = { - increase: "emerald", - moderateIncrease: "emerald", - unchanged: "orange", - moderateDecrease: "rose", - decrease: "rose", -}; diff --git a/packages/features/insights/lib/index.ts b/packages/features/insights/lib/index.ts index b880e893ed8d12..1db19732adfb83 100644 --- a/packages/features/insights/lib/index.ts +++ b/packages/features/insights/lib/index.ts @@ -1,3 +1,2 @@ export { calculateDeltaType } from "./calculateDeltaType"; -export { colors } from "./colors"; export { valueFormatter } from "./valueFormatter"; diff --git a/packages/lib/server/service/insightsBooking.ts b/packages/lib/server/service/insightsBooking.ts index d392441f46115a..14fb22840a9618 100644 --- a/packages/lib/server/service/insightsBooking.ts +++ b/packages/lib/server/service/insightsBooking.ts @@ -47,6 +47,19 @@ export type SelectedFields = T extends undefined : never; }; +type UserStatsData = { + userId: number; + user: { + id: number; + username: string | null; + name: string | null; + email: string; + avatarUrl: string; + }; + emailMd5: string; + count: number; +}[]; + export const bookingDataSchema = z .object({ id: z.number(), @@ -682,8 +695,9 @@ export class InsightsBookingService { }); // Transform aggregate data into the expected format - const result = dateRanges.map(({ formattedDate }) => { + const result = dateRanges.map(({ formattedDate, formattedDateFull }) => { const eventData = { + formattedDateFull: formattedDateFull, Month: formattedDate, Created: 0, Completed: 0, @@ -789,7 +803,7 @@ export class InsightsBookingService { async getMembersStatsWithCount( type: "all" | "cancelled" | "noShow" = "all", sortOrder: "ASC" | "DESC" = "DESC" - ) { + ): Promise { const baseConditions = await this.getBaseConditions(); let additionalCondition = Prisma.sql``; @@ -857,22 +871,22 @@ export class InsightsBookingService { return result; } - async getMembersRatingStats(sortOrder: "ASC" | "DESC" = "DESC") { + async getMembersRatingStats(sortOrder: "ASC" | "DESC" = "DESC"): Promise { const baseConditions = await this.getBaseConditions(); const bookingsFromTeam = await this.prisma.$queryRaw< Array<{ userId: number; - averageRating: number; + count: number; }> >` SELECT "userId", - AVG("rating")::float as "averageRating" + AVG("rating")::float as "count" FROM "BookingTimeStatusDenormalized" WHERE ${baseConditions} AND "userId" IS NOT NULL AND "rating" IS NOT NULL GROUP BY "userId" - ORDER BY "averageRating" ${sortOrder === "ASC" ? Prisma.sql`ASC` : Prisma.sql`DESC`} + ORDER BY "count" ${sortOrder === "ASC" ? Prisma.sql`ASC` : Prisma.sql`DESC`} LIMIT 10 `; @@ -910,7 +924,7 @@ export class InsightsBookingService { userId: booking.userId, user, emailMd5: md5(user.email), - averageRating: booking.averageRating, + count: booking.count, }; }) .filter((item): item is NonNullable => item !== null); diff --git a/packages/platform/atoms/tailwind.config.cjs b/packages/platform/atoms/tailwind.config.cjs index f3a30a9372ebc5..0bc287306748eb 100644 --- a/packages/platform/atoms/tailwind.config.cjs +++ b/packages/platform/atoms/tailwind.config.cjs @@ -2,12 +2,7 @@ const base = require("@calcom/config/tailwind-preset"); /** @type {import('tailwindcss').Config} */ module.exports = { ...base, - content: [ - ...base.content, - "../../../packages/ui/!(node_modules)/**/*.{js,ts,jsx,tsx,mdx}", - "../../../node_modules/@tremor/**/*.{js,ts,jsx,tsx}", - "./**/*.tsx", - ], + content: [...base.content, "../../../packages/ui/!(node_modules)/**/*.{js,ts,jsx,tsx,mdx}", "./**/*.tsx"], plugins: [...base.plugins, require("tailwindcss-animate")], theme: { ...base.theme, diff --git a/packages/ui/components/progress-bar/ProgressBar.tsx b/packages/ui/components/progress-bar/ProgressBar.tsx new file mode 100644 index 00000000000000..bd45b0df885b8b --- /dev/null +++ b/packages/ui/components/progress-bar/ProgressBar.tsx @@ -0,0 +1,38 @@ +import type { VariantProps } from "class-variance-authority"; +import { cva } from "class-variance-authority"; + +import classNames from "@calcom/ui/classNames"; + +const progressBarStyles = cva("h-2 rounded-full", { + variants: { + color: { + green: "bg-green-500", + blue: "bg-blue-500", + red: "bg-red-500", + yellow: "bg-yellow-500", + gray: "bg-gray-500", + }, + }, + defaultVariants: { + color: "gray", + }, +}); + +export interface ProgressBarProps extends VariantProps { + percentageValue: number; + label?: string; + className?: string; +} + +export function ProgressBar({ color, percentageValue, label, className }: ProgressBarProps) { + const clampedPercentage = Math.min(100, Math.max(0, percentageValue)); + + return ( +
+
+
+
+ {label && {label}} +
+ ); +} diff --git a/packages/ui/components/progress-bar/index.ts b/packages/ui/components/progress-bar/index.ts new file mode 100644 index 00000000000000..836dc43aae1721 --- /dev/null +++ b/packages/ui/components/progress-bar/index.ts @@ -0,0 +1,2 @@ +export { ProgressBar } from "./ProgressBar"; +export type { ProgressBarProps } from "./ProgressBar"; diff --git a/packages/ui/package.json b/packages/ui/package.json index 96d2fb9a70d72d..f03f25050fae14 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -43,6 +43,7 @@ "./components/organization-banner": "./components/organization-banner/index.ts", "./components/pagination": "./components/pagination/index.ts", "./components/popover": "./components/popover/index.ts", + "./components/progress-bar": "./components/progress-bar/index.ts", "./components/radio": "./components/radio/index.ts", "./components/scrollable": "./components/scrollable/index.ts", "./components/sheet": "./components/sheet/index.ts", diff --git a/yarn.lock b/yarn.lock index a2907c05c8cdf2..70536df6e30676 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2096,15 +2096,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.21.0": - version: 7.26.10 - resolution: "@babel/runtime@npm:7.26.10" - dependencies: - regenerator-runtime: ^0.14.0 - checksum: 22d2e0abb86e90de489ab16bb578db6fe2b63a88696db431198b24963749820c723f1982298cdbbea187f7b2b80fb4d98a514faf114ddb2fdc14a4b96277b955 - languageName: node - linkType: hard - "@babel/runtime@npm:^7.23.2": version: 7.23.5 resolution: "@babel/runtime@npm:7.23.5" @@ -4061,7 +4052,6 @@ __metadata: "@tanstack/react-query": ^5.17.15 "@team-plain/typescript-sdk": ^5.9.0 "@testing-library/react": ^13.3.0 - "@tremor/react": ^2.11.0 "@types/accept-language-parser": 1.5.2 "@types/async": ^3.2.15 "@types/bcryptjs": ^2.4.2 @@ -5843,7 +5833,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.2.1": +"@floating-ui/dom@npm:^1.0.1": version: 1.2.6 resolution: "@floating-ui/dom@npm:1.2.6" dependencies: @@ -5871,18 +5861,6 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react-dom@npm:^1.3.0": - version: 1.3.0 - resolution: "@floating-ui/react-dom@npm:1.3.0" - dependencies: - "@floating-ui/dom": ^1.2.1 - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: ce0ad3e3bbe43cfd15a6a0d5cccede02175c845862bfab52027995ab99c6b29630180dc7d146f76ebb34730f90a6ab9bf193c8984fe8d7f56062308e4ca98f77 - languageName: node - linkType: hard - "@floating-ui/react-dom@npm:^2.0.0": version: 2.0.1 resolution: "@floating-ui/react-dom@npm:2.0.1" @@ -5895,20 +5873,6 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react@npm:^0.19.1": - version: 0.19.2 - resolution: "@floating-ui/react@npm:0.19.2" - dependencies: - "@floating-ui/react-dom": ^1.3.0 - aria-hidden: ^1.1.3 - tabbable: ^6.0.1 - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: 00fd827c2dcf879fec221d89ef5b90836bbecacc236ce2acc787db32ae7311d490cd136b13a8d0b6ab12842554a2ee1110605aa832af71a45c0a7297e342072c - languageName: node - linkType: hard - "@floating-ui/utils@npm:^0.2.9": version: 0.2.9 resolution: "@floating-ui/utils@npm:0.2.9" @@ -16884,22 +16848,6 @@ __metadata: languageName: node linkType: hard -"@tremor/react@npm:^2.11.0": - version: 2.11.0 - resolution: "@tremor/react@npm:2.11.0" - dependencies: - "@floating-ui/react": ^0.19.1 - date-fns: ^2.28.0 - react-transition-group: ^4.4.5 - recharts: ^2.3.2 - tailwind-merge: ^1.9.1 - peerDependencies: - react: ^18.0.0 - react-dom: ">=16.6.0" - checksum: 76bee230a819289a5e87204f51161724b49bcc281308b4c41a5918e57e93497e2e97eb85b951bd750b918093784fdb28a2d1060b89502544f1a9cf76232da6d3 - languageName: node - linkType: hard - "@trivago/prettier-plugin-sort-imports@npm:4.1.1": version: 4.1.1 resolution: "@trivago/prettier-plugin-sort-imports@npm:4.1.1" @@ -20088,15 +20036,6 @@ __metadata: languageName: node linkType: hard -"aria-hidden@npm:^1.1.3": - version: 1.2.3 - resolution: "aria-hidden@npm:1.2.3" - dependencies: - tslib: ^2.0.0 - checksum: 7d7d211629eef315e94ed3b064c6823d13617e609d3f9afab1c2ed86399bb8e90405f9bdd358a85506802766f3ecb468af985c67c846045a34b973bcc0289db9 - languageName: node - linkType: hard - "aria-hidden@npm:^1.2.4": version: 1.2.4 resolution: "aria-hidden@npm:1.2.4" @@ -23796,15 +23735,6 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:^2.28.0": - version: 2.30.0 - resolution: "date-fns@npm:2.30.0" - dependencies: - "@babel/runtime": ^7.21.0 - checksum: f7be01523282e9bb06c0cd2693d34f245247a29098527d4420628966a2d9aad154bd0e90a6b1cf66d37adcb769cd108cf8a7bd49d76db0fb119af5cdd13644f4 - languageName: node - linkType: hard - "date-fns@npm:^3.6.0": version: 3.6.0 resolution: "date-fns@npm:3.6.0" @@ -23927,7 +23857,7 @@ __metadata: languageName: node linkType: hard -"decimal.js-light@npm:^2.4.1, decimal.js-light@npm:^2.5.1": +"decimal.js-light@npm:^2.5.1": version: 2.5.1 resolution: "decimal.js-light@npm:2.5.1" checksum: f5a2c7eac1c4541c8ab8a5c8abea64fc1761cefc7794bd5f8afd57a8a78d1b51785e0c4e4f85f4895a043eaa90ddca1edc3981d1263eb6ddce60f32bf5fe66c9 @@ -26641,7 +26571,7 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.1, eventemitter3@npm:^4.0.4": +"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.4": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" checksum: 1875311c42fcfe9c707b2712c32664a245629b42bb0a5a84439762dd0fd637fc54d078155ea83c2af9e0323c9ac13687e03cfba79b03af9f40c89b4960099374 @@ -26900,13 +26830,6 @@ __metadata: languageName: node linkType: hard -"fast-equals@npm:^5.0.1": - version: 5.2.2 - resolution: "fast-equals@npm:5.2.2" - checksum: 7156bcade0be5ee4dc335969d255a5815348d57080e1876fa1584451eafd0c92588de5f5840e55f81841b6d907ade2a49a46e4ec33e6f7a283a209c0fd8f8a59 - languageName: node - linkType: hard - "fast-glob@npm:3.3.1": version: 3.3.1 resolution: "fast-glob@npm:3.3.1" @@ -39389,13 +39312,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.3.1": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: e20fe84c86ff172fc8d898251b7cc2c43645d108bf96d0b8edf39b98f9a2cae97b40520ee7ed8ee0085ccc94736c4886294456033304151c3f94978cec03df21 - languageName: node - linkType: hard - "react-live-chat-loader@npm:^2.8.1": version: 2.8.1 resolution: "react-live-chat-loader@npm:2.8.1" @@ -39709,20 +39625,6 @@ __metadata: languageName: node linkType: hard -"react-smooth@npm:^4.0.4": - version: 4.0.4 - resolution: "react-smooth@npm:4.0.4" - dependencies: - fast-equals: ^5.0.1 - prop-types: ^15.8.1 - react-transition-group: ^4.4.5 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 909305d40bae79a011ff21a10c4bc7ddadc87ac5ff093b4a5f827f730f093ec4e044c4330688d29b3ad2db83aab8997c3bb1bae550a9c66de74521b8ed52cc53 - languageName: node - linkType: hard - "react-sticky-box@npm:^2.0.4": version: 2.0.4 resolution: "react-sticky-box@npm:2.0.4" @@ -39811,21 +39713,6 @@ __metadata: languageName: node linkType: hard -"react-transition-group@npm:^4.4.5": - version: 4.4.5 - resolution: "react-transition-group@npm:4.4.5" - dependencies: - "@babel/runtime": ^7.5.5 - dom-helpers: ^5.0.1 - loose-envify: ^1.4.0 - prop-types: ^15.6.2 - peerDependencies: - react: ">=16.6.0" - react-dom: ">=16.6.0" - checksum: 75602840106aa9c6545149d6d7ae1502fb7b7abadcce70a6954c4b64a438ff1cd16fc77a0a1e5197cdd72da398f39eb929ea06f9005c45b132ed34e056ebdeb1 - languageName: node - linkType: hard - "react-turnstile@npm:^1.1.3": version: 1.1.3 resolution: "react-turnstile@npm:1.1.3" @@ -40079,34 +39966,6 @@ __metadata: languageName: node linkType: hard -"recharts-scale@npm:^0.4.4": - version: 0.4.5 - resolution: "recharts-scale@npm:0.4.5" - dependencies: - decimal.js-light: ^2.4.1 - checksum: e970377190a610e684a32c7461c7684ac3603c2e0ac0020bbba1eea9d099b38138143a8e80bf769bb49c0b7cecf22a2f5c6854885efed2d56f4540d4aa7052bd - languageName: node - linkType: hard - -"recharts@npm:^2.3.2": - version: 2.15.1 - resolution: "recharts@npm:2.15.1" - dependencies: - clsx: ^2.0.0 - eventemitter3: ^4.0.1 - lodash: ^4.17.21 - react-is: ^18.3.1 - react-smooth: ^4.0.4 - recharts-scale: ^0.4.4 - tiny-invariant: ^1.3.1 - victory-vendor: ^36.6.8 - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: c288012087424e7067bf4a48a3d50a7c0ea231188ecaaf17685d93711ef1846aedd37509dfc25c046052f671e7e19dd2de5d8b9c1e497a64c39995c9c338b40a - languageName: node - linkType: hard - "recharts@npm:^3.0.2": version: 3.0.2 resolution: "recharts@npm:3.0.2" @@ -43713,13 +43572,6 @@ __metadata: languageName: node linkType: hard -"tabbable@npm:^6.0.1": - version: 6.1.1 - resolution: "tabbable@npm:6.1.1" - checksum: 348639497262241ce8e0ccb0664ea582a386183107299ee8f27cf7b56bc84f36e09eaf667d3cb4201e789634012a91f7129bcbd49760abe874fbace35b4cf429 - languageName: node - linkType: hard - "tailwind-merge@npm:^1.13.2": version: 1.13.2 resolution: "tailwind-merge@npm:1.13.2" @@ -43727,13 +43579,6 @@ __metadata: languageName: node linkType: hard -"tailwind-merge@npm:^1.9.1": - version: 1.14.0 - resolution: "tailwind-merge@npm:1.14.0" - checksum: 8cf5d37f51b3b32e4bdd5544feaed34357bfba2f64f14834cc4a21ac29b0ae22d7255386467a0f1dadb3e38499389efbbabeddcd0b16571af5a0d346a4921877 - languageName: node - linkType: hard - "tailwind-merge@npm:^2.5.4": version: 2.6.0 resolution: "tailwind-merge@npm:2.6.0" @@ -46218,28 +46063,6 @@ __metadata: languageName: node linkType: hard -"victory-vendor@npm:^36.6.8": - version: 36.9.2 - resolution: "victory-vendor@npm:36.9.2" - dependencies: - "@types/d3-array": ^3.0.3 - "@types/d3-ease": ^3.0.0 - "@types/d3-interpolate": ^3.0.1 - "@types/d3-scale": ^4.0.2 - "@types/d3-shape": ^3.1.0 - "@types/d3-time": ^3.0.0 - "@types/d3-timer": ^3.0.0 - d3-array: ^3.1.6 - d3-ease: ^3.0.1 - d3-interpolate: ^3.0.1 - d3-scale: ^4.0.2 - d3-shape: ^3.1.0 - d3-time: ^3.0.0 - d3-timer: ^3.0.1 - checksum: a755110e287b700202d08ac81982093ab100edaa9d61beef1476d59e9705605bd8299a3aa41fa04b933a12bd66737f4c8f7d18448dd6488c69d4f72480023a2e - languageName: node - linkType: hard - "victory-vendor@npm:^37.0.2": version: 37.3.6 resolution: "victory-vendor@npm:37.3.6"