Files
screenclipper/apps/frontend/app/page.tsx
T
ayrisdevandClaude Sonnet 5 58ef40edf8 feat: segment süresi varsayılanını 15 dk -> 5 dk düşür + dashboard'u kart-grid'e çevir
Global SEGMENT_TIME_SEC varsayılanı 900->300 (docker-compose, .env.example,
env.ts, signal_detection.py fallback'i) — kanal bazlı override zaten
panelden ayarlanabiliyordu, bu sadece varsayılanı değiştiriyor.

Dashboard'daki kanal listesi tablo yerine kart-grid: her kanal kendi
kartında, durum rozetleri üstte belirgin, aksiyonlar altta — önceki
tablo düzeni (özellikle aksiyon sütunundaki sıkışık butonlar) okunması
zor bulunuyordu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 22:55:12 +03:00

128 lines
5.2 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.
import Link from "next/link";
import { prisma } from "@streamclipper/db";
import { addChannel, toggleChannelActive, toggleAllChannels, checkChannelNow } from "./actions";
import { fetchChannelStatuses } from "../lib/apiDaemon";
import { formatTr } from "../lib/formatDate";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
import { cn } from "@/lib/utils";
export const dynamic = "force-dynamic";
export default async function DashboardPage() {
const [channels, statuses] = await Promise.all([
prisma.channel.findMany({
orderBy: { name: "asc" },
include: {
sessions: {
where: { endedAt: null },
orderBy: { startedAt: "desc" },
take: 1,
},
},
}),
fetchChannelStatuses(),
]);
return (
<div className="flex flex-col gap-6">
<h1 className="text-2xl font-semibold tracking-tight">Kanal Durumu</h1>
<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="text-sm text-muted-foreground">Henüz kanal eklenmedi.</p>
) : (
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-muted-foreground">{channels.length} kanal</span>
<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>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{channels.map((c) => {
const recording = c.sessions.length > 0;
const status = statuses.get(c.id);
return (
<Card
key={c.id}
className={cn("gap-4 py-5", recording && "border-live/40")}
>
<CardHeader className="gap-1 px-5">
<div className="flex items-start justify-between gap-2">
<Link href={`/channels/${c.id}`} className="font-semibold leading-tight hover:text-primary hover:underline">
{c.name}
</Link>
<Badge variant={c.isActive ? "ok" : "muted"} className="shrink-0">
{c.isActive ? "Aktif" : "Pasif"}
</Badge>
</div>
<span className="font-mono-num text-xs text-muted-foreground">{c.youtubeHandle}</span>
</CardHeader>
<CardContent className="flex flex-col gap-2 px-5">
<div className="flex flex-wrap items-center gap-1.5">
<Badge variant={recording ? "live" : "muted"}>
{recording ? "🔴 Kayıtta" : "Kayıtta değil"}
</Badge>
{status?.lastPollError && (
<Badge
variant="err"
title={`${formatTr(status.lastPollError.at)}${status.lastPollError.message}`}
>
hata
</Badge>
)}
</div>
<span className="font-mono-num text-xs text-muted-foreground">
Son kontrol: {c.lastCheckedAt ? formatTr(c.lastCheckedAt) : "—"}
</span>
</CardContent>
<CardFooter className="gap-2 px-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>
</CardFooter>
</Card>
);
})}
</div>
</div>
)}
</div>
);
}