Files
nextjs-boilerplate/app/[locale]/admin/logs/LogsTabs.tsx
T

138 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState } from 'react'
interface CrashRow {
id: string
event_type: string | null
severity: string
error_name: string | null
error_message: string | null
module: string | null
app_version: string | null
os: string | null
device_id: string | null
created_at: string
}
interface LicenseEventRow {
id: string
event_type: string
license_id: string
device_id: string | null
ip_address: string | null
app_version: string | null
created_at: string
}
function formatDateTime(iso: string) {
return new Date(iso).toLocaleString('tr-TR', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })
}
function severityBadge(severity: string) {
const map: Record<string, string> = {
fatal: 'bg-red-100 text-red-700 border-red-300 dark:bg-red-950/60 dark:text-red-400 dark:border-red-900',
error: 'bg-red-50 text-red-700 border-red-200 dark:bg-red-950/40 dark:text-red-400 dark:border-red-900',
warning: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/40 dark:text-amber-400 dark:border-amber-900',
info: 'bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950/40 dark:text-blue-400 dark:border-blue-900',
}
const cls = map[severity] || 'bg-gray-50 text-gray-600 border-gray-200 dark:bg-gray-900 dark:text-gray-400 dark:border-gray-800'
return <span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border shrink-0 ${cls}`}>{severity}</span>
}
function TabButton({ active, onClick, children, count }: { active: boolean; onClick: () => void; children: React.ReactNode; count: number }) {
return (
<button
onClick={onClick}
className={`px-4 py-2.5 text-sm font-medium border-b-2 transition-colors -mb-px ${
active
? 'border-indigo-600 text-indigo-600 dark:text-indigo-400'
: 'border-transparent text-gray-500 hover:text-gray-900 dark:hover:text-white'
}`}
>
{children} <span className="text-xs text-gray-400">({count})</span>
</button>
)
}
export default function LogsTabs({ crashes, licenseEvents }: { crashes: CrashRow[]; licenseEvents: LicenseEventRow[] }) {
const [tab, setTab] = useState<'crashes' | 'events'>('crashes')
return (
<div>
<div className="border-b border-gray-200 dark:border-gray-800 flex gap-2 mb-4">
<TabButton active={tab === 'crashes'} onClick={() => setTab('crashes')} count={crashes.length}>
Hata Kayıtları
</TabButton>
<TabButton active={tab === 'events'} onClick={() => setTab('events')} count={licenseEvents.length}>
Lisans Olayları
</TabButton>
</div>
{tab === 'crashes' && (
<div className="rounded-lg bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
<thead className="bg-gray-50 dark:bg-gray-900">
<tr>
{['Zaman', 'Önem', 'Tür', 'Hata', 'Modül', 'Sürüm / OS'].map((h) => (
<th key={h} className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">{h}</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-gray-100 dark:divide-gray-800">
{crashes.map((c) => (
<tr key={c.id} className="hover:bg-gray-50 dark:hover:bg-gray-900/50">
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">{formatDateTime(c.created_at)}</td>
<td className="px-4 py-3 whitespace-nowrap">{severityBadge(c.severity)}</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">{c.event_type}</td>
<td className="px-4 py-3 text-sm text-gray-900 dark:text-white max-w-md truncate" title={c.error_message || undefined}>
{c.error_name ? `${c.error_name}: ` : ''}{c.error_message || '—'}
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">{c.module || '—'}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-400">v{c.app_version} · {c.os || '—'}</td>
</tr>
))}
{crashes.length === 0 && (
<tr><td colSpan={6} className="px-4 py-8 text-center text-sm text-gray-500">Hata kaydı yok.</td></tr>
)}
</tbody>
</table>
</div>
</div>
)}
{tab === 'events' && (
<div className="rounded-lg bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
<thead className="bg-gray-50 dark:bg-gray-900">
<tr>
{['Zaman', 'Olay', 'Lisans', 'Cihaz', 'IP', 'Sürüm'].map((h) => (
<th key={h} className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">{h}</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-gray-100 dark:divide-gray-800">
{licenseEvents.map((e) => (
<tr key={e.id} className="hover:bg-gray-50 dark:hover:bg-gray-900/50">
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">{formatDateTime(e.created_at)}</td>
<td className="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">{e.event_type}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-400 font-mono">{e.license_id?.slice(0, 8)}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-400 font-mono">{e.device_id ? `${e.device_id.slice(0, 8)}…` : '—'}</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">{e.ip_address || '—'}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-400">{e.app_version || '—'}</td>
</tr>
))}
{licenseEvents.length === 0 && (
<tr><td colSpan={6} className="px-4 py-8 text-center text-sm text-gray-500">Lisans olayı yok.</td></tr>
)}
</tbody>
</table>
</div>
</div>
)}
</div>
)
}