"use client"; import Link from "next/link"; import { useCallback, useEffect, useState } from "react"; import { ChevronLeft, ChevronRight, Download, Puzzle, Radar, Sparkles, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { PageHeader } from "@/components/layout/page-header"; import { DraftCard } from "@/components/draft-card"; import { notify } from "@/lib/notifications/store"; interface MatrixRow { id: string; sortOrder: number | null; searchTag: string | null; angle: string | null; hook: string | null; text: string; referenceNotes: string | null; sources: string | null; imageBrief?: string | null; imagePath?: string | null; imagePaths?: string | null; draftType?: string | null; rationale?: string | null; status: string; createdAt: string; } interface AccountStatus { id: string; sessionSynced?: boolean; browserConnected?: boolean; } const PAGE_SIZE = 10; export default function MatrixPage() { const [rows, setRows] = useState([]); const [total, setTotal] = useState(0); const [totalPages, setTotalPages] = useState(1); const [page, setPage] = useState(1); const [loading, setLoading] = useState(true); const [sessionSynced, setSessionSynced] = useState(false); const [hasAccount, setHasAccount] = useState(false); const [selectMode, setSelectMode] = useState(false); const [selectedIds, setSelectedIds] = useState>(new Set()); const [bulkDeleting, setBulkDeleting] = useState(false); const [confirmBulkDelete, setConfirmBulkDelete] = useState(false); const load = useCallback(async (targetPage = page) => { setLoading(true); try { const [matrixRes, accountsRes] = await Promise.all([ fetch(`/api/matrix?page=${targetPage}&limit=${PAGE_SIZE}`), fetch("/api/accounts"), ]); const [data, accountData] = await Promise.all([ matrixRes.json().catch(() => ({})), accountsRes.json().catch(() => ({})), ]); if (!matrixRes.ok) { notify({ type: "error", title: "載入草稿失敗", message: data.error ?? "請稍後再試", }); setRows([]); setTotal(0); setTotalPages(1); return; } const accounts = (accountData.accounts ?? []) as AccountStatus[]; const active = accounts.find((account) => account.id === accountData.activeAccountId) ?? accounts[0]; const fetchedRows = data.rows ?? []; const fetchedTotal = data.total ?? 0; const fetchedTotalPages = data.totalPages ?? 1; setRows(fetchedRows); setTotal(fetchedTotal); setTotalPages(fetchedTotalPages); setHasAccount(Boolean(active)); setSessionSynced(Boolean(active?.sessionSynced || active?.browserConnected)); if (fetchedRows.length === 0 && targetPage > 1) { setPage(1); } else if (targetPage > fetchedTotalPages) { setPage(fetchedTotalPages); } } catch { notify({ type: "error", title: "載入草稿失敗", message: "網路連線異常,請稍後再試", }); setRows([]); setTotal(0); setTotalPages(1); } finally { setLoading(false); } }, [page]); useEffect(() => { void load(page); }, [page, load]); useEffect(() => { setSelectedIds(new Set()); }, [page]); function goToPage(p: number) { if (p < 1 || p > totalPages || p === page) return; setPage(p); } function exitSelectMode() { setSelectMode(false); setSelectedIds(new Set()); } function toggleDraftSelection(id: string, selected: boolean) { setSelectedIds((prev) => { const next = new Set(prev); if (selected) next.add(id); else next.delete(id); return next; }); } function toggleSelectAllOnPage() { const pageIds = rows.map((row) => row.id); const allSelected = pageIds.length > 0 && pageIds.every((id) => selectedIds.has(id)); setSelectedIds((prev) => { const next = new Set(prev); if (allSelected) { for (const id of pageIds) next.delete(id); } else { for (const id of pageIds) next.add(id); } return next; }); } async function handleBulkDelete() { const ids = Array.from(selectedIds); if (ids.length === 0) return; setBulkDeleting(true); try { const res = await fetch("/api/drafts", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids }), }); const data = await res.json(); if (!res.ok) { notify({ type: "error", title: "批量刪除失敗", message: data.error ?? "請稍後再試", }); return; } notify({ type: "success", title: "已刪除選取的草稿", message: `共刪除 ${data.deleted ?? ids.length} 篇`, }); exitSelectMode(); await load(page); } finally { setBulkDeleting(false); } } const pageIds = rows.map((row) => row.id); const allOnPageSelected = pageIds.length > 0 && pageIds.every((id) => selectedIds.has(id)); const selectedCount = selectedIds.size; return (
開始海巡 } />

已生成內容

{total > 0 ? `共 ${total} 篇 · 第 ${page}/${totalPages} 頁` : "完成第一個風格任務後會出現在這裡"}

{total > 0 && (
{selectMode ? ( <> ) : ( <> )}
)}
{loading ? (
{[0, 1, 2].map((i) => (
))}
) : rows.length === 0 ? (
{sessionSynced ? : }

{sessionSynced ? "Extension 已同步,可以開始海巡" : hasAccount ? "先同步你的 Threads Extension" : "先建立經營帳號"}

{sessionSynced ? "目前帳號的 Threads Session 已就緒。新增海巡任務,設定主題後開始分析與抓取。" : hasAccount ? "登入 Threads 後按 Extension 同步;完成後這裡會自動辨識帳號狀態。" : "建立帳號並填好人設,再透過 Extension 同步 Threads Session。"}

{sessionSynced ? ( ) : hasAccount ? ( ) : ( )}
) : ( <>
{rows.map((row, index) => ( load(page)} index={index} selectable={selectMode} selected={selectedIds.has(row.id)} onSelectedChange={(selected) => toggleDraftSelection(row.id, selected)} /> ))}
{totalPages > 1 && (
{page} / {totalPages}
)} )}
); }