- new tables user_panels (panel: procurement/inventory/pm + level full/limited)
and user_project_members (M:N membership, separate from pm responsibility)
- UserContext carries panels + member_project_ids; /users/me and the users
list include them
- admin endpoints: PUT /users/{id}/panels, PUT /users/{id}/panel-projects
- inventory scope: panel inventory 'full' -> manager scope (all projects, NO
stock change); 'limited' -> supervisor scope (own memberships, stock change
allowed); legacy roles kept as migration fallback; admin untouched
- procurement writes: _procurement_writer is now panel-aware (admin, legacy
procurement_manager, or procurement 'full') across all 18 write routers;
quote CREATE additionally allows 'limited' users restricted to their member
projects (validated server-side)
- frontend: panel-aware HomeRoute (inventory -> warehouse panel, procurement
limited -> minimal RequesterHome), wizard project options restricted for
limited users, change-inventory button/route gated to inventory 'limited',
organization panel gains the panels & access-levels management section
95 lines
3.8 KiB
TypeScript
95 lines
3.8 KiB
TypeScript
// ============================================================
|
|
// Auth store — Casdoor session (v2.6, درونصفحهای / password grant)
|
|
// Dev mode: no token — identity sent via X-User-Role/X-Project-Id
|
|
// headers (backend dev fallback) so the app works before Casdoor
|
|
// is fully seeded.
|
|
// ============================================================
|
|
|
|
import { create } from "zustand";
|
|
import { persist } from "zustand/middleware";
|
|
|
|
export interface AuthUser {
|
|
id: string;
|
|
casdoor_id: string;
|
|
username?: string;
|
|
first_name: string;
|
|
last_name: string;
|
|
phone: string | null;
|
|
role: "pm" | "procurement_manager" | "admin" | "supervisor" | null;
|
|
role_label: string | null;
|
|
// لایه پنلها (جایگزین نقشها)
|
|
panels?: Record<string, string>;
|
|
member_project_ids?: string[];
|
|
project_id: string | null; // پروژهی فعال (PM) — با سوییچر عوض میشود
|
|
project_name: string | null;
|
|
projects?: { id: string; name: string }[]; // همهی پروژههای تخصیصیافته (1:N)
|
|
display_name: string;
|
|
}
|
|
|
|
interface AuthStore {
|
|
token: string | null;
|
|
user: AuthUser | null;
|
|
isDev: boolean;
|
|
setToken: (token: string | null) => void;
|
|
setUser: (user: AuthUser | null) => void;
|
|
setDevUser: (role: "pm" | "admin" | "procurement_manager", projectId: string | null, projectName: string | null) => void;
|
|
// پروژههای تخصیصیافته را مینشاند و پروژهی فعال را اعتبارسنجی میکند:
|
|
// اگر پروژهی ذخیرهشده (localStorage) باطل شده باشد، به اولین پروژه برمیگردد.
|
|
setProjects: (projects: { id: string; name: string }[]) => void;
|
|
// جابهجایی پروژهی فعال بدون هیچ تغییری در نشست/توکن (بدون احراز هویت مجدد)
|
|
setActiveProject: (projectId: string) => void;
|
|
logout: () => void;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthStore>()(
|
|
persist(
|
|
(set) => ({
|
|
token: null,
|
|
user: null,
|
|
isDev: false,
|
|
setToken: (token) => set({ token, isDev: false }),
|
|
setUser: (user) => set({ user, isDev: false }),
|
|
setDevUser: (role, projectId, projectName) =>
|
|
set({
|
|
token: null,
|
|
isDev: true,
|
|
user: {
|
|
id: "dev-user",
|
|
casdoor_id: "dev",
|
|
first_name: "کاربر",
|
|
last_name: "آزمایشی",
|
|
phone: null,
|
|
role,
|
|
role_label: role === "pm" ? "مدیر پروژه (آزمایشی)" : role === "procurement_manager" ? "مدیر تدارکات (آزمایشی)" : "ادمین (آزمایشی)",
|
|
project_id: projectId,
|
|
project_name: projectName,
|
|
projects: projectId ? [{ id: projectId, name: projectName || "" }] : [],
|
|
display_name: projectId ? `کاربر آزمایشی — ${projectName || ""}` : "کاربر آزمایشی",
|
|
},
|
|
}),
|
|
setProjects: (projects) =>
|
|
set((s) => {
|
|
if (!s.user) return {};
|
|
const active = projects.find((p) => p.id === s.user!.project_id) ?? projects[0] ?? null;
|
|
return {
|
|
user: {
|
|
...s.user,
|
|
projects,
|
|
project_id: active?.id ?? null,
|
|
project_name: active?.name ?? null,
|
|
},
|
|
};
|
|
}),
|
|
setActiveProject: (projectId) =>
|
|
set((s) => {
|
|
if (!s.user) return {};
|
|
const target = (s.user.projects ?? []).find((p) => p.id === projectId);
|
|
if (!target) return {};
|
|
return { user: { ...s.user, project_id: target.id, project_name: target.name } };
|
|
}),
|
|
logout: () => set({ token: null, user: null, isDev: false }),
|
|
}),
|
|
{ name: "procurement-auth-v1" },
|
|
),
|
|
);
|