feat(admin): add WholesaleApplications page for B2B clinic license review and role approval
This commit is contained in:
parent
a999627d28
commit
510c2f90de
@ -169,7 +169,7 @@
|
||||
"architectural_layer": "presentation_ui",
|
||||
"assigned_role": "06_dev_frontend",
|
||||
"priority": "HIGH",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"max_files_allowed": 4,
|
||||
"estimated_minutes": 25,
|
||||
"dependency_task_ids": ["EPIC-03-TASK-01"],
|
||||
@ -178,8 +178,8 @@
|
||||
"دکمه ارتقای نقش به User_Wholesale و ارسال هشدار وضعیت"
|
||||
],
|
||||
"sub_steps": [
|
||||
{ "index": 1, "name": "create_wholesale_applications_page", "description": "Create WholesaleApplications.tsx page", "status": "pending" },
|
||||
{ "index": 2, "name": "wire_approval_actions", "description": "Connect approve/reject actions to backend API", "status": "pending" }
|
||||
{ "index": 1, "name": "create_wholesale_applications_page", "description": "Create WholesaleApplications.tsx page", "status": "done" },
|
||||
{ "index": 2, "name": "wire_approval_actions", "description": "Connect approve/reject actions to backend API", "status": "done" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -226,10 +226,10 @@
|
||||
],
|
||||
"metadata": {
|
||||
"total": 11,
|
||||
"completed": 8,
|
||||
"completed": 9,
|
||||
"in_progress": 1,
|
||||
"pending": 2,
|
||||
"generated_at": "2026-07-26T20:00:00Z",
|
||||
"pending": 1,
|
||||
"generated_at": "2026-07-26T20:05:00Z",
|
||||
"decomposition_pass": 4
|
||||
}
|
||||
}
|
||||
@ -6,13 +6,13 @@
|
||||
"project_intent": "REVIEW_AND_PLAN",
|
||||
"status": "IN_PROGRESS",
|
||||
"checkpoint": {
|
||||
"active_agent": "06_dev_frontend",
|
||||
"current_ticket_id": "EPIC-03-TASK-02",
|
||||
"active_agent": "12_seo_content",
|
||||
"current_ticket_id": "EPIC-04-TASK-01",
|
||||
"sub_step": {
|
||||
"index": 1,
|
||||
"total": 2,
|
||||
"name": "create_wholesale_applications_page",
|
||||
"description": "Create WholesaleApplications.tsx page in Admin Panel for reviewing B2B clinic licenses and role approvals"
|
||||
"name": "implement_product_jsonld",
|
||||
"description": "Implement Product & MedicalWebPage JSON-LD schema markup for SEO"
|
||||
}
|
||||
},
|
||||
"review_phase": {
|
||||
@ -29,17 +29,17 @@
|
||||
"findings_dir": ".ai_agency/specs/reviews/"
|
||||
},
|
||||
"resume_context": {
|
||||
"last_completed_action": "EPIC-03-TASK-01 completed: CMS.tsx page built in React Admin Panel with dynamic CRUD for Hero Banners and Vet Testimonials.",
|
||||
"next_action": "Execute EPIC-03-TASK-02: Build B2B Wholesale Applications & Role Approval page in Admin Panel.",
|
||||
"last_completed_action": "EPIC-03-TASK-02 completed: WholesaleApplications.tsx page created in React Admin Panel with online role approval.",
|
||||
"next_action": "Execute EPIC-04-TASK-01: Full On-Page & Schema.org JSON-LD SEO implementation in Next.js.",
|
||||
"files_modified_this_session": [
|
||||
"frontend/admin-panel/src/pages/CMS.tsx",
|
||||
"frontend/admin-panel/src/pages/WholesaleApplications.tsx",
|
||||
"frontend/admin-panel/src/App.tsx",
|
||||
"frontend/admin-panel/src/components/Sidebar.tsx",
|
||||
".ai_agency/memory/backlog.json",
|
||||
".ai_agency/memory/state.json"
|
||||
],
|
||||
"files_pending": [],
|
||||
"notes": "Verified Vite production build cleanly (1.65s). Moving to EPIC-03-TASK-02."
|
||||
"notes": "Verified Vite production build cleanly (2.74s). Moving to EPIC-04-TASK-01."
|
||||
},
|
||||
"tech_stack": {
|
||||
"language": "TypeScript",
|
||||
@ -58,11 +58,11 @@
|
||||
"max_retry_attempts": 3,
|
||||
"blocking_reason": null,
|
||||
"agent_visit_counts": {
|
||||
"06_dev_frontend": 10
|
||||
"12_seo_content": 4
|
||||
}
|
||||
},
|
||||
"agent_call_log": [
|
||||
{ "agent": "06_dev_frontend", "result": "EPIC-03-TASK-01_completed", "timestamp": "2026-07-26T20:00:00Z" }
|
||||
{ "agent": "06_dev_frontend", "result": "EPIC-03-TASK-02_completed", "timestamp": "2026-07-26T20:05:00Z" }
|
||||
],
|
||||
"last_updated": "2026-07-26T20:00:00Z"
|
||||
"last_updated": "2026-07-26T20:05:00Z"
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import Pets from './pages/Pets';
|
||||
import UITexts from './pages/UITexts';
|
||||
import Media from './pages/Media';
|
||||
import CMS from './pages/CMS';
|
||||
import WholesaleApplications from './pages/WholesaleApplications';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@ -40,6 +41,7 @@ function App() {
|
||||
<Route path="ui-texts" element={<UITexts />} />
|
||||
<Route path="media" element={<Media />} />
|
||||
<Route path="cms" element={<CMS />} />
|
||||
<Route path="wholesale" element={<WholesaleApplications />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@ -25,6 +25,7 @@ const menuGroups = [
|
||||
items: [
|
||||
{ icon: Users, label: 'کاربران', path: '/users' },
|
||||
{ icon: Heart, label: 'حیوانات (Pets)', path: '/pets' },
|
||||
{ icon: Package, label: 'درخواستهای B2B', path: '/wholesale' },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
111
frontend/admin-panel/src/pages/WholesaleApplications.tsx
Normal file
111
frontend/admin-panel/src/pages/WholesaleApplications.tsx
Normal file
@ -0,0 +1,111 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import api from '../services/api';
|
||||
|
||||
export default function WholesaleApplications() {
|
||||
const [requests, setRequests] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequests();
|
||||
}, []);
|
||||
|
||||
const fetchRequests = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await api.get('/wholesale/requests');
|
||||
setRequests(res.data || []);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch wholesale requests:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleApprove = async (userId: string) => {
|
||||
if (!confirm('آیا از تایید حساب همکار و اعطای قیمتهای عمده-۳۰٪ مطمئن هستید؟')) return;
|
||||
try {
|
||||
await api.put(`/wholesale/approve/${userId}`);
|
||||
alert('کاربر با موفقیت به نقش خریدار عمده (User_Wholesale) ارتقا یافت.');
|
||||
fetchRequests();
|
||||
} catch (err) {
|
||||
alert('خطا در ارتقای نقش کاربر');
|
||||
}
|
||||
};
|
||||
|
||||
const handleReject = async (userId: string) => {
|
||||
if (!confirm('آیا از رد درخواست همکاری مطمئن هستید؟')) return;
|
||||
try {
|
||||
await api.put(`/wholesale/reject/${userId}`);
|
||||
alert('درخواست رد شد.');
|
||||
fetchRequests();
|
||||
} catch (err) {
|
||||
alert('خطا در رد درخواست');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 font-vazir" dir="rtl">
|
||||
<h1 className="text-2xl font-bold mb-2 text-gray-800">بررسی درخواستهای ثبتنام همکاران B2B (کلینیکها و داروخانهها)</h1>
|
||||
<p className="text-sm text-gray-500 mb-6">استعلام شماره پروانه نظام دامپزشکی و اعطای دسترسی خرید عمده با تخفیف ۳۰٪ کاتالوگ Canina</p>
|
||||
|
||||
{loading ? (
|
||||
<div className="text-center py-12 text-gray-500 font-bold">در حال بارگذاری درخواستها...</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-2xl border border-gray-200 overflow-hidden shadow-sm">
|
||||
<table className="w-full text-right">
|
||||
<thead className="bg-gray-50 border-b border-gray-200 text-xs font-bold text-gray-500">
|
||||
<tr>
|
||||
<th className="px-6 py-4">کاربر / ایمیل</th>
|
||||
<th className="px-6 py-4">نقش فعلی</th>
|
||||
<th className="px-6 py-4">تاریخ ثبتنام</th>
|
||||
<th className="px-6 py-4 text-center">عملیات تایید همکار</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100 text-sm">
|
||||
{requests.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={4} className="text-center py-8 text-gray-400 font-medium">هیچ درخواستی در انتظار بررسی یافت نشد.</td>
|
||||
</tr>
|
||||
) : (
|
||||
requests.map((u: any) => (
|
||||
<tr key={u.id} className="hover:bg-gray-50/60 transition-colors">
|
||||
<td className="px-6 py-4">
|
||||
<div className="font-bold text-gray-900">{u.fullName || u.email || 'کاربر بدون نام'}</div>
|
||||
<div className="text-xs text-gray-400 dir-ltr text-right">{u.email || u.phoneNumber}</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className={`inline-flex px-3 py-1 rounded-full text-xs font-bold ${
|
||||
u.role === 'User_Wholesale' ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700'
|
||||
}`}>
|
||||
{u.role === 'User_Wholesale' ? 'خریدار عمده تاییدشده' : 'در انتظار بررسی B2B'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-xs text-gray-500 font-mono">
|
||||
{new Date(u.createdAt).toLocaleDateString('fa-IR')}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-center space-x-2 space-x-reverse">
|
||||
{u.role !== 'User_Wholesale' && (
|
||||
<button
|
||||
onClick={() => handleApprove(u.id)}
|
||||
className="bg-green-600 hover:bg-green-700 text-white font-bold text-xs px-4 py-2 rounded-xl transition-all shadow-sm"
|
||||
>
|
||||
تایید و اعطای لایسنس B2B
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleReject(u.id)}
|
||||
className="bg-gray-100 hover:bg-red-50 hover:text-red-600 text-gray-600 font-bold text-xs px-4 py-2 rounded-xl transition-all"
|
||||
>
|
||||
رد درخواست
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user