نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی
مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import type { AuditEvent, ReadinessResponse } from "@agy/contracts";
|
||||
import {
|
||||
Badge,
|
||||
Card,
|
||||
CardHint,
|
||||
CardTitle,
|
||||
EmptyState,
|
||||
ErrorState,
|
||||
LoadingState,
|
||||
Stat,
|
||||
Timeline,
|
||||
UsageBar,
|
||||
} from "@agy/ui";
|
||||
import { ApiError, api } from "@/lib/api";
|
||||
import { useSession } from "@/lib/session";
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { activeOrganization, user } = useSession();
|
||||
const [readiness, setReadiness] = React.useState<ReadinessResponse | null>(null);
|
||||
const [audit, setAudit] = React.useState<AuditEvent[]>([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
const load = React.useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const health = await api.readiness();
|
||||
setReadiness(health);
|
||||
if (activeOrganization) {
|
||||
setAudit(await api.listAudit(activeOrganization.id).catch(() => []));
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof ApiError ? err.message : "دریافت وضعیت ناموفق بود.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [activeOrganization]);
|
||||
|
||||
React.useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
|
||||
if (loading) return <LoadingState label="در حال دریافت وضعیت سازمان…" />;
|
||||
if (error) return <ErrorState message={error} onRetry={load} />;
|
||||
|
||||
if (!activeOrganization) {
|
||||
return (
|
||||
<EmptyState
|
||||
title="هنوز سازمانی انتخاب نشده است"
|
||||
description="برای دیدن داشبورد، ابتدا یک سازمان بسازید یا از منوی بالا یکی را انتخاب کنید."
|
||||
action={
|
||||
<a
|
||||
href="/onboarding"
|
||||
className="rounded-lg bg-accent px-4 py-2 text-sm text-white hover:bg-accent-strong"
|
||||
>
|
||||
ساخت سازمان
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const adapters = readiness?.checks.adapters ?? {};
|
||||
const databaseOk = readiness?.checks.database === "ok";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<header className="flex flex-col gap-1">
|
||||
<h1 className="text-lg font-medium text-slate-100">
|
||||
{activeOrganization.name}
|
||||
</h1>
|
||||
<p className="text-sm text-slate-400">
|
||||
خوش آمدید{user?.name ? `، ${user.name}` : ""}. وضعیت کلی سازمان در یک نگاه.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<Stat label="اعتبار" value="—" hint="پس از فاز billing" />
|
||||
<Stat label="وظایف فعال" value="0" hint="پس از فاز Task engine" />
|
||||
<Stat label="کارمندهای فعال" value="0" hint="پس از فاز Workforce" />
|
||||
<Stat
|
||||
label="وضعیت سرویس"
|
||||
value={databaseOk ? "سالم" : "ناپایدار"}
|
||||
tone={databaseOk ? "neutral" : "danger"}
|
||||
hint={`محیط: ${readiness?.checks.adapters ? "dev" : "—"}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 lg:grid-cols-2">
|
||||
<Card>
|
||||
<CardTitle>مصرف این ماه</CardTitle>
|
||||
<CardHint>سقف مصرف بر اساس پلن سازمان محاسبه میشود.</CardHint>
|
||||
<div className="mt-4">
|
||||
<UsageBar used={0} total={100} label="مصرف از سقف" />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardTitle>وضعیت اتصالها</CardTitle>
|
||||
<CardHint>
|
||||
مدل و provider از دید شما پنهان است؛ اینها فقط وضعیت فنی سرویساند.
|
||||
</CardHint>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<Badge tone={databaseOk ? "success" : "danger"}>
|
||||
پایگاه داده: {databaseOk ? "متصل" : "خطا"}
|
||||
</Badge>
|
||||
{Object.entries(adapters).map(([key, value]) => (
|
||||
<Badge key={key} tone={value === "mock" ? "warning" : "info"}>
|
||||
{key}: {value}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 lg:grid-cols-2">
|
||||
<Card>
|
||||
<CardTitle>رویدادهای اخیر سازمان</CardTitle>
|
||||
<CardHint>هر عملیات حساس در audit log ثبت میشود.</CardHint>
|
||||
<div className="mt-4">
|
||||
{audit.length === 0 ? (
|
||||
<p className="text-xs text-slate-500">رویدادی ثبت نشده است.</p>
|
||||
) : (
|
||||
<Timeline
|
||||
items={audit.slice(0, 8).map((event) => ({
|
||||
id: event.id,
|
||||
title: event.action,
|
||||
meta: new Date(event.created_at).toLocaleString("fa-IR"),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardTitle>دامنههای پیشنمایش</CardTitle>
|
||||
<CardHint>پس از فاز Domain Routing، دامنههای تست پروژهها اینجا دیده میشوند.</CardHint>
|
||||
<p className="mt-4 text-xs text-slate-500">دامنهای ثبت نشده است.</p>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user