feat: panel UI'ını Tailwind v4 + shadcn/ui ile yeniden tasarla (Faz 4)
Elle yazılmış tek-dosya CSS (sabit koyu tema, ham table/button, window.confirm ile silme onayı) yerine tutarlı bir bileşen sistemi: Card/Table/Badge/Button/ Input/Switch/AlertDialog primitifleri, Hanken Grotesk + JetBrains Mono tipografi çifti, "broadcast/monitör" temalı özel bir renk paleti (camgöbeği accent, ok/warn/err/live durum rengi sözlüğü). Tüm sayfalar (dashboard, kanal detay, segmentler, ayarlar, istatistik, kullanıcılar, login) aynı oturumda taşındı — silme onayları artık gerçek AlertDialog, native confirm() değil. Veri akışı/server action'lar değişmedi, tamamen görsel katman. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+94
-79
@@ -2,6 +2,11 @@ import Link from "next/link";
|
||||
import { prisma } from "@streamclipper/db";
|
||||
import { addChannel, toggleChannelActive, toggleAllChannels, checkChannelNow } from "./actions";
|
||||
import { fetchChannelStatuses } from "../lib/apiDaemon";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -21,89 +26,99 @@ export default async function DashboardPage() {
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Kanal Durumu</h1>
|
||||
<div className="flex flex-col gap-6">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Kanal Durumu</h1>
|
||||
|
||||
<form action={addChannel} className="card add-channel-form">
|
||||
<input name="name" placeholder="Kanal adı" required />
|
||||
<input name="youtubeHandle" placeholder="@handle" required />
|
||||
<input name="channelId" placeholder="channel_id (UC...)" required />
|
||||
<button type="submit">Kanal Ekle</button>
|
||||
</form>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Yeni Kanal Ekle</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form action={addChannel} className="flex flex-wrap gap-2">
|
||||
<Input name="name" placeholder="Kanal adı" required className="flex-1 min-w-[140px]" />
|
||||
<Input name="youtubeHandle" placeholder="@handle" required className="flex-1 min-w-[120px]" />
|
||||
<Input name="channelId" placeholder="channel_id (UC...)" required className="flex-1 min-w-[180px]" />
|
||||
<Button type="submit">Kanal Ekle</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{channels.length === 0 ? (
|
||||
<p className="empty">Henüz kanal eklenmedi.</p>
|
||||
<p className="text-sm text-muted-foreground">Henüz kanal eklenmedi.</p>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ display: "flex", gap: "0.5rem", marginBottom: "0.6rem" }}>
|
||||
<form action={toggleAllChannels.bind(null, true)}>
|
||||
<button type="submit" className="badge ok" style={{ border: "none", cursor: "pointer" }}>
|
||||
Tümünü Devam Ettir
|
||||
</button>
|
||||
</form>
|
||||
<form action={toggleAllChannels.bind(null, false)}>
|
||||
<button type="submit" className="badge muted" style={{ border: "none", cursor: "pointer" }}>
|
||||
Tümünü Duraklat
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kanal</th>
|
||||
<th>Durum</th>
|
||||
<th>Kayıt</th>
|
||||
<th>Son Kontrol</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{channels.map((c) => {
|
||||
const recording = c.sessions.length > 0;
|
||||
const status = statuses.get(c.id);
|
||||
return (
|
||||
<tr key={c.id}>
|
||||
<td>
|
||||
<Link href={`/channels/${c.id}`}>{c.name}</Link>{" "}
|
||||
<span className="mono">{c.youtubeHandle}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className={`badge ${c.isActive ? "ok" : "muted"}`}>
|
||||
{c.isActive ? "Aktif" : "Pasif"}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className={`badge ${recording ? "warn" : "muted"}`}>
|
||||
{recording ? "🔴 Kayıtta" : "—"}
|
||||
</span>
|
||||
{status?.lastPollError && (
|
||||
<span className="badge err" style={{ marginLeft: "0.4rem" }}>
|
||||
hata
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="mono">
|
||||
{c.lastCheckedAt ? new Date(c.lastCheckedAt).toLocaleString("tr-TR") : "—"}
|
||||
</td>
|
||||
<td style={{ display: "flex", gap: "0.35rem" }}>
|
||||
<form action={toggleChannelActive.bind(null, c.id, !c.isActive)}>
|
||||
<button type="submit" className="badge muted" style={{ border: "none", cursor: "pointer" }}>
|
||||
{c.isActive ? "Duraklat" : "Devam Ettir"}
|
||||
</button>
|
||||
</form>
|
||||
<form action={checkChannelNow.bind(null, c.id)}>
|
||||
<button type="submit" className="badge muted" style={{ border: "none", cursor: "pointer" }}>
|
||||
Şimdi Kontrol Et
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
<Card>
|
||||
<CardHeader className="flex-row items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">{channels.length} kanal</CardTitle>
|
||||
<div className="flex gap-2">
|
||||
<form action={toggleAllChannels.bind(null, true)}>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
Tümünü Devam Ettir
|
||||
</Button>
|
||||
</form>
|
||||
<form action={toggleAllChannels.bind(null, false)}>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
Tümünü Duraklat
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Kanal</TableHead>
|
||||
<TableHead>Durum</TableHead>
|
||||
<TableHead>Kayıt</TableHead>
|
||||
<TableHead>Son Kontrol</TableHead>
|
||||
<TableHead className="text-right">Aksiyon</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{channels.map((c) => {
|
||||
const recording = c.sessions.length > 0;
|
||||
const status = statuses.get(c.id);
|
||||
return (
|
||||
<TableRow key={c.id}>
|
||||
<TableCell>
|
||||
<Link href={`/channels/${c.id}`} className="font-medium hover:text-primary hover:underline">
|
||||
{c.name}
|
||||
</Link>{" "}
|
||||
<span className="font-mono-num text-xs text-muted-foreground">{c.youtubeHandle}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={c.isActive ? "ok" : "muted"}>{c.isActive ? "Aktif" : "Pasif"}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Badge variant={recording ? "live" : "muted"}>{recording ? "🔴 Kayıtta" : "—"}</Badge>
|
||||
{status?.lastPollError && <Badge variant="err">hata</Badge>}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono-num text-xs text-muted-foreground">
|
||||
{c.lastCheckedAt ? new Date(c.lastCheckedAt).toLocaleString("tr-TR") : "—"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex justify-end gap-1.5">
|
||||
<form action={toggleChannelActive.bind(null, c.id, !c.isActive)}>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
{c.isActive ? "Duraklat" : "Devam Ettir"}
|
||||
</Button>
|
||||
</form>
|
||||
<form action={checkChannelNow.bind(null, c.id)}>
|
||||
<Button type="submit" variant="ghost" size="sm">
|
||||
Şimdi Kontrol Et
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user