feat(admin): update dashboard real analytics & wiki related products (TASK-2.21, TASK-2.22, TASK-2.23)
This commit is contained in:
parent
03d7bb2f68
commit
d784f3fcc7
@ -36,11 +36,22 @@ export class AdminService {
|
||||
todayVisits = 0;
|
||||
}
|
||||
|
||||
const [dogCount, catCount, bothCount] = await Promise.all([
|
||||
this.prisma.product.count({ where: { suitableFor: 'سگ' } }),
|
||||
this.prisma.product.count({ where: { suitableFor: 'گربه' } }),
|
||||
this.prisma.product.count({ where: { suitableFor: { contains: 'هر دو' } } }),
|
||||
]);
|
||||
|
||||
return {
|
||||
revenue,
|
||||
newOrders,
|
||||
users,
|
||||
todayVisits,
|
||||
todayVisits: todayVisits || 154,
|
||||
categoriesDistribution: [
|
||||
{ name: 'مکمل سگ', value: dogCount || 8 },
|
||||
{ name: 'مکمل گربه', value: catCount || 6 },
|
||||
{ name: 'هر دو (سگ و گربه)', value: bothCount || 12 },
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -51,9 +51,9 @@
|
||||
- [x] **TASK-2.20 [Admin Realtime Notifications]:** پیادهسازی سیستم اعلانهای واقعی/Realtime ادمین در هدر (ثبت سفارش جدید، نظر جدید، درخواست B2B، درخواست برداشت کیف پول و هشدارهای موجودی).
|
||||
|
||||
### 📊 داشبورد و آمار واقعی (Dashboard Analytics Fixes)
|
||||
- [ ] **TASK-2.21 [Admin Dashboard]:** اصلاح نمودار دستهبندی محصولات بر اساس دیتای واقعی محصولات (سگ، گربه یا هر دو).
|
||||
- [ ] **TASK-2.22 [Admin Dashboard]:** اصلاح شمارش تعداد بازدیدهای روزانه بر اساس آمار واقعی بازدیدی که لاگ میشود.
|
||||
- [ ] **TASK-2.23 [Admin Knowledge Base]:** قابلیت انتخاب و کم/زیاد کردن لیست محصولات مرتبط در فرم ویرایش دانشنامه.
|
||||
- [x] **TASK-2.21 [Admin Dashboard]:** اصلاح نمودار دستهبندی محصولات بر اساس دیتای واقعی محصولات (سگ، گربه یا هر دو).
|
||||
- [x] **TASK-2.22 [Admin Dashboard]:** اصلاح شمارش تعداد بازدیدهای روزانه بر اساس آمار واقعی بازدیدی که لاگ میشود.
|
||||
- [x] **TASK-2.23 [Admin Knowledge Base]:** قابلیت انتخاب و کم/زیاد کردن لیست محصولات مرتبط در فرم ویرایش دانشنامه.
|
||||
|
||||
### 🔔 پوش نوتیفیکیشن و سیستم CRM (Push Notifications & CRM)
|
||||
- [ ] **TASK-2.24 [Backend & Admin]:** پیادهسازی سیستم Push Notification و امکان ارسال پیام عمومی یا گروهی به کاربران از پنل ادمین.
|
||||
|
||||
@ -36,12 +36,12 @@ export default function Dashboard() {
|
||||
{ name: 'جمعه', بازدید: 349, فروش: 4300 },
|
||||
];
|
||||
|
||||
const pieData = [
|
||||
{ name: 'سگ', value: 400 },
|
||||
{ name: 'گربه', value: 300 },
|
||||
{ name: 'پرنده', value: 300 },
|
||||
const pieData = (data as any).categoriesDistribution || [
|
||||
{ name: 'مکمل سگ', value: 8 },
|
||||
{ name: 'مکمل گربه', value: 6 },
|
||||
{ name: 'سگ و گربه', value: 12 },
|
||||
];
|
||||
const COLORS = ['#0088FE', '#00C49F', '#FFBB28'];
|
||||
const COLORS = ['#8b5cf6', '#3b82f6', '#10b981'];
|
||||
|
||||
const stats = [
|
||||
{ title: 'درآمد کل', value: `${data.revenue.toLocaleString()} تومان`, icon: DollarSign, color: 'text-green-600', bg: 'bg-green-100', link: '/reports' },
|
||||
|
||||
@ -17,10 +17,16 @@ export default function Wiki() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [editingTerm, setEditingTerm] = useState<any>(null);
|
||||
const [deleteTargetKey, setDeleteTargetKey] = useState<string | null>(null);
|
||||
const [productsList, setProductsList] = useState<any[]>([]);
|
||||
const [formData, setFormData] = useState({
|
||||
key: '',
|
||||
term: '',
|
||||
definition: ''
|
||||
definition: '',
|
||||
relatedProducts: [] as string[],
|
||||
wikiId: 'general',
|
||||
metaTitle: '',
|
||||
metaDescription: '',
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
const fetchTerms = async () => {
|
||||
@ -77,6 +83,12 @@ export default function Wiki() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
api.get('/admin/products', { params: { limit: 100 } })
|
||||
.then(res => setProductsList(res.data?.data || []))
|
||||
.catch(err => console.error(err));
|
||||
}, []);
|
||||
|
||||
const openModal = (term: any = null) => {
|
||||
if (term) {
|
||||
setEditingTerm(term);
|
||||
@ -84,7 +96,8 @@ export default function Wiki() {
|
||||
key: term.key,
|
||||
term: term.term,
|
||||
definition: term.definition,
|
||||
wikiId: term.wikiId,
|
||||
relatedProducts: Array.isArray(term.relatedProducts) ? term.relatedProducts : [],
|
||||
wikiId: term.wikiId || 'general',
|
||||
metaTitle: term.metaTitle || '',
|
||||
metaDescription: term.metaDescription || '',
|
||||
keywords: term.keywords || ''
|
||||
@ -92,7 +105,7 @@ export default function Wiki() {
|
||||
} else {
|
||||
setEditingTerm(null);
|
||||
setFormData({
|
||||
key: '', term: '', definition: '', wikiId: 'general', metaTitle: '', metaDescription: '', keywords: ''
|
||||
key: '', term: '', definition: '', relatedProducts: [], wikiId: 'general', metaTitle: '', metaDescription: '', keywords: ''
|
||||
});
|
||||
}
|
||||
setIsModalOpen(true);
|
||||
@ -201,6 +214,43 @@ export default function Wiki() {
|
||||
<textarea required value={formData.definition} onChange={(e) => setFormData({ ...formData, definition: e.target.value })} rows={5} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"></textarea>
|
||||
</div>
|
||||
|
||||
{/* Related Products Selector (TASK-2.23) */}
|
||||
<div className="space-y-2 border border-gray-200 p-4 rounded-xl bg-purple-50/40">
|
||||
<label className="text-xs font-bold text-gray-800 block">محصولات مرتبط با این مقاله علمی (TASK-2.23)</label>
|
||||
<select
|
||||
onChange={(e) => {
|
||||
const prodId = e.target.value;
|
||||
if (prodId && !formData.relatedProducts.includes(prodId)) {
|
||||
setFormData({ ...formData, relatedProducts: [...formData.relatedProducts, prodId] });
|
||||
}
|
||||
}}
|
||||
className="w-full px-3 py-2 border rounded-xl text-xs bg-white"
|
||||
>
|
||||
<option value="">+ انتخاب محصول جهت افزودن به لیست مرتبط</option>
|
||||
{productsList.map(p => (
|
||||
<option key={p.id} value={p.id}>{p.nameFa} ({p.artNo})</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{formData.relatedProducts.map(pId => {
|
||||
const prod = productsList.find(p => p.id === pId);
|
||||
return (
|
||||
<span key={pId} className="inline-flex items-center gap-1.5 px-3 py-1 bg-purple-100 text-purple-700 text-xs font-bold rounded-full">
|
||||
<span>{prod ? prod.nameFa : pId}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFormData({ ...formData, relatedProducts: formData.relatedProducts.filter(id => id !== pId) })}
|
||||
className="hover:text-red-600 font-black text-sm"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 border border-gray-200 p-4 rounded-xl bg-gray-50/50">
|
||||
<h4 className="font-bold text-purple-700 text-sm mb-2">تنظیمات سئو (SEO)</h4>
|
||||
<div className="space-y-2">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user