Merge pm (panel-based access, supervisor role) onto the restored RBAC base

Reverting 253c922 first made this merge small — only two conflicts:
- MobileTabBar: supervisor null-check combined with v7's /review hiding
- ArrivalsPage: pm's PM+supervisor scoping combined with v8's 3-way fetch
  (transfers) and its transfer-receive helpers, plus pm's canAccess gate
This commit is contained in:
MMD
2026-10-01 15:36:57 +03:30
41 changed files with 743 additions and 146 deletions
+5 -3
View File
@@ -14,6 +14,8 @@ export interface Step4Props {
onAssign: (tempId: string, proj: string) => void;
onUnassign: (tempId: string, proj: string) => void;
onAddProject: (name: string) => void;
/** سطح limited تدارکات: افزودن پروژه جدید ممنوع */
canAddProject?: boolean;
}
const ALL_KEY = "__all__";
@@ -111,7 +113,7 @@ function DroppableProject({
}
// مرحله ۴ — تخصیص پروژه‌ها با درگ‌اندروپ (یا کلیک-قلم بعد کلیک-پروژه)
export function Step4Projects({ items, projects, assignment, onAssign, onUnassign, onAddProject }: Step4Props) {
export function Step4Projects({ items, projects, assignment, onAssign, onUnassign, onAddProject, canAddProject = true }: Step4Props) {
const [picked, setPicked] = useState<string | null>(null);
const [newProj, setNewProj] = useState("");
@@ -213,8 +215,8 @@ export function Step4Projects({ items, projects, assignment, onAssign, onUnassig
{!projects.length && (
<div className="dd-empty">هنوز پروژه‌ای تعریف نشده است — از پایین یک پروژه اضافه کنید.</div>
)}
{/* افزودن پروژه به‌صورت درجا — زیر لیست پروژه‌ها */}
<div className="mt-3 rounded-md border border-dashed border-line p-2">
{/* افزودن پروژه به‌صورت درجا — زیر لیست پروژه‌ها (سطح limited: ممنوع) */}
<div className="mt-3 rounded-md border border-dashed border-line p-2" hidden={!canAddProject}>
<div className="mb-1.5 text-[11.5px] font-semibold text-muted">افزودن پروژه‌ی جدید:</div>
<div className="flex items-center gap-2">
<input
+19 -2
View File
@@ -36,6 +36,7 @@ import {
DialogHeader,
DialogTitle,
} from "../../components/ui/dialog";
import { useAuthStore } from "../../store/auth";
const TOTAL_STEPS = 5;
@@ -149,6 +150,17 @@ export function WizardPage({ isStandalone = false }: { isStandalone?: boolean })
const suppliers = useSuppliers();
const materials = useMaterials();
const projects = useProjects();
// سطح limited تدارکات: استعلام فقط برای پروژه‌های عضویت — بدون افزودن پروژه جدید
const user = useAuthStore((s) => s.user);
const isLimitedProcurement =
user?.panels?.procurement === "limited" && user?.role !== "admin";
const memberProjectNames = useMemo(
() =>
projects
.filter((p) => (user?.member_project_ids ?? []).includes(p.id))
.map((p) => p.name),
[projects, user],
);
const topics = useAppStore((s) => s.topics);
const createQuote = useCreateQuote();
const addSupplier = useAddSupplier();
@@ -504,13 +516,17 @@ export function WizardPage({ isStandalone = false }: { isStandalone?: boolean })
// ---------- پروژه‌ها ----------
const projectNames = useMemo(() => {
const names = new Set<string>(projects.map((p) => p.name));
const names = new Set<string>(
isLimitedProcurement
? memberProjectNames
: projects.map((p) => p.name),
);
quotes.forEach((q) => {
if (q.project) names.add(q.project);
(q.projects || []).forEach((n) => names.add(n));
});
return [...names];
}, [projects, quotes]);
}, [projects, quotes, isLimitedProcurement, memberProjectNames]);
const assign = (tempId: string, proj: string) => {
setAssignment((a) => {
@@ -950,6 +966,7 @@ export function WizardPage({ isStandalone = false }: { isStandalone?: boolean })
assignment={assignment}
onAssign={assign}
onUnassign={unassign}
canAddProject={!isLimitedProcurement}
onAddProject={(name) => {
void addProject.mutateAsync({ id: nextId("p", projects), name });
}}