feat(frontend): unified warehouse UI, calendar fix, partial receipts, RBAC views, admin panel, dev-mode 3-step
- header: icon box + dynamic subheader '[user] | [role] – [project/همه پروژهها]' on every warehouse page; module context kept in the hamburger (items 1-2) - project manager view: office button + inline logout removed; hamburger contains only «خروج از حساب»; sub-views get «بازگشت به داشبورد» (Header backTo/goBack); «افزایش موجود» primary button on the dashboard (item 8) - daily control: cells render the day-of-month (was the Jalali year), month nav uses real Jalali months, header is pure Jalali «امروز ۸ شهریور ۱۴۰۵», today gets a blue accent (item 3) - arrivals: partial (item/qty) receipt — any valid item is enough to submit, ceiling + allocation-sum validation with inline errors, Received/In-Transit badges; ledger shows «رسیده X از Y» / «در راه Z» + «رسید جزیی» badge (item 4) - api client: multipart uploads no longer send Content-Type json (fixes the invoice/proforma 400 «دادهی ارسالشده نامعتبر است»); undefined query params are dropped (fixes empty admin project filter, item 5) - organization: user-creation card removed, role column read-only for procurement manager, Casdoor sync button (item 6) - admin panel (/admin): Casdoor user sync + grant/revoke «مدیر تدارکات»; Developer Mode is a 3-step admin-gated flow on the login page (item 7)
This commit is contained in:
+25
-3
@@ -35,6 +35,16 @@ import type {
|
||||
const BASE_URL = (import.meta.env.VITE_API_BASE_URL as string | undefined)?.trim() || "/api/v1";
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
// حذف کلیدهای خالی/undefined از پارامترهای کوئری — URLSearchParams مقدار
|
||||
// undefined را به رشتهی "undefined" تبدیل میکند و بکاند ۴۰۰ برمیگرداند.
|
||||
function cleanParams(params: Record<string, unknown>): Record<string, string> {
|
||||
const out: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
if (v !== undefined && v !== null && v !== "") out[k] = String(v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// v2.6: توکن Casdoor (Bearer) یا هدرهای توسعه (X-User-*) — بکاند هر دو را میپذیرد.
|
||||
function authHeaders(init?: RequestInit): Record<string, string> {
|
||||
const { token, user, isDev } = useAuthStore.getState();
|
||||
@@ -192,7 +202,11 @@ async function uploadFile(path: string, file: File): Promise<Attachment> {
|
||||
form.append("file", file);
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(BASE_URL + path, { method: "POST", body: form, headers: authHeaders() });
|
||||
// Content-Type را حذف میکنیم — مرورگر باید multipart boundary را خودش
|
||||
// بگذارد؛ وگرنه FastAPI فرم را نمیخواند و ۴۰۰/۴۲۲ برمیگردد.
|
||||
const headers = authHeaders();
|
||||
delete headers["Content-Type"];
|
||||
res = await fetch(BASE_URL + path, { method: "POST", body: form, headers });
|
||||
} catch {
|
||||
throw new Error("اتصال به سرور برقرار نشد — آیا بکاند در دسترس است؟");
|
||||
}
|
||||
@@ -466,6 +480,7 @@ function adaptLineItem(raw: RawLineItem): Quote["line_items"][number] {
|
||||
accepted_ts_id: raw.accepted_termsheet_id ?? raw.decision?.chosen_item_termsheet_id ?? null,
|
||||
rejected: raw.decision?.status === "rejected",
|
||||
arrived_date: raw.receipt?.received_at ?? null,
|
||||
received_qty: raw.receipt?.received_qty ?? null,
|
||||
projects: (raw.projects || []).map((p) => ({ id: p.id, name: p.name })),
|
||||
};
|
||||
}
|
||||
@@ -1421,6 +1436,13 @@ export const apiV26 = {
|
||||
patchData<UserRow>(`/users/${id}`, { role }),
|
||||
assignProject: async (id: string, projectId: string | null): Promise<UserRow> =>
|
||||
postData<UserRow>(`/users/${id}/project`, { project_id: projectId }),
|
||||
// همگامسازی هویتها از Casdoor (ادمین/مدیر تدارکات) — نقشها دستنخورده میمانند
|
||||
sync: async (): Promise<{ users: UserRow[]; created: number }> => {
|
||||
const res = await request<{ data: UserRow[]; meta: { total: number; created: number } }>("/users/sync", {
|
||||
method: "POST",
|
||||
});
|
||||
return { users: res.data, created: res.meta?.created ?? 0 };
|
||||
},
|
||||
},
|
||||
|
||||
warehouses: {
|
||||
@@ -1444,12 +1466,12 @@ export const apiV26 = {
|
||||
|
||||
inventory: {
|
||||
list: async (params: { project_id?: string; category_id?: string; warehouse_id?: string; material_id?: string; sort?: string } = {}): Promise<InventoryRow[]> => {
|
||||
const qs = new URLSearchParams({ page_size: "100", ...(params as Record<string, string>) }).toString();
|
||||
const qs = new URLSearchParams({ page_size: "100", ...cleanParams(params) }).toString();
|
||||
const data = await getData<Parameters<typeof rawInventory>[0][]>(`/inventory?${qs}`);
|
||||
return data.map(rawInventory);
|
||||
},
|
||||
transactions: async (params: { project_id?: string; warehouse_id?: string; transaction_type?: string; date_from?: string; date_to?: string; sort?: string } = {}): Promise<InventoryTransaction[]> => {
|
||||
const qs = new URLSearchParams({ page_size: "100", ...(params as Record<string, string>) }).toString();
|
||||
const qs = new URLSearchParams({ page_size: "100", ...cleanParams(params) }).toString();
|
||||
return getData<InventoryTransaction[]>(`/inventory/transactions?${qs}`);
|
||||
},
|
||||
createTransaction: async (body: {
|
||||
|
||||
Reference in New Issue
Block a user