نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی

مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت.
تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
mjalmoudy94
2026-10-13 00:39:28 +03:30
commit 717e53a7ce
185 changed files with 39910 additions and 0 deletions
+147
View File
@@ -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>
);
}