feat(frontend): add React UI — procurement office, wizard, suppliers, inventory panel (dash port), role-based login
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Header, PageLayout } from "../../components/layout/Header";
|
||||
import { startCasdoorLogin } from "../../lib/casdoor";
|
||||
import { api } from "../../lib/api";
|
||||
import { useAuthStore } from "../../store/auth";
|
||||
import type { Project } from "../../lib/types";
|
||||
|
||||
// ورود به سامانه — Casdoor (OIDC + PKCE) + ورود آزمایشی برای توسعه
|
||||
export function LoginPage() {
|
||||
const setToken = useAuthStore((s) => s.setToken);
|
||||
const setUser = useAuthStore((s) => s.setUser);
|
||||
const setDevUser = useAuthStore((s) => s.setDevUser);
|
||||
const navigate = useNavigate();
|
||||
const [params] = useSearchParams();
|
||||
|
||||
// ورود آزمایشی: پروژهها از بکاند خوانده میشوند
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [devRole, setDevRole] = useState<"pm" | "admin" | "procurement_manager">("pm");
|
||||
const [devProjectId, setDevProjectId] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
void api.projects.list().then(setProjects).catch(() => setProjects([]));
|
||||
}, []);
|
||||
|
||||
const devProject = useMemo(
|
||||
() => projects.find((p) => p.id === devProjectId) || null,
|
||||
[projects, devProjectId],
|
||||
);
|
||||
|
||||
// Callback: ?code=...&state=...
|
||||
useEffect(() => {
|
||||
const code = params.get("code");
|
||||
if (!code) return;
|
||||
void (async () => {
|
||||
try {
|
||||
const { exchangeCodeForToken } = await import("../../lib/casdoor");
|
||||
const tok = await exchangeCodeForToken(code);
|
||||
setToken(tok.access_token);
|
||||
// fetch profile from our backend
|
||||
const { apiV26 } = await import("../../lib/api");
|
||||
const profile = await apiV26.users.me();
|
||||
setUser(profile);
|
||||
toast.success(`خوش آمدید، ${profile.display_name}`);
|
||||
// مدیر پروژه → پنل انبار؛ بقیه → دفتر پیشنهادهای خرید
|
||||
navigate(profile.role === "pm" ? "/inventory" : "/");
|
||||
} catch (err) {
|
||||
toast.error(`خطا در ورود: ${err instanceof Error ? err.message : "خطای ناشناخته"}`);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [params]);
|
||||
|
||||
const devLogin = () => {
|
||||
setDevUser(devRole, devRole === "pm" ? devProjectId || null : null, devProject?.name || null);
|
||||
toast.success("ورود آزمایشی انجام شد — نقش: " + (devRole === "pm" ? "مدیر پروژه" : devRole === "procurement_manager" ? "مدیر تدارکات" : "ادمین"));
|
||||
// مدیر پروژه → پنل انبار؛ مدیر تدارکات/ادمین → دفتر پیشنهادهای خرید
|
||||
navigate(devRole === "pm" ? "/inventory" : "/");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Header title="ورود به سامانه" sub="احراز هویت با حساب سازمانی (Casdoor)" />
|
||||
<PageLayout className="flex max-w-md flex-col items-start gap-4">
|
||||
<div className="rounded-xl border border-line bg-panel p-4 shadow-sm">
|
||||
<h3 className="mb-1 text-[14px] font-bold">ورود با حساب سازمانی</h3>
|
||||
<p className="mb-3 text-[12.5px] leading-6 text-muted">
|
||||
برای ورود به سامانه تدارکات و انبار، با حساب سازمانی خود وارد شوید. پس از ورود، نقش شما
|
||||
(مدیر پروژه / مدیر تدارکات / ادمین) مشخص و دسترسیها بر اساس آن فعال میشود.
|
||||
</p>
|
||||
<Button onClick={() => void startCasdoorLogin()}>ورود با Casdoor</Button>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-dashed border-accent/40 bg-blue-bg/20 p-4">
|
||||
<h3 className="mb-1 text-[14px] font-bold">ورود آزمایشی (توسعه)</h3>
|
||||
<p className="mb-3 text-[12.5px] leading-6 text-muted">
|
||||
بدون Casdoor وارد شوید — نقش و پروژه از طریق هدرهای توسعه به بکاند ارسال میشود
|
||||
(فقط برای توسعه و تست محلی).
|
||||
</p>
|
||||
<div className="grid gap-3">
|
||||
<div className="field">
|
||||
<label>نقش</label>
|
||||
<select
|
||||
className="h-9 w-full rounded-lg border border-line bg-panel px-3 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
|
||||
value={devRole}
|
||||
onChange={(e) => setDevRole(e.target.value as "pm" | "admin")}
|
||||
>
|
||||
<option value="pm">مدیر پروژه (PM)</option>
|
||||
<option value="procurement_manager">مدیر تدارکات</option>
|
||||
<option value="admin">ادمین (فقطخواندنی)</option>
|
||||
</select>
|
||||
</div>
|
||||
{devRole === "pm" && (
|
||||
<div className="field">
|
||||
<label>پروژه</label>
|
||||
<select
|
||||
className="h-9 w-full rounded-lg border border-line bg-panel px-3 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
|
||||
value={devProjectId}
|
||||
onChange={(e) => setDevProjectId(e.target.value)}
|
||||
>
|
||||
<option value="">— انتخاب پروژه —</option>
|
||||
{projects.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{p.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<Button variant="green" onClick={devLogin} disabled={devRole === "pm" && !devProjectId}>
|
||||
ورود آزمایشی
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user