-
-
+ {/* 4. Two-column: Recommendation + Agent Activity */}
+
+ {/* Recommendation */}
+
+
推荐下一步
+
+
+ {recommendation.icon}
-
-
-
- {/* Agent Activity Summary */}
-
-
-
-
- Agent活动
+
+
+ {recommendation.title}
+
+
+ {recommendation.description}
-
- 查看全部
-
-
-
+
+
+
+
+
+
+
+
+ {/* Agent Activity */}
+
+
+
{MOCK_AGENTS.map((agent) => (
))}
-
-
+
+
);
diff --git a/frontend/app/(dashboard)/dashboard/queries/page.tsx b/frontend/app/(dashboard)/dashboard/queries/page.tsx
index 693eed5..a74f249 100644
--- a/frontend/app/(dashboard)/dashboard/queries/page.tsx
+++ b/frontend/app/(dashboard)/dashboard/queries/page.tsx
@@ -1,7 +1,6 @@
"use client";
-import { useEffect, useState } from "react";
-import { useSession } from "next-auth/react";
+import { useState } from "react";
import {
Table,
TableBody,
@@ -31,8 +30,11 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-import { api } from "@/lib/api";
+import { fetchWithAuth } from "@/lib/api/client";
+import type { QueryListResponse, ApiQueryItem } from "@/lib/api/queries";
import { PLATFORM_MAP, PLATFORMS } from "@/lib/platforms";
+import { useApi } from "@/lib/hooks/use-api";
+import { LoadingState, ErrorState, EmptyState } from "@/components/ui/api-states";
import {
Plus,
Pencil,
@@ -40,22 +42,9 @@ import {
Play,
Loader2,
Search,
- AlertTriangle,
CheckCircle,
} from "lucide-react";
-interface QueryItem {
- id: string;
- keyword: string;
- target_brand: string;
- brand_aliases?: string[];
- platforms: string[];
- frequency: string;
- status: string;
- last_queried_at: string | null;
- created_at: string;
-}
-
interface QueryFormData {
keyword: string;
target_brand: string;
@@ -78,10 +67,11 @@ const emptyForm: QueryFormData = {
};
export default function QueriesPage() {
- const { data: session } = useSession();
- const [queries, setQueries] = useState
([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
+ const { data: queriesResponse, isLoading: loading, error: apiError, refresh: refreshQueries } =
+ useApi("/api/v1/queries/");
+
+ const queries: ApiQueryItem[] = (queriesResponse?.items ?? []);
+ const error = apiError?.message ?? null;
const [dialogOpen, setDialogOpen] = useState(false);
const [editingId, setEditingId] = useState(null);
@@ -95,41 +85,25 @@ export default function QueriesPage() {
const [actionLoading, setActionLoading] = useState(null);
const [formErrors, setFormErrors] = useState>({});
const [successMsg, setSuccessMsg] = useState(null);
+ const [mutationError, setMutationError] = useState(null);
function showSuccess(msg: string) {
setSuccessMsg(msg);
setTimeout(() => setSuccessMsg(null), 3000);
}
- useEffect(() => {
- if (!session?.accessToken) return;
- loadQueries();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [session?.accessToken]);
-
- async function loadQueries() {
- try {
- setLoading(true);
- const data = await api.queries.list(session!.accessToken);
- setQueries(data.items || []);
- setError(null);
- } catch (err) {
- setError(err instanceof Error ? err.message : "加载查询词失败");
- } finally {
- setLoading(false);
- }
- }
-
function openAddDialog() {
setEditingId(null);
setFormData(emptyForm);
setFormErrors({});
+ setMutationError(null);
setDialogOpen(true);
}
- function openEditDialog(item: QueryItem) {
+ function openEditDialog(item: ApiQueryItem) {
setEditingId(item.id);
setFormErrors({});
+ setMutationError(null);
setFormData({
keyword: item.keyword,
target_brand: item.target_brand,
@@ -156,13 +130,11 @@ export default function QueriesPage() {
}
async function handleSave() {
- if (!session?.accessToken) return;
- if (!validateForm()) {
- return;
- }
+ if (!validateForm()) return;
try {
setSaving(true);
+ setMutationError(null);
const payload = {
keyword: formData.keyword.trim(),
target_brand: formData.target_brand.trim(),
@@ -175,16 +147,22 @@ export default function QueriesPage() {
};
if (editingId) {
- await api.queries.update(session.accessToken, editingId, payload);
+ await fetchWithAuth(`/api/v1/queries/${editingId}`, {
+ method: "PUT",
+ body: JSON.stringify(payload),
+ });
} else {
- await api.queries.create(session.accessToken, payload);
+ await fetchWithAuth("/api/v1/queries/", {
+ method: "POST",
+ body: JSON.stringify(payload),
+ });
}
setDialogOpen(false);
- setSaving(false);
showSuccess(editingId ? "修改成功" : "添加成功");
- await loadQueries();
+ refreshQueries();
} catch (err) {
- setError(err instanceof Error ? err.message : "保存失败");
+ setMutationError(err instanceof Error ? err.message : "保存失败");
+ } finally {
setSaving(false);
}
}
@@ -195,31 +173,30 @@ export default function QueriesPage() {
}
async function handleDelete() {
- if (!session?.accessToken || !deletingId) return;
+ if (!deletingId) return;
try {
setDeleting(true);
- await api.queries.delete(session.accessToken, deletingId);
+ await fetchWithAuth(`/api/v1/queries/${deletingId}`, { method: "DELETE" });
setDeleteDialogOpen(false);
setDeletingId(null);
showSuccess("删除成功");
- await loadQueries();
+ refreshQueries();
} catch (err) {
- setError(err instanceof Error ? err.message : "删除失败");
+ setMutationError(err instanceof Error ? err.message : "删除失败");
} finally {
setDeleting(false);
}
}
async function handleRunQuery(id: string) {
- if (!session?.accessToken) return;
setActionLoading(id);
- setError(null);
+ setMutationError(null);
try {
- await api.queries.runNow(session.accessToken, id);
+ await fetchWithAuth(`/api/v1/queries/${id}/run-now`, { method: "POST" });
showSuccess("查询已执行");
- await loadQueries();
+ refreshQueries();
} catch (err) {
- setError(err instanceof Error ? err.message : "立即查询失败");
+ setMutationError(err instanceof Error ? err.message : "立即查询失败");
} finally {
setActionLoading(null);
}
@@ -243,9 +220,21 @@ export default function QueriesPage() {
管理您的关键词查询任务