feat: implement GradientStatCard component and apply Figtree font across the dashboard with responsive grid layout updates.
This commit is contained in:
parent
950cfe9f83
commit
d8d7e542d0
53
src/components/shared/GradientStatCard.tsx
Normal file
53
src/components/shared/GradientStatCard.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import type { LucideIcon } from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
interface GradientStatCardProps {
|
||||||
|
icon: LucideIcon;
|
||||||
|
value: string | number;
|
||||||
|
label: string;
|
||||||
|
badge?: {
|
||||||
|
text: string;
|
||||||
|
variant: 'success' | 'warning' | 'info' | 'error' | 'green' | 'gray';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GradientStatCard: React.FC<GradientStatCardProps> = ({ icon: Icon, value, label, badge }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="rounded-[8px] p-[1px] h-full"
|
||||||
|
style={{
|
||||||
|
background: 'var(--Linear, linear-gradient(161deg, #084CC8 -1.15%, #75C044 44.29%, #FED314 89.74%))',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-start gap-3 px-4 py-4 min-h-[108px] h-full w-full rounded-[7px] bg-[#F8FAFC]">
|
||||||
|
<div className="flex items-start justify-between w-full">
|
||||||
|
<div className="text-[#94A3B8]">
|
||||||
|
<Icon className="w-5 h-5 stroke-[1.8]" />
|
||||||
|
</div>
|
||||||
|
{badge && (
|
||||||
|
<div className={cn(
|
||||||
|
"px-2.5 py-1 rounded-full text-[10px] font-bold tracking-tight whitespace-nowrap",
|
||||||
|
(badge.variant === 'success' || badge.variant === 'green') ? "bg-[#f1fffb] text-[#16c784]" :
|
||||||
|
badge.variant === 'warning' ? "bg-[#fff5e5] text-[#fca004]" :
|
||||||
|
badge.variant === 'info' ? "bg-[#f0f9ff] text-[#0ea5e9]" :
|
||||||
|
badge.variant === 'error' ? "bg-[#fdf5f4] text-[#e0352a]" :
|
||||||
|
"bg-[#f3f4f6] text-[#6b7280]" // default / gray
|
||||||
|
)}>
|
||||||
|
{badge.text}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="text-[28px] leading-none font-bold text-[#1E293B] tracking-[-0.02em]">
|
||||||
|
{value}
|
||||||
|
</h4>
|
||||||
|
<p className="text-[11px] font-semibold text-[#64748B] uppercase tracking-wider mt-1">
|
||||||
|
{label}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -43,3 +43,4 @@ export { ActiveOnlyToggle } from './ActiveOnlyToggle';
|
|||||||
export { SearchBox } from './SearchBox';
|
export { SearchBox } from './SearchBox';
|
||||||
export { FormTagInput } from './FormTagInput';
|
export { FormTagInput } from './FormTagInput';
|
||||||
export { MarkdownViewer } from './MarkdownViewer';
|
export { MarkdownViewer } from './MarkdownViewer';
|
||||||
|
export { GradientStatCard } from './GradientStatCard';
|
||||||
|
|||||||
@ -2,12 +2,10 @@ import { useState, useEffect } from "react";
|
|||||||
import {
|
import {
|
||||||
Building2,
|
Building2,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
// Users,
|
|
||||||
// TrendingUp,
|
|
||||||
Package,
|
Package,
|
||||||
Heart,
|
Heart,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { StatCard } from "./StatCard";
|
import { GradientStatCard } from "@/components/shared";
|
||||||
import type { StatCardData } from "@/types/dashboard";
|
import type { StatCardData } from "@/types/dashboard";
|
||||||
import { dashboardService } from "@/services/dashboard-service";
|
import { dashboardService } from "@/services/dashboard-service";
|
||||||
|
|
||||||
@ -29,7 +27,7 @@ export const StatsGrid = () => {
|
|||||||
icon: Building2,
|
icon: Building2,
|
||||||
value: data.totalTenants,
|
value: data.totalTenants,
|
||||||
label: "Total Tenants",
|
label: "Total Tenants",
|
||||||
badge: { text: `${data.activeTenants} active`, variant: "green" },
|
badge: { text: `${data.activeTenants} active`, variant: "success" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: CheckCircle2,
|
icon: CheckCircle2,
|
||||||
@ -40,26 +38,14 @@ export const StatsGrid = () => {
|
|||||||
data.totalTenants > 0
|
data.totalTenants > 0
|
||||||
? `${Math.round((data.activeTenants / data.totalTenants) * 100)}% Rate`
|
? `${Math.round((data.activeTenants / data.totalTenants) * 100)}% Rate`
|
||||||
: "0% Rate",
|
: "0% Rate",
|
||||||
variant: "green",
|
variant: "success",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// icon: Users,
|
|
||||||
// value: data.totalUsers,
|
|
||||||
// label: "Total Users",
|
|
||||||
// badge: { text: "All users", variant: "gray" },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// icon: TrendingUp,
|
|
||||||
// value: data.activeSessions,
|
|
||||||
// label: "Active Sessions",
|
|
||||||
// badge: { text: "Live now", variant: "gray" },
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
icon: Package,
|
icon: Package,
|
||||||
value: data.registeredModules,
|
value: data.registeredModules,
|
||||||
label: "Registered Modules",
|
label: "Registered Modules",
|
||||||
badge: { text: "Total", variant: "gray" },
|
badge: { text: "Total", variant: "info" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Heart,
|
icon: Heart,
|
||||||
@ -73,8 +59,8 @@ export const StatsGrid = () => {
|
|||||||
variant:
|
variant:
|
||||||
data.healthyModules === data.registeredModules &&
|
data.healthyModules === data.registeredModules &&
|
||||||
data.registeredModules > 0
|
data.registeredModules > 0
|
||||||
? "green"
|
? "success"
|
||||||
: "gray",
|
: "info",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -93,15 +79,17 @@ export const StatsGrid = () => {
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6 mb-4 md:mb-6 auto-rows-fr">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6 mb-4 md:mb-6 auto-rows-fr">
|
||||||
{Array.from({ length: 6 }).map((_, index) => (
|
{Array.from({ length: 4 }).map((_, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="bg-white border border-[rgba(0,0,0,0.08)] rounded-lg p-[17px] flex flex-col gap-3 h-[107px] animate-pulse"
|
className="rounded-[8px] p-[1px] h-[108px] bg-gray-100 animate-pulse"
|
||||||
>
|
>
|
||||||
<div className="h-4 bg-gray-200 rounded w-1/4"></div>
|
<div className="w-full h-full rounded-[7px] bg-white p-4 space-y-3">
|
||||||
<div className="h-8 bg-gray-200 rounded w-1/2"></div>
|
<div className="h-4 bg-gray-50 rounded w-1/4"></div>
|
||||||
<div className="h-3 bg-gray-200 rounded w-1/3"></div>
|
<div className="h-8 bg-gray-50 rounded w-1/2"></div>
|
||||||
|
<div className="h-3 bg-gray-50 rounded w-1/3"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -117,9 +105,9 @@ export const StatsGrid = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6 mb-4 md:mb-6 auto-rows-fr">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6 mb-4 md:mb-6 auto-rows-fr">
|
||||||
{statsData.map((stat, index) => (
|
{statsData.map((stat, index) => (
|
||||||
<StatCard key={index} data={stat} />
|
<GradientStatCard key={index} {...stat} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap');
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
|
--font-sans: "Figtree", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
--radius-lg: var(--radius);
|
--radius-lg: var(--radius);
|
||||||
@ -115,9 +116,12 @@
|
|||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
border-color: var(--color-border);
|
||||||
|
outline-color: var(--color-ring);
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
background-color: var(--color-background);
|
||||||
|
color: var(--color-foreground);
|
||||||
|
font-family: var(--font-sans);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from "react";
|
||||||
import { Layout } from '@/components/layout/Layout';
|
import { Layout } from "@/components/layout/Layout";
|
||||||
import { StatsGrid } from '@/features/dashboard/components/StatsGrid';
|
import { StatsGrid } from "@/features/dashboard/components/StatsGrid";
|
||||||
import { RecentActivity } from '@/features/dashboard/components/RecentActivity';
|
import { RecentActivity } from "@/features/dashboard/components/RecentActivity";
|
||||||
import { QuickActions } from '@/features/dashboard/components/QuickActions';
|
import { QuickActions } from "@/features/dashboard/components/QuickActions";
|
||||||
// import { SystemHealth } from '@/features/dashboard/components/SystemHealth';
|
// import { SystemHealth } from '@/features/dashboard/components/SystemHealth';
|
||||||
|
|
||||||
const Dashboard = (): ReactElement => {
|
const Dashboard = (): ReactElement => {
|
||||||
@ -10,19 +10,22 @@ const Dashboard = (): ReactElement => {
|
|||||||
<Layout
|
<Layout
|
||||||
currentPage="Dashboard Overview"
|
currentPage="Dashboard Overview"
|
||||||
pageHeader={{
|
pageHeader={{
|
||||||
title: 'Platform Overview',
|
title: "Platform Overview",
|
||||||
description: 'Monitor system health, tenant activity, and user metrics in real-time.',
|
description:
|
||||||
|
"Monitor system health, tenant activity, and user metrics in real-time.",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Stats Grid */}
|
|
||||||
<StatsGrid />
|
<StatsGrid />
|
||||||
|
|
||||||
{/* Bottom Section */}
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-4 lg:gap-6 pb-8">
|
||||||
<div className="flex flex-col lg:flex-row gap-4 md:gap-6 items-start mt-4 md:mt-6">
|
{/* Main Content Area (Left) */}
|
||||||
<RecentActivity />
|
<div className="lg:col-span-8 xl:col-span-9 flex flex-col gap-6">
|
||||||
<div className="flex flex-col gap-4 md:gap-6 w-full lg:w-[300px] lg:shrink-0">
|
<RecentActivity />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar (Right) */}
|
||||||
|
<div className="lg:col-span-4 xl:col-span-3 flex flex-col gap-6">
|
||||||
<QuickActions />
|
<QuickActions />
|
||||||
{/* <SystemHealth /> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@ -16,9 +16,11 @@ import { workflowService } from "@/services/workflow-service";
|
|||||||
import { dashboardService, type TenantDashboardStats } from "@/services/dashboard-service";
|
import { dashboardService, type TenantDashboardStats } from "@/services/dashboard-service";
|
||||||
import type { WorkflowTask } from "@/types/workflow";
|
import type { WorkflowTask } from "@/types/workflow";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { GradientStatCard } from "@/components/shared";
|
||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
|
||||||
interface StatCardProps {
|
interface StatCardProps {
|
||||||
icon: React.ComponentType<{ className?: string; strokeWidth?: number }>;
|
icon: LucideIcon;
|
||||||
value: string | number;
|
value: string | number;
|
||||||
label: string;
|
label: string;
|
||||||
badge?: {
|
badge?: {
|
||||||
@ -27,52 +29,6 @@ interface StatCardProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatCard = ({
|
|
||||||
icon: Icon,
|
|
||||||
value,
|
|
||||||
label,
|
|
||||||
badge
|
|
||||||
}: StatCardProps): ReactElement => {
|
|
||||||
const { primaryColor } = useAppTheme();
|
|
||||||
return (
|
|
||||||
<div className="relative group h-full">
|
|
||||||
<div className="bg-white border border-[#e5e7eb] rounded-xl p-4.5 flex flex-col gap-4 shadow-sm hover:shadow-md transition-all h-full relative overflow-hidden">
|
|
||||||
{/* Interaction Gradient */}
|
|
||||||
<div
|
|
||||||
className="absolute top-0 left-0 w-full h-[2px] opacity-0 group-hover:opacity-100 transition-opacity"
|
|
||||||
style={{ background: `linear-gradient(to right, ${primaryColor}, #75c044, #fed314)` }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<div className="w-10 h-10 rounded-md bg-gray-50 flex items-center justify-center border border-gray-100">
|
|
||||||
<Icon className="w-5 h-5 text-[#374151]" strokeWidth={1.5} />
|
|
||||||
</div>
|
|
||||||
{badge && (
|
|
||||||
<div className={cn(
|
|
||||||
"px-2.5 py-1 rounded-full text-[10px] font-bold tracking-tight whitespace-nowrap",
|
|
||||||
badge.variant === 'success' ? "bg-[#f1fffb] text-[#16c784]" :
|
|
||||||
badge.variant === 'warning' ? "bg-[#fff5e5] text-[#fca004]" :
|
|
||||||
badge.variant === 'info' ? "bg-[#f0f9ff] text-[#0ea5e9]" :
|
|
||||||
"bg-[#fdf5f4] text-[#e0352a]"
|
|
||||||
)}>
|
|
||||||
{badge.text}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-0.5">
|
|
||||||
<div className="text-[28px] font-bold tracking-tight text-[#111827] leading-none">
|
|
||||||
{value}
|
|
||||||
</div>
|
|
||||||
<div className="text-[11px] font-semibold text-[#6b7280] uppercase tracking-wider mt-1">
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TaskCard = ({ task }: { task: WorkflowTask }) => {
|
const TaskCard = ({ task }: { task: WorkflowTask }) => {
|
||||||
// const { primaryColor } = useAppTheme();
|
// const { primaryColor } = useAppTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -309,12 +265,12 @@ const Dashboard = (): ReactElement => {
|
|||||||
{/* Main Content Area (Left) */}
|
{/* Main Content Area (Left) */}
|
||||||
<div className="lg:col-span-8 xl:col-span-9 flex flex-col gap-6">
|
<div className="lg:col-span-8 xl:col-span-9 flex flex-col gap-6">
|
||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-5 gap-4">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="col-span-full text-center py-8 text-gray-400 text-sm">Loading statistics...</div>
|
<div className="col-span-full text-center py-8 text-gray-400 text-sm">Loading statistics...</div>
|
||||||
) : statCards.length > 0 ? (
|
) : statCards.length > 0 ? (
|
||||||
statCards.map((card, index) => (
|
statCards.map((card, index) => (
|
||||||
<StatCard key={index} {...card} />
|
<GradientStatCard key={index} {...card} />
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="col-span-full text-center py-8 text-gray-400 text-sm">No statistics available</div>
|
<div className="col-span-full text-center py-8 text-gray-400 text-sm">No statistics available</div>
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { useEffect, useState, type ReactElement } from "react";
|
import { useEffect, useState, type ReactElement } from "react";
|
||||||
import { Layout } from "@/components/layout/Layout";
|
import { Layout } from "@/components/layout/Layout";
|
||||||
import { DataTable, type Column, FilterDropdown } from "@/components/shared";
|
import {
|
||||||
|
DataTable,
|
||||||
|
type Column,
|
||||||
|
FilterDropdown,
|
||||||
|
GradientStatCard,
|
||||||
|
} from "@/components/shared";
|
||||||
import { aiService } from "@/services/ai-service";
|
import { aiService } from "@/services/ai-service";
|
||||||
import type { AICostSummary } from "@/types/ai";
|
import type { AICostSummary } from "@/types/ai";
|
||||||
import { showToast } from "@/utils/toast";
|
import { showToast } from "@/utils/toast";
|
||||||
@ -184,7 +189,11 @@ export const TenantAIDashboard = (): ReactElement => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
readOnly
|
readOnly
|
||||||
value={startDate && endDate ? `${formatDate(startDate)} - ${formatDate(endDate)}` : "All time"}
|
value={
|
||||||
|
startDate && endDate
|
||||||
|
? `${formatDate(startDate)} - ${formatDate(endDate)}`
|
||||||
|
: "All time"
|
||||||
|
}
|
||||||
className="w-full sm:w-[220px] h-11 px-4 pr-10 rounded-[8px] border border-[#D1D5DB] bg-white text-[14px] font-medium text-[#334155] outline-none cursor-pointer"
|
className="w-full sm:w-[220px] h-11 px-4 pr-10 rounded-[8px] border border-[#D1D5DB] bg-white text-[14px] font-medium text-[#334155] outline-none cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<Calendar className="w-4 h-4 text-[#475569] absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none" />
|
<Calendar className="w-4 h-4 text-[#475569] absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||||
@ -209,78 +218,30 @@ export const TenantAIDashboard = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Row */}
|
{/* Stats Row */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 gap-4 select-none">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 select-none">
|
||||||
{/* Card 1 */}
|
<GradientStatCard
|
||||||
<div className="rounded-[8px] bg-gradient-to-r from-[#3B82F6] via-[#22C55E] to-[#EAB308] p-[1px]">
|
icon={MessageSquare}
|
||||||
<div className="flex flex-col items-start gap-3 px-4 py-3 min-h-[108px] rounded-[7px] bg-[#F8FAFC]">
|
value={costs?.summary?.total_completions || 0}
|
||||||
<div className="text-[#94A3B8]">
|
label="Total Completions"
|
||||||
<MessageSquare className="w-4 h-4 stroke-[1.8]" />
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<GradientStatCard
|
||||||
<h4 className="text-[18px] leading-[24px] font-semibold text-[#1E293B] tracking-[-0.02em]">
|
icon={Coins}
|
||||||
{costs?.summary?.total_completions || 0}
|
value={formatTokens(costs?.summary?.total_tokens || 0)}
|
||||||
</h4>
|
label="Total Tokens"
|
||||||
<p className="text-[14px] leading-[20px] font-normal text-[#64748B]">
|
/>
|
||||||
Total Completions
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card 2 */}
|
<GradientStatCard
|
||||||
<div className="rounded-[8px] bg-gradient-to-r from-[#3B82F6] via-[#22C55E] to-[#EAB308] p-[1px]">
|
icon={DollarSign}
|
||||||
<div className="flex flex-col items-start gap-3 px-4 py-3 min-h-[108px] rounded-[7px] bg-[#F8FAFC]">
|
value={`$${(costs?.summary?.total_cost || 0).toFixed(2)}`}
|
||||||
<div className="text-[#94A3B8]">
|
label="Total Cost (USD)"
|
||||||
<Coins className="w-4 h-4 stroke-[1.8]" />
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<GradientStatCard
|
||||||
<h4 className="text-[18px] leading-[24px] font-semibold text-[#1E293B] tracking-[-0.02em]">
|
icon={Timer}
|
||||||
{formatTokens(costs?.summary?.total_tokens || 0)}
|
value={formatLatency(costs?.summary?.avg_latency_ms || 0)}
|
||||||
</h4>
|
label="Avg Latency (ms)"
|
||||||
<p className="text-[14px] leading-[20px] font-normal text-[#64748B]">
|
/>
|
||||||
Total Tokens
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card 3 */}
|
|
||||||
<div className="rounded-[8px] bg-gradient-to-r from-[#3B82F6] via-[#22C55E] to-[#EAB308] p-[1px]">
|
|
||||||
<div className="flex flex-col items-start gap-3 px-4 py-3 min-h-[108px] rounded-[7px] bg-[#F8FAFC]">
|
|
||||||
<div className="text-[#94A3B8]">
|
|
||||||
<DollarSign className="w-4 h-4 stroke-[1.8]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="text-[18px] leading-[24px] font-semibold text-[#1E293B] tracking-[-0.02em]">
|
|
||||||
${(costs?.summary?.total_cost || 0).toFixed(2)}
|
|
||||||
</h4>
|
|
||||||
<p className="text-[14px] leading-[20px] font-normal text-[#64748B]">
|
|
||||||
Total Cost (USD)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card 4 */}
|
|
||||||
<div className="rounded-[8px] bg-gradient-to-r from-[#3B82F6] via-[#22C55E] to-[#EAB308] p-[1px]">
|
|
||||||
<div className="flex flex-col items-start gap-3 px-4 py-3 min-h-[108px] rounded-[7px] bg-[#F8FAFC]">
|
|
||||||
<div className="text-[#94A3B8]">
|
|
||||||
<Timer className="w-4 h-4 stroke-[1.8]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="text-[18px] leading-[24px] font-semibold text-[#1E293B] tracking-[-0.02em]">
|
|
||||||
{formatLatency(costs?.summary?.avg_latency_ms || 0)}
|
|
||||||
</h4>
|
|
||||||
<p className="text-[14px] leading-[20px] font-normal text-[#64748B]">
|
|
||||||
Avg Latency (ms)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Charts and Tables */}
|
{/* Charts and Tables */}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
|
||||||
export interface StatCardData {
|
export interface StatCardData {
|
||||||
icon: React.ComponentType<{ className?: string; strokeWidth?: number; color?: string; style?: React.CSSProperties }>;
|
icon: LucideIcon;
|
||||||
value: string | number;
|
value: string | number;
|
||||||
label: string;
|
label: string;
|
||||||
badge?: {
|
badge?: {
|
||||||
text: string;
|
text: string;
|
||||||
variant: 'green' | 'gray';
|
variant: 'success' | 'warning' | 'info' | 'error' | 'green' | 'gray';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,7 +19,7 @@ export interface ActivityLog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface QuickAction {
|
export interface QuickAction {
|
||||||
icon: React.ComponentType<{ className?: string; strokeWidth?: number; color?: string; style?: React.CSSProperties }>;
|
icon: LucideIcon;
|
||||||
label: string;
|
label: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import path from "path";
|
|||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(),react()],
|
plugins: [tailwindcss(), react()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "src"),
|
"@": path.resolve(__dirname, "src"),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user