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:
@@ -1,13 +1,14 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { Warehouse as WarehouseIcon } from "lucide-react";
|
||||
import { Header, PageLayout } from "../../components/layout/Header";
|
||||
import { useAuthStore } from "../../store/auth";
|
||||
import { PanelMenu } from "../../components/PanelMenu";
|
||||
import { accountMenuItems, useLogout } from "./panelMenu";
|
||||
import { accountMenuItems, inventorySubheader, pmMenuItems, useLogout } from "./panelMenu";
|
||||
import { InventoryTopNav } from "./InventoryTopNav";
|
||||
import { useInventoryData, unitName, whName } from "./useInventoryData";
|
||||
import { faNum } from "../../lib/formatters";
|
||||
import { gregorianToJalali, MONTHS, toFa } from "../../lib/jalali";
|
||||
import { gregorianToJalali, jalaliToGregorian, MONTHS, toFa } from "../../lib/jalali";
|
||||
import type { InventoryTransaction } from "../../lib/types";
|
||||
|
||||
function gregKey(d: Date): string {
|
||||
@@ -83,6 +84,21 @@ export function DailyControlPage() {
|
||||
const [ty, tm] = gregorianToJalali(anchor.getFullYear(), anchor.getMonth() + 1, anchor.getDate());
|
||||
const monthTitle = `${toFa(ty)} ${MONTHS[tm - 1]}`;
|
||||
|
||||
// جابهجایی بین ماههای شمسی (نه ±۳۳ روز) — ماه قبل/بعد با سالشماری درست
|
||||
const shiftMonth = (delta: number) => {
|
||||
let nm = tm + delta;
|
||||
let ny = ty;
|
||||
if (nm < 1) {
|
||||
nm = 12;
|
||||
ny -= 1;
|
||||
} else if (nm > 12) {
|
||||
nm = 1;
|
||||
ny += 1;
|
||||
}
|
||||
const [gy, gm] = jalaliToGregorian(ny, nm, 1);
|
||||
setAnchor(new Date(gy, gm - 1, 1));
|
||||
};
|
||||
|
||||
const dayTx = txByDay.get(sel) || [];
|
||||
const withdrawals = dayTx.filter((t) => t.transaction_type === "WITHDRAWAL");
|
||||
const wGroups = useMemo(() => {
|
||||
@@ -101,14 +117,18 @@ export function DailyControlPage() {
|
||||
|
||||
const isToday = sel === todayKey();
|
||||
const selDate = new Date(Number(sel.split("-")[0]), Number(sel.split("-")[1]) - 1, Number(sel.split("-")[2]));
|
||||
const [sy, sm, sd] = gregorianToJalali(selDate.getFullYear(), selDate.getMonth() + 1, selDate.getDate());
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Header
|
||||
title="کنترل روزانه"
|
||||
sub={user?.project_name || ""}
|
||||
menu={isAdmin ? <PanelMenu title="پنل مدیریت انبار" items={accountMenuItems(logout)} /> : undefined}
|
||||
actions={isAdmin || isPM ? <InventoryTopNav pathname={location.pathname} onLogout={logout} variant={isAdmin ? "admin" : "pm"} /> : undefined}
|
||||
sub={inventorySubheader(user)}
|
||||
backTo="/inventory"
|
||||
backLabel="بازگشت به داشبورد"
|
||||
icon={<WarehouseIcon className="h-4.5 w-4.5" strokeWidth={2} />}
|
||||
menu={<PanelMenu title="پنل مدیریت انبار" items={isAdmin ? accountMenuItems(logout) : pmMenuItems(logout)} />}
|
||||
actions={<InventoryTopNav pathname={location.pathname} variant={isAdmin ? "admin" : "pm"} />}
|
||||
/>
|
||||
<PageLayout className="flex flex-col gap-3">
|
||||
{isAdmin && (
|
||||
@@ -137,9 +157,9 @@ export function DailyControlPage() {
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<div className="text-[14px] font-bold">📅 {monthTitle}</div>
|
||||
<div className="flex gap-1.5">
|
||||
<button className="rounded-lg border border-line px-3 py-1 text-[12px]" onClick={() => setAnchor(new Date(anchor.getTime() - 33 * 86400000))}>▶ ماه قبل</button>
|
||||
<button className="rounded-lg border border-line px-3 py-1 text-[12px]" onClick={() => shiftMonth(-1)}>▶ ماه قبل</button>
|
||||
<button className="rounded-lg border border-line px-3 py-1 text-[12px]" onClick={() => { setAnchor(new Date()); setSel(todayKey()); setActRec(null); }}>امروز</button>
|
||||
<button className="rounded-lg border border-line px-3 py-1 text-[12px]" onClick={() => setAnchor(new Date(anchor.getTime() + 33 * 86400000))}>◀ ماه بعد</button>
|
||||
<button className="rounded-lg border border-line px-3 py-1 text-[12px]" onClick={() => shiftMonth(1)}>◀ ماه بعد</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-7 gap-1">
|
||||
@@ -147,14 +167,15 @@ export function DailyControlPage() {
|
||||
<div key={d} className="py-1 text-center text-[11px] font-bold text-muted">{d}</div>
|
||||
))}
|
||||
{cells.map((c, i) => {
|
||||
const [jd] = gregorianToJalali(c.d.getFullYear(), c.d.getMonth() + 1, c.d.getDate());
|
||||
// gregorianToJalali → [سال، ماه، روز] — روزِ ماه در خانهی تقویم
|
||||
const [, , jd] = gregorianToJalali(c.d.getFullYear(), c.d.getMonth() + 1, c.d.getDate());
|
||||
const k = gregKey(c.d);
|
||||
return (
|
||||
<button
|
||||
key={i}
|
||||
type="button"
|
||||
onClick={() => { setSel(k); setActRec(null); }}
|
||||
className={`relative rounded-lg py-2 text-[12px] ${!c.inMonth ? "text-muted/40" : ""} ${c.isToday ? "bg-accent/15 font-bold text-accent" : ""} ${sel === k ? "ring-2 ring-accent" : "hover:bg-accent/5"}`}
|
||||
className={`relative rounded-lg py-2 text-[12px] ${!c.inMonth ? "text-muted/40" : ""} ${c.isToday ? "bg-accent font-bold text-white" : ""} ${sel === k ? "ring-2 ring-accent" : "hover:bg-accent/5"}`}
|
||||
>
|
||||
{toFa(jd)}
|
||||
{c.n > 0 && (
|
||||
@@ -171,7 +192,7 @@ export function DailyControlPage() {
|
||||
{/* برداشتهای روز */}
|
||||
<div className="rounded-xl border border-line bg-panel p-4">
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<h2 className="text-[14px] font-bold">🕘 کنترل روزانه — {isToday ? "امروز" : ""} {faNum(selDate.getDate())} {MONTHS[selDate.getMonth()]} {toFa(selDate.getFullYear())}</h2>
|
||||
<h2 className="text-[14px] font-bold">🕘 کنترل روزانه — {isToday ? "امروز " : ""}{toFa(sd)} {MONTHS[sm - 1]} {toFa(sy)}</h2>
|
||||
<span className="rounded-lg bg-blue/10 px-2 py-1 text-[11.5px] font-bold text-blue">▼ برداشتها: {faNum(wGroups.length)} قلم</span>
|
||||
</div>
|
||||
<p className="mb-3 text-[11.5px] leading-5 text-muted">
|
||||
|
||||
Reference in New Issue
Block a user