Currency Api Dashboard

TypeScript

Modern currency exchange rate dashboard with real-time market data, conversion tools, analytics, historical trends, and responsive admin interface.

Stars
18
Forks
1
Downloads
N/A
Open Issues
0
Files main

Repository Files

Loading file structure...
resources/js/pages/dashboard.tsx
import { Head } from '@inertiajs/react';
import {
    Activity,
    CheckCircle2,
    XCircle,
    TrendingUp,
    Database,
    Sparkles,
} from 'lucide-react';
import React, { useMemo } from 'react';
import {
    Card,
    CardContent,
    CardDescription,
    CardHeader,
    CardTitle,
} from '@/components/ui/card';

interface ChartItem {
    date: string;
    calls: number;
}

interface RecentLog {
    id: number;
    token_name: string;
    endpoint: string;
    method: string;
    ip_address: string | null;
    status_code: number;
    created_at: string;
}

interface DashboardProps {
    stats: {
        total_calls_today: number;
        total_calls_month: number;
        success_rate: number;
        total_tokens: number;
        chart_data: ChartItem[];
        recent_logs: RecentLog[];
    };
}

export default function Dashboard({ stats }: DashboardProps) {
    // Graph SVG Calculations
    const maxCalls = useMemo(() => {
        const max = Math.max(...stats.chart_data.map((d) => d.calls), 0);

        return max === 0 ? 10 : Math.ceil(max * 1.15); // Add a 15% buffer
    }, [stats.chart_data]);

    const graphPoints = useMemo(() => {
        const width = 600;
        const height = 180;
        const paddingLeft = 35;
        const paddingRight = 10;
        const paddingTop = 15;
        const paddingBottom = 20;

        const chartWidth = width - paddingLeft - paddingRight;
        const chartHeight = height - paddingTop - paddingBottom;
        const dataCount = stats.chart_data.length;

        if (dataCount <= 1) {
            return { linePath: '', areaPath: '', points: [] };
        }

        const points = stats.chart_data.map((d, index) => {
            const x = paddingLeft + (index / (dataCount - 1)) * chartWidth;
            const y =
                height - paddingBottom - (d.calls / maxCalls) * chartHeight;

            return { x, y, calls: d.calls, date: d.date };
        });

        const linePath = points
            .map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`)
            .join(' ');
        const areaPath = `${linePath} L ${points[points.length - 1].x} ${height - paddingBottom} L ${points[0].x} ${height - paddingBottom} Z`;

        return { linePath, areaPath, points };
    }, [stats.chart_data, maxCalls]);

    return (
        <>
            <Head title="Platform Overview" />
            <div className="min-h-screen w-full space-y-6 bg-background p-4 text-foreground md:p-6">
                {/* Header */}
                <div className="flex flex-col gap-2 border-b border-border pb-6">
                    <div className="flex items-center gap-2">
                        <span className="rounded-lg bg-primary/10 p-1.5 text-primary">
                            <Sparkles className="h-5 w-5" />
                        </span>
                        <h1 className="text-3xl font-extrabold tracking-tight text-foreground">
                            Platform Overview
                        </h1>
                    </div>
                    <p className="text-sm text-muted-foreground">
                        Real-time query metrics, service logs audit, and
                        exchange rate system activity.
                    </p>
                </div>

                {/* Metrics Stats Grid */}
                <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
                    <Card className="border border-border bg-card text-card-foreground shadow-xs">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
                                Today's API Hits
                            </CardTitle>
                            <div className="flex items-center space-x-1">
                                <span className="relative flex h-2 w-2">
                                    <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75"></span>
                                    <span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500"></span>
                                </span>
                                <Activity className="h-4 w-4 text-emerald-500" />
                            </div>
                        </CardHeader>
                        <CardContent>
                            <div className="text-3xl font-black">
                                {stats.total_calls_today.toLocaleString()}
                            </div>
                            <p className="mt-1 text-xs text-muted-foreground">
                                Queries handled today
                            </p>
                        </CardContent>
                    </Card>

                    <Card className="border border-border bg-card text-card-foreground shadow-xs">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
                                Monthly API Hits
                            </CardTitle>
                            <TrendingUp className="h-4 w-4 text-primary" />
                        </CardHeader>
                        <CardContent>
                            <div className="text-3xl font-black">
                                {stats.total_calls_month.toLocaleString()}
                            </div>
                            <p className="mt-1 text-xs text-muted-foreground">
                                Current billing period
                            </p>
                        </CardContent>
                    </Card>

                    <Card className="border border-border bg-card text-card-foreground shadow-xs">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
                                API Success Rate
                            </CardTitle>
                            <CheckCircle2 className="h-4 w-4 text-emerald-500" />
                        </CardHeader>
                        <CardContent>
                            <div className="text-3xl font-black">
                                {stats.success_rate}%
                            </div>
                            <p className="mt-1 text-xs text-muted-foreground">
                                Successful status codes
                            </p>
                        </CardContent>
                    </Card>

                    <Card className="border border-border bg-card text-card-foreground shadow-xs">
                        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                            <CardTitle className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
                                Active Keys
                            </CardTitle>
                            <Database className="h-4 w-4 text-amber-500" />
                        </CardHeader>
                        <CardContent>
                            <div className="text-3xl font-black">
                                {stats.total_tokens}
                            </div>
                            <p className="mt-1 text-xs text-muted-foreground">
                                Authorized developers
                            </p>
                        </CardContent>
                    </Card>
                </div>

                {/* Graph Card */}
                <Card className="border border-border bg-card text-card-foreground shadow-xs">
                    <CardHeader>
                        <CardTitle className="text-lg font-bold">
                            API Traffic Metrics
                        </CardTitle>
                        <CardDescription>
                            Visualizing request count over the last 14 days.
                        </CardDescription>
                    </CardHeader>
                    <CardContent className="flex h-[210px] flex-col justify-end">
                        {stats.total_calls_today === 0 &&
                        stats.total_calls_month === 0 ? (
                            <div className="flex flex-1 flex-col items-center justify-center rounded-lg border border-dashed border-border bg-muted/10 p-4 text-center">
                                <Activity className="mb-2 h-8 w-8 animate-pulse text-muted-foreground/60" />
                                <p className="text-sm font-medium text-muted-foreground">
                                    No API traffic recorded yet.
                                </p>
                                <p className="mt-1 text-xs text-muted-foreground">
                                    Go to the "API Tokens" page, copy an
                                    authorization key and query the endpoints.
                                </p>
                            </div>
                        ) : (
                            <div className="relative h-[180px] w-full">
                                <svg
                                    viewBox="0 0 600 180"
                                    className="h-full w-full"
                                >
                                    <defs>
                                        <linearGradient
                                            id="areaGradient"
                                            x1="0"
                                            y1="0"
                                            x2="0"
                                            y2="1"
                                        >
                                            <stop
                                                offset="0%"
                                                stopColor="var(--primary)"
                                                stopOpacity="0.2"
                                            />
                                            <stop
                                                offset="100%"
                                                stopColor="var(--primary)"
                                                stopOpacity="0.0"
                                            />
                                        </linearGradient>
                                    </defs>

                                    {/* Horizontal Grid lines */}
                                    {[0, 0.25, 0.5, 0.75, 1].map(
                                        (ratio, index) => {
                                            const y = 15 + ratio * 145;

                                            return (
                                                <g key={index}>
                                                    <line
                                                        x1="35"
                                                        y1={y}
                                                        x2="590"
                                                        y2={y}
                                                        stroke="var(--border)"
                                                        strokeWidth="1"
                                                        strokeDasharray="4 4"
                                                    />
                                                    <text
                                                        x="30"
                                                        y={y + 3}
                                                        textAnchor="end"
                                                        fontSize="9"
                                                        fill="var(--muted-foreground)"
                                                        className="font-mono"
                                                    >
                                                        {Math.round(
                                                            maxCalls *
                                                                (1 - ratio),
                                                        )}
                                                    </text>
                                                </g>
                                            );
                                        },
                                    )}

                                    {/* Graph Paths */}
                                    {graphPoints.linePath && (
                                        <>
                                            <path
                                                d={graphPoints.areaPath}
                                                fill="url(#areaGradient)"
                                            />
                                            <path
                                                d={graphPoints.linePath}
                                                fill="none"
                                                stroke="var(--primary)"
                                                strokeWidth="2.5"
                                                strokeLinecap="round"
                                                strokeLinejoin="round"
                                            />
                                        </>
                                    )}

                                    {/* Interactive Hover Circles and Labels */}
                                    {graphPoints.points.map((pt, i) => (
                                        <g key={i} className="group/dot">
                                            <circle
                                                cx={pt.x}
                                                cy={pt.y}
                                                r="10"
                                                fill="transparent"
                                                className="cursor-pointer"
                                            />
                                            <circle
                                                cx={pt.x}
                                                cy={pt.y}
                                                r="4"
                                                fill="var(--background)"
                                                stroke="var(--primary)"
                                                strokeWidth="2"
                                                className="group-hover/dot:r-5.5 cursor-pointer transition-all"
                                            />
                                            {/* Tooltip on Hover */}
                                            <g className="pointer-events-none opacity-0 transition-opacity duration-200 group-hover/dot:opacity-100">
                                                <rect
                                                    x={pt.x - 45}
                                                    y={pt.y - 32}
                                                    width="90"
                                                    height="22"
                                                    rx="4"
                                                    fill="var(--foreground)"
                                                />
                                                <text
                                                    x={pt.x}
                                                    y={pt.y - 17}
                                                    textAnchor="middle"
                                                    fontSize="10"
                                                    fontWeight="600"
                                                    fill="var(--background)"
                                                >
                                                    {pt.calls} requests
                                                </text>
                                            </g>
                                        </g>
                                    ))}

                                    {/* X Axis Labels */}
                                    {graphPoints.points.map((pt, i) => {
                                        if (i % 2 !== 0) {
                                            return null;
                                        }

                                        return (
                                            <text
                                                key={i}
                                                x={pt.x}
                                                y="176"
                                                textAnchor="middle"
                                                fontSize="9"
                                                fill="var(--muted-foreground)"
                                            >
                                                {pt.date}
                                            </text>
                                        );
                                    })}
                                </svg>
                            </div>
                        )}
                    </CardContent>
                </Card>

                {/* Recent API Request Logs */}
                <Card className="border border-border bg-card text-card-foreground shadow-xs">
                    <CardHeader>
                        <CardTitle className="text-lg font-bold">
                            API Query History
                        </CardTitle>
                        <CardDescription>
                            Real-time audit log of developer API hits processed
                            by the middleware.
                        </CardDescription>
                    </CardHeader>
                    <CardContent>
                        <div className="overflow-hidden rounded-lg border border-border">
                            <div className="overflow-x-auto">
                                <table className="w-full border-collapse text-left text-sm">
                                    <thead>
                                        <tr className="border-b border-border bg-muted/40 font-semibold text-muted-foreground">
                                            <th className="p-3">Time</th>
                                            <th className="p-3">Token Name</th>
                                            <th className="w-24 p-3 text-center">
                                                Method
                                            </th>
                                            <th className="p-3">
                                                Endpoint Path
                                            </th>
                                            <th className="w-24 p-3 text-center">
                                                Status
                                            </th>
                                            <th className="w-36 p-3 pr-6 text-right">
                                                IP Address
                                            </th>
                                        </tr>
                                    </thead>
                                    <tbody className="divide-y divide-border">
                                        {stats.recent_logs.length === 0 ? (
                                            <tr>
                                                <td
                                                    colSpan={6}
                                                    className="p-8 text-center text-muted-foreground"
                                                >
                                                    No API request logs recorded
                                                    yet.
                                                </td>
                                            </tr>
                                        ) : (
                                            stats.recent_logs.map((log) => {
                                                const isSuccess =
                                                    log.status_code >= 200 &&
                                                    log.status_code < 300;

                                                return (
                                                    <tr
                                                        key={log.id}
                                                        className="transition-colors hover:bg-muted/10"
                                                    >
                                                        <td className="p-3 text-xs whitespace-nowrap text-muted-foreground">
                                                            {log.created_at}
                                                        </td>
                                                        <td className="p-3 font-semibold whitespace-nowrap">
                                                            {log.token_name}
                                                        </td>
                                                        <td className="p-3 text-center">
                                                            <span
                                                                className={`rounded-sm px-2 py-0.5 text-[10px] font-bold uppercase ${
                                                                    log.method ===
                                                                    'GET'
                                                                        ? 'bg-primary/10 text-primary'
                                                                        : 'bg-secondary/80 text-secondary-foreground'
                                                                }`}
                                                            >
                                                                {log.method}
                                                            </span>
                                                        </td>
                                                        <td
                                                            className="max-w-xs truncate p-3 font-mono text-xs"
                                                            title={log.endpoint}
                                                        >
                                                            {log.endpoint}
                                                        </td>
                                                        <td className="p-3 text-center">
                                                            <span
                                                                className={`inline-flex items-center gap-1 text-xs font-bold ${
                                                                    isSuccess
                                                                        ? 'text-emerald-600 dark:text-emerald-400'
                                                                        : 'text-red-500'
                                                                }`}
                                                            >
                                                                {isSuccess ? (
                                                                    <CheckCircle2 className="h-3.5 w-3.5" />
                                                                ) : (
                                                                    <XCircle className="h-3.5 w-3.5" />
                                                                )}
                                                                {
                                                                    log.status_code
                                                                }
                                                            </span>
                                                        </td>
                                                        <td className="p-3 pr-6 text-right font-mono text-xs text-muted-foreground">
                                                            {log.ip_address ||
                                                                '127.0.0.1'}
                                                        </td>
                                                    </tr>
                                                );
                                            })
                                        )}
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </CardContent>
                </Card>
            </div>
        </>
    );
}

Dashboard.layout = {
    breadcrumbs: [
        {
            title: 'Dashboard',
            href: '/dashboard',
        },
    ],
};