fiemwl / src /app /(dashboard) /dashboard /cache /components /CacheEntriesTab.tsx
automindy's picture
Upload 1980 files
6111b2b verified
Raw
History Blame Contribute Delete
6.58 kB
"use client";
import { useState, useEffect, useCallback } from "react";
import { Button } from "@/shared/components";
import { useTranslations } from "next-intl";
interface CacheEntry {
id: string;
signature: string;
model: string;
hit_count: number;
tokens_saved: number;
created_at: string;
expires_at: string;
}
interface Pagination {
page: number;
limit: number;
total: number;
totalPages: number;
}
export default function CacheEntriesTab() {
const t = useTranslations("cache");
const [entries, setEntries] = useState<CacheEntry[]>([]);
const [pagination, setPagination] = useState<Pagination>({
page: 1,
limit: 20,
total: 0,
totalPages: 0,
});
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState("");
const [deleting, setDeleting] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const fetchEntries = useCallback(
async (page = 1) => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams({ page: String(page), limit: String(pagination.limit) });
if (search) params.set("search", search);
const res = await fetch(`/api/cache/entries?${params}`);
if (res.ok) {
const data = await res.json();
setEntries(data.entries);
setPagination(data.pagination);
} else {
setEntries([]);
setError(t("entriesLoadError"));
}
} catch {
setEntries([]);
setError(t("entriesLoadError"));
} finally {
setLoading(false);
}
},
[pagination.limit, search, t]
);
useEffect(() => {
fetchEntries();
}, [fetchEntries]);
const handleDelete = async (signature: string) => {
setDeleting(signature);
try {
await fetch(`/api/cache/entries?signature=${encodeURIComponent(signature)}`, {
method: "DELETE",
});
await fetchEntries(pagination.page);
} finally {
setDeleting(null);
}
};
const formatDate = (dateStr: string) => {
return new Date(dateStr).toLocaleString();
};
return (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<input
type="text"
placeholder={t("searchEntries")}
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && fetchEntries()}
className="flex-1 px-3 py-2 text-sm rounded-lg border border-border bg-surface text-text-main placeholder:text-text-muted"
/>
<Button variant="secondary" size="sm" onClick={() => fetchEntries()}>
{t("search")}
</Button>
</div>
{loading ? (
<div className="text-sm text-text-muted">{t("loading")}</div>
) : error ? (
<div className="flex items-center justify-between gap-3 rounded-xl border border-red-500/20 bg-red-500/5 px-4 py-3">
<div className="text-sm text-red-300">{error}</div>
<Button variant="secondary" size="sm" onClick={() => fetchEntries(pagination.page)}>
{t("refresh")}
</Button>
</div>
) : entries.length === 0 ? (
<div className="text-sm text-text-muted text-center py-8">{t("noEntries")}</div>
) : (
<>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="text-left text-xs text-text-muted border-b border-border/30">
<th className="pb-2 pr-4">{t("signature")}</th>
<th className="pb-2 pr-4">{t("model")}</th>
<th className="pb-2 pr-4">{t("hits")}</th>
<th className="pb-2 pr-4">{t("tokensSaved")}</th>
<th className="pb-2 pr-4">{t("created")}</th>
<th className="pb-2 pr-4">{t("expires")}</th>
<th className="pb-2">{t("actions")}</th>
</tr>
</thead>
<tbody>
{entries.map((entry) => (
<tr key={entry.id} className="border-b border-border/20">
<td className="py-2 pr-4 font-mono text-xs">
{entry.signature.slice(0, 12)}...
</td>
<td className="py-2 pr-4">{entry.model}</td>
<td className="py-2 pr-4 tabular-nums">{entry.hit_count}</td>
<td className="py-2 pr-4 tabular-nums text-green-500">
{(entry.tokens_saved ?? 0).toLocaleString()}
</td>
<td className="py-2 pr-4 text-xs text-text-muted">
{formatDate(entry.created_at)}
</td>
<td className="py-2 pr-4 text-xs text-text-muted">
{formatDate(entry.expires_at)}
</td>
<td className="py-2">
<button
onClick={() => handleDelete(entry.signature)}
disabled={deleting === entry.signature}
className="text-xs text-red-400 hover:text-red-300 disabled:opacity-50"
>
{deleting === entry.signature ? "..." : "🗑️"}
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Pagination */}
{pagination.totalPages > 1 && (
<div className="flex items-center justify-center gap-2 pt-2">
<Button
variant="secondary"
size="sm"
onClick={() => fetchEntries(pagination.page - 1)}
disabled={pagination.page <= 1}
>
</Button>
<span className="text-sm text-text-muted">
{pagination.page} / {pagination.totalPages}
</span>
<Button
variant="secondary"
size="sm"
onClick={() => fetchEntries(pagination.page + 1)}
disabled={pagination.page >= pagination.totalPages}
>
</Button>
</div>
)}
</>
)}
</div>
);
}