138 lines
6.6 KiB
TypeScript
138 lines
6.6 KiB
TypeScript
'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>
|
||
)
|
||
}
|