feat(wholesale): implement Bulk Order Matrix with role-based wholesale pricing display [TASK-104]

This commit is contained in:
parsa aghaei 2026-07-26 14:36:53 +03:30
parent 9122035784
commit 3ab4541933
3 changed files with 66 additions and 34 deletions

View File

@ -46,7 +46,7 @@
"title": "پیاده‌سازی پنل و جدول سفارش حجمی (Bulk Order Matrix) برای خریداران عمده تاییدشده",
"priority": "HIGH",
"assigned_role": "05_dev_frontend",
"status": "pending",
"status": "completed",
"max_file_count": 3,
"estimated_minutes": 15,
"dependency_task_ids": ["TASK-103"],

View File

@ -5,8 +5,8 @@
"checkpoint": {
"stage": "DEVELOPMENT",
"active_agent": "05_dev_frontend",
"current_ticket_id": "TASK-104",
"sub_step_index": 4,
"current_ticket_id": "TASK-105",
"sub_step_index": 5,
"total_sub_steps": 5
},
"execution_guards": {
@ -16,11 +16,11 @@
"token_usage_total": 0
},
"git_state": {
"active_branch": "feature/TASK-103",
"active_branch": "feature/TASK-104",
"last_healthy_commit": "HEAD"
},
"context_buffer": {
"last_agent_summary": "TASK-103 (Wholesale Buyer Verification & License Form) completed and verified."
"last_agent_summary": "TASK-104 (Bulk Order Matrix and Wholesale Pricing Tiers) completed and verified."
},
"last_updated": "2026-07-26T14:35:00Z"
"last_updated": "2026-07-26T14:36:00Z"
}

View File

@ -2,6 +2,7 @@
import React, { useState, useEffect } from "react";
import { Product } from "../lib/data/products";
import { useCartStore } from "../lib/store/cartStore";
import { useUserStore } from "../lib/store/userStore";
import { productService } from "../lib/services/productService";
import { motion, AnimatePresence } from "motion/react";
import {
@ -12,9 +13,12 @@ import {
FileText,
Package,
CheckCircle2,
Lock,
Percent,
X
} from "lucide-react";
import SafeImage from "./SafeImage";
import { toPersian } from "../lib/utils";
interface QuickOrderItem {
product: Product;
@ -26,8 +30,11 @@ export default function B2BPortal({ onClose }: { onClose: () => void }) {
const [quantities, setQuantities] = useState<Record<string, number>>({});
const [searchQuery, setSearchQuery] = useState("");
const { addItem } = useCartStore();
const { role } = useUserStore();
const [showSuccess, setShowSuccess] = useState(false);
const isWholesaleVerified = (role as string) === "User_Wholesale" || (role as string) === "ADMIN" || (role as string) === "User_Partner";
useEffect(() => {
productService.getProducts({ limit: 999 }).then(res => setProducts(res.data));
}, []);
@ -57,6 +64,16 @@ export default function B2BPortal({ onClose }: { onClose: () => void }) {
};
const totalItems = Object.values(quantities).reduce((acc: number, val) => acc + (val as any), 0);
const totalWholesalePrice = Object.entries(quantities).reduce((sum, [id, qty]) => {
const numQty = qty as number;
const prod = products.find(p => p.id === id);
if (prod && numQty > 0) {
const basePrice = prod.priceValue || 0;
const wholesalePrice = basePrice * 0.7; // 30% discount for wholesale
return sum + (wholesalePrice * numQty);
}
return sum;
}, 0);
return (
<motion.div
@ -135,12 +152,16 @@ export default function B2BPortal({ onClose }: { onClose: () => void }) {
<tr>
<th className="px-6 py-4 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest">تصویر</th>
<th className="px-6 py-4 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest">نام و کد کالا</th>
<th className="px-6 py-4 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest">وضعیت موجودی</th>
<th className="px-6 py-4 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest">قیمت تکفروشی</th>
<th className="px-6 py-4 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest">قیمت عمده (همکار)</th>
<th className="px-6 py-4 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest">تعداد سفارش</th>
</tr>
</thead>
<tbody className="divide-y divide-medical-gray-100">
{filteredProducts.map(product => (
{filteredProducts.map(product => {
const basePrice = product.priceValue || 0;
const wholesalePrice = basePrice * 0.7; // 30% discount
return (
<tr key={product.id} className="hover:bg-medical-gray-50/50 transition-colors">
<td className="px-6 py-4">
<SafeImage src={product.image} className="w-12 h-12" imgClassName="object-contain" alt={product.name} />
@ -149,11 +170,21 @@ export default function B2BPortal({ onClose }: { onClose: () => void }) {
<div className="font-black text-medical-gray-900">{product.name}</div>
<div className="text-[10px] font-vazir text-medical-gray-400 mt-1">شناسه کالا: {product.artNo}</div>
</td>
<td className="px-6 py-4 text-xs font-bold text-medical-gray-500">
{toPersian(basePrice.toLocaleString())} تومان
</td>
<td className="px-6 py-4">
<span className="inline-flex items-center gap-1.5 px-3 py-1 bg-green-50 text-green-600 rounded-full text-[10px] font-bold">
<CheckCircle2 className="w-3 h-3" />
آماده ارسال
{isWholesaleVerified ? (
<span className="inline-flex items-center gap-1.5 px-3 py-1 bg-amber-50 text-amber-700 rounded-full text-xs font-black">
<Percent className="w-3 h-3 text-amber-600" />
{toPersian(wholesalePrice.toLocaleString())} تومان
</span>
) : (
<span className="inline-flex items-center gap-1.5 px-3 py-1 bg-gray-100 text-gray-500 rounded-full text-[10px] font-bold">
<Lock className="w-3 h-3 text-gray-400" />
نیازمند تایید ادمین
</span>
)}
</td>
<td className="px-6 py-4">
<input
@ -166,7 +197,8 @@ export default function B2BPortal({ onClose }: { onClose: () => void }) {
/>
</td>
</tr>
))}
);
})}
</tbody>
</table>
</div>