-
{label}
-
{children}
+
);
}
-// کارت ساده و یکدست یک پیشنهاد در دفتر
+// کارت پیشنهاد خرید — بهینهشده برای کاربری سرپا با موبایل
export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit }: QuoteCardProps) {
const sup = supplierById(store.suppliers, q.supplier_id);
const expired = isExpired(q);
@@ -46,24 +46,25 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
const unitWarns = unitWarning(store, q);
const summary = itemsSummary(store, q);
- // دکمه یا وضعیت پذیرش
+ // ستون / دکمه پذیرش
let acceptedCell: React.ReactNode;
if (q.status === "offered") {
acceptedCell = (
-
);
} else if (q.accepted_date) {
- acceptedCell =
{fmtDateTime(q.accepted_date)};
+ acceptedCell =
{fmtDateTime(q.accepted_date)};
} else {
acceptedCell =
—;
}
+ // وضعیت رسید کالا
const activeItems = (q.line_items || []).filter((li) => !li.rejected);
const receivedSum = activeItems.reduce((s, li) => s + (li.received_qty || 0), 0);
const orderedSum = activeItems.reduce((s, li) => s + (li.quantity || 0), 0);
@@ -73,23 +74,24 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
const anyReceived = receivedSum > 0;
const fullyDelivered = activeItems.length > 0 && fulfilledItems === activeItems.length && anyReceived;
+ // نشان پیشرفت تکمیل
let fulfillmentBadge: React.ReactNode = null;
if (q.status === "accepted" || q.status === "arrived") {
if (fullyDelivered || q.status === "arrived") {
fulfillmentBadge = (
-
- ✓ تکمیل شد
+
+ ✓ تکمیل
);
} else if (anyReceived) {
fulfillmentBadge = (
-
- رسید: {fmtNum(fulfilledItems)} از {fmtNum(activeItems.length)} قلم
+
+ رسید: {fmtNum(fulfilledItems)}/{fmtNum(activeItems.length)}
);
} else {
fulfillmentBadge = (
-
+
⏳ در راه
);
@@ -103,12 +105,6 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
if (Number.isNaN(t)) return acc;
return !acc || t > acc.getTime() ? new Date(li.arrived_date) : acc;
}, null);
- const receiptTimeLine =
- lastReceiptAt != null ? (
-
- آخرین رسید: {fmtDateTime(lastReceiptAt.toISOString())}
-
- ) : null;
if (q.status === "accepted") {
if (q.arrived_date) {
@@ -116,12 +112,11 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
} else if (receivedSum > 0) {
arrivedCell = (
- رسیده {fmtNum(receivedSum)} از {fmtNum(orderedSum)} — در راه {fmtNum(Math.max(0, orderedSum - receivedSum))}
- {receiptTimeLine}
+ {fmtNum(receivedSum)} از {fmtNum(orderedSum)}
);
} else {
- arrivedCell = در انتظار PM;
+ arrivedCell = در انتظار انبار;
}
} else if (q.status === "arrived") {
arrivedCell = (
@@ -136,47 +131,42 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
return (
- {/* هدر یکپارچه و تمیز بدون کادر تیره */}
-
-
+ {/* سربرگ کارت */}
+
+
{sup.name.slice(0, 1)}
-
+
{sup.name}
- {sup.location &&
{sup.location}
}
+ {sup.location &&
{sup.location}
}
-
- {/* دکمههای عملیاتی راست به چپ */}
-
+
{fulfillmentBadge ?? (
-
{STATUS_LABEL[q.status]}
+
+ {STATUS_LABEL[q.status]}
+
)}
-
- {/* دکمه ویرایش مینیمال */}
-
- {/* فلش آکاردئون */}
- {/* اطلاعات کلیدی کارت */}
-
+ {/* اطلاعات کلیدی (گرید ۲ ستونه موبایل / ۴ ستونه دسکتاپ) */}
+
{summary.count > 1 ? (
-
{fmtNum(summary.count)} قلم
-
- {store.quotes
- .find((x) => x.id === q.id)
- ?.line_items?.filter((li) => !li.rejected)
- .map((li) => {
- const m = materialById(store.materials, li.material_id);
- return (
-
-
- {materialLabel(m)}
-
- {fmtNum(li.quantity)} {li.unit}
-
-
- );
- })}
+
{fmtNum(summary.count)} قلم کالا
+
+ {store.quotes.find((x) => x.id === q.id)?.line_items?.filter((li) => !li.rejected).slice(0, 3).map((li) => {
+ const m = materialById(store.materials, li.material_id);
+ return (
+
+ • {materialLabel(m)} ({fmtNum(li.quantity)} {li.unit})
+
+ );
+ })}
+ {(q.line_items?.length || 0) > 3 && (
+ و {(q.line_items?.length || 0) - 3} قلم دیگر...
+ )}
) : (
- summary.text
+
{summary.text}
)}
{unitWarns.map((w) => (
@@ -227,37 +213,34 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
-
+
- {q.projects && q.projects.length > 0 ? (
-
- {q.projects.map((p, i) => (
- {p}
- ))}
-
- ) : (
- projectCell(q)
- )}
+
+ {q.projects && q.projects.length > 0 ? q.projects.join("، ") : projectCell(q)}
+
-
+
{pc ? (
-
- {pc.num} ({pc.words} تومان)
-
+
+
+ {pc.num} تومان
+
+
({pc.words})
+
) : (
"—"
)}
-
+
{ts.title !== "—" ? (
-
+
{ts.title}
- {ts.sub &&
{ts.sub}
}
+ {ts.sub &&
{ts.sub}
}
) : (
—
@@ -265,29 +248,35 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
- {/* پاورقی تمیز و روشن */}
-
-
-
- پیشنهاد: {fmtDateTime(q.quote_date)}
+ {/* تاریخها و اکشنها (۲ ستونه در موبایل، نواری در دسکتاپ) */}
+
+
+
+ تاریخ:
+ {fmtDate(q.quote_date)}
-
-
- معتبر تا:{" "}
+
+
+
+ اعتبار:
{fmtDate(q.expiry_date)}
- {expired && }
+ {expired && }
+
- پذیرش: {acceptedCell}
+ پذیرش:
+ {acceptedCell}
-
- رسید: {arrivedCell}
+
+
+ رسید:
+ {arrivedCell}
- {/* جریان (expand) */}
+ {/* جزئیات تکمیلی بازشونده */}
{expanded && }
);
diff --git a/frontend/src/pages/Inventory/InventoryPage.tsx b/frontend/src/pages/Inventory/InventoryPage.tsx
index a89413c..c65ad3b 100644
--- a/frontend/src/pages/Inventory/InventoryPage.tsx
+++ b/frontend/src/pages/Inventory/InventoryPage.tsx
@@ -1,6 +1,6 @@
import { useMemo, useState } from "react";
import { Link, useLocation, useSearchParams } from "react-router-dom";
-import { Plus, Warehouse as WarehouseIcon, ArrowUpDown, ArrowUp, ArrowDown, X } from "lucide-react";
+import { Plus, Warehouse as WarehouseIcon, SlidersHorizontal, ArrowUpDown, X } from "lucide-react";
import { Header, PageLayout } from "../../components/layout/Header";
import { EmptyState } from "../../components/EmptyState";
import { Badge } from "../../components/ui/badge";
@@ -13,6 +13,7 @@ import { useInventoryData, brandName, catName, materialName, unitName, whName }
import type { InventoryRow, InventoryTransaction, TransactionType } from "../../lib/types";
import { faNum } from "../../lib/formatters";
import { isoToJalaliStr } from "../../lib/jalali";
+import { cn } from "../../lib/utils";
// رنگبندی معنایی: سبز زمردی = ورود/افزایش، رز = خروج/کاهش، آبی = خنثی/جابهجایی
const TX_META: Record = {
@@ -48,7 +49,6 @@ function specsHash(specs: Record | null | undefined): string {
return keys.map((k) => `${k}=${s[k]}`).join("|");
}
-// داشبورد انبار — پورت کامل نسخهی ارسالی (گروهبندی + خط زمانی + فیلترها)
export function InventoryPage() {
const location = useLocation();
const user = useAuthStore((s) => s.user);
@@ -64,6 +64,7 @@ export function InventoryPage() {
project: "",
});
const [sort, setSort] = useState<"asc" | "desc" | "">("");
+ const [showMobileFilters, setShowMobileFilters] = useState(false);
const [expandedKey, setExpandedKey] = useState(null);
const [selTx, setSelTx] = useState(null);
const [expBrand, setExpBrand] = useState(null);
@@ -133,7 +134,7 @@ export function InventoryPage() {
.slice()
.sort((a, b) => new Date(a.transaction_date).getTime() - new Date(b.transaction_date).getTime());
- const hasFilter = !!(filters.category || filters.warehouse || filters.material || (isAdmin && filters.project));
+ const hasFilter = !!(filters.category || filters.warehouse || (isAdmin && filters.project));
if (loading) return ;
if (!user || (!isAdmin && !isPM)) {
@@ -148,149 +149,169 @@ export function InventoryPage() {
}
return (
-
+
}
menu={
}
actions={
- <>
-
+
+ {/* تبهای تکراری در موبایل مخفی و فقط در دسکتاپ نمایش داده میشوند */}
+
+
+
{isPM && (
-
}
/>
-
- {/* نوار فیلترها و ابزارها */}
-
-
-
-