Live Dashboard and debug logging
This commit is contained in:
@@ -1,118 +1,199 @@
|
|||||||
/**
|
/**
|
||||||
* ════════════════════════════════════════════════════════════
|
* ════════════════════════════════════════════════════════════
|
||||||
* مُصادَق (Musadaq) — Dashboard Statistics Components
|
* مُصادَق (Musadaq) — Dashboard Page
|
||||||
* ════════════════════════════════════════════════════════════
|
* ════════════════════════════════════════════════════════════
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
|
BarChart3,
|
||||||
|
Users,
|
||||||
FileText,
|
FileText,
|
||||||
CheckCircle2,
|
|
||||||
AlertCircle,
|
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
Wallet,
|
ArrowUpRight,
|
||||||
ArrowUpRight
|
ArrowDownRight,
|
||||||
|
Clock,
|
||||||
|
CheckCircle2,
|
||||||
|
AlertCircle,
|
||||||
|
Building2
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
const stats = [
|
import apiClient from '../../api/client';
|
||||||
{ label: 'إجمالي الفواتير', value: '1,280', icon: FileText, color: 'text-primary-600', bg: 'bg-primary-50', change: '+12%' },
|
|
||||||
{ label: 'تمت مصادقتها', value: '1,150', icon: CheckCircle2, color: 'text-emerald-600', bg: 'bg-emerald-50', change: '+18%' },
|
|
||||||
{ label: 'قيد المراجعة', value: '42', icon: AlertCircle, color: 'text-amber-600', bg: 'bg-amber-50', change: '-5%' },
|
|
||||||
{ label: 'مجموع الضريبة (JOD)', value: '14,250.000', icon: Wallet, color: 'text-blue-600', bg: 'bg-blue-50', change: '+8%' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const DashboardPage = () => {
|
export const DashboardPage = () => {
|
||||||
|
const [stats, setStats] = useState<any>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
const fetchStats = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.get('/dashboard/stats');
|
||||||
|
setStats(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch dashboard stats', error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchStats();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
<div className="flex-1 flex justify-center items-center">
|
||||||
<header className="flex items-center justify-between">
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
|
||||||
<div>
|
</div>
|
||||||
<h2 className="text-3xl font-bold text-slate-900">لوحة التحكم</h2>
|
|
||||||
<p className="text-slate-500 mt-1">نظرة عامة على نشاطك الضريبي هذا الشهر.</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-3">
|
|
||||||
<button className="bg-white border border-slate-200 text-slate-700 font-semibold py-2.5 px-6 rounded-xl shadow-sm hover:bg-slate-50 transition-all flex items-center gap-2">
|
|
||||||
<TrendingUp className="w-4 h-4 text-primary-500" />
|
|
||||||
تصدير التقارير
|
|
||||||
</button>
|
|
||||||
<button className="btn-primary py-2.5 px-6 rounded-xl flex items-center gap-2 shadow-lg shadow-primary-500/25">
|
|
||||||
<FileText className="w-5 h-5" />
|
|
||||||
فاتورة جديدة
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* ── Stats Grid ────────────────────────────────────────── */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
||||||
{stats.map((stat, i) => (
|
|
||||||
<motion.div
|
|
||||||
key={i}
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: i * 0.1 }}
|
|
||||||
className="card-premium p-6 group cursor-pointer"
|
|
||||||
>
|
|
||||||
<div className="flex items-start justify-between mb-4">
|
|
||||||
<div className={`p-3 rounded-2xl ${stat.bg} ${stat.color} transition-transform group-hover:scale-110 duration-300`}>
|
|
||||||
<stat.icon className="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<div className={`flex items-center gap-1 text-[12px] font-bold px-2 py-1 rounded-full ${stat.change.startsWith('+') ? 'bg-emerald-50 text-emerald-600' : 'bg-red-50 text-red-600'}`}>
|
|
||||||
<ArrowUpRight className="w-3 h-3" />
|
|
||||||
{stat.change}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-slate-500 text-sm font-medium">{stat.label}</p>
|
|
||||||
<h3 className="text-2xl font-bold text-slate-900 mt-1">{stat.value}</h3>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ── Main Dashboard Content (Placeholder for Charts/Lists) ── */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
||||||
<div className="lg:col-span-2 space-y-6">
|
|
||||||
<div className="card-premium h-[400px] p-6 flex flex-col">
|
|
||||||
<div className="flex items-center justify-between mb-8">
|
|
||||||
<h4 className="font-bold text-lg">تحليلات الفوترة الأسبوعية</h4>
|
|
||||||
<select className="bg-slate-50 border border-slate-100 rounded-lg py-1.5 px-3 text-sm font-medium outline-none">
|
|
||||||
<option>آخر 7 أيام</option>
|
|
||||||
<option>آخر 30 يوم</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 bg-slate-50 rounded-2xl border border-dashed border-slate-200 flex items-center justify-center">
|
|
||||||
<p className="text-slate-400 text-sm font-medium italic">رسم بياني توضيحي (Chart integration goes here)</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="card-premium p-6 bg-primary-600 text-white shadow-xl shadow-primary-500/30">
|
|
||||||
<h4 className="font-bold text-lg mb-2">استهلاك الاشتراك الحالي</h4>
|
|
||||||
<p className="text-primary-100 text-sm mb-6">لقد استهلكت 65% من حصتك الشهرية من الفواتير.</p>
|
|
||||||
<div className="w-full h-3 bg-white/20 rounded-full overflow-hidden mb-6">
|
|
||||||
<div className="w-2/3 h-full bg-white rounded-full shadow-lg" />
|
|
||||||
</div>
|
|
||||||
<button className="w-full bg-white text-primary-600 font-bold py-3 rounded-xl hover:bg-primary-50 transition-all">
|
|
||||||
ترقية الباقة الآن
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="card-premium p-6">
|
|
||||||
<h4 className="font-bold text-lg mb-4">آخر النشاطات</h4>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{[1, 2, 3].map(i => (
|
|
||||||
<div key={i} className="flex items-center gap-3 p-2 hover:bg-slate-50 rounded-xl transition-all cursor-pointer">
|
|
||||||
<div className="w-10 h-10 bg-slate-100 rounded-lg flex items-center justify-center">
|
|
||||||
<FileText className="w-5 h-5 text-slate-500" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<p className="text-sm font-bold text-slate-800">فاتورة مبيعات #A-2024-001</p>
|
|
||||||
<p className="text-[12px] text-slate-500">منذ 10 دقائق · تمت المصادقة</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const statCards = [
|
||||||
|
{
|
||||||
|
title: 'إجمالي الفواتير',
|
||||||
|
value: stats?.totalInvoices || 0,
|
||||||
|
icon: FileText,
|
||||||
|
color: 'bg-blue-500',
|
||||||
|
trend: '+12%',
|
||||||
|
isUp: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'الفواتير المصدقة',
|
||||||
|
value: stats?.approvedInvoices || 0,
|
||||||
|
icon: CheckCircle2,
|
||||||
|
color: 'bg-emerald-500',
|
||||||
|
trend: '+8%',
|
||||||
|
isUp: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'إجمالي الشركات',
|
||||||
|
value: stats?.companiesCount || 0,
|
||||||
|
icon: Building2,
|
||||||
|
color: 'bg-purple-500',
|
||||||
|
trend: '+2',
|
||||||
|
isUp: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'إجمالي الضرائب (JOD)',
|
||||||
|
value: Number(stats?.totalTax || 0).toLocaleString('ar-JO', { minimumFractionDigits: 3 }),
|
||||||
|
icon: TrendingUp,
|
||||||
|
color: 'bg-amber-500',
|
||||||
|
trend: '+5%',
|
||||||
|
isUp: true
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-8 animate-in fade-in duration-700">
|
||||||
|
<header className="flex justify-between items-end">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl font-black text-slate-900 tracking-tight">لوحة التحكم</h2>
|
||||||
|
<p className="text-slate-500 mt-1 font-medium">مرحباً بك مجدداً! إليك ملخص نشاط مكتبك اليوم.</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button className="glass border-slate-200 px-5 py-2.5 rounded-2xl text-slate-600 font-bold text-sm hover:bg-white transition-all">
|
||||||
|
آخر 30 يوم
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* ── Stats Grid ────────────────────────────────────────── */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
{statCards.map((card, idx) => (
|
||||||
|
<motion.div
|
||||||
|
key={card.title}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: idx * 0.1 }}
|
||||||
|
className="card-premium p-6 group hover:scale-[1.02] transition-all cursor-default"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start mb-4">
|
||||||
|
<div className={`w-12 h-12 ${card.color} rounded-2xl flex items-center justify-center text-white shadow-lg shadow-inherit/20`}>
|
||||||
|
<card.icon className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<span className={`flex items-center gap-1 text-xs font-bold ${card.isUp ? 'text-emerald-600 bg-emerald-50' : 'text-red-600 bg-red-50'} px-2 py-1 rounded-lg`}>
|
||||||
|
{card.trend}
|
||||||
|
{card.isUp ? <ArrowUpRight className="w-3 h-3" /> : <ArrowDownRight className="w-3 h-3" />}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-slate-500 font-bold text-sm mb-1">{card.title}</h3>
|
||||||
|
<div className="text-2xl font-black text-slate-900 tracking-tight">{card.value}</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
{/* ── Recent Activities ────────────────────────────────── */}
|
||||||
|
<div className="lg:col-span-2 space-y-4">
|
||||||
|
<div className="flex items-center justify-between px-2">
|
||||||
|
<h3 className="text-xl font-bold text-slate-900 flex items-center gap-2">
|
||||||
|
<Clock className="w-5 h-5 text-primary-600" />
|
||||||
|
آخر الفواتير المرفوعة
|
||||||
|
</h3>
|
||||||
|
<button className="text-primary-600 text-sm font-bold hover:underline">عرض الكل</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card-premium overflow-hidden bg-white border border-slate-100">
|
||||||
|
{(!stats?.recentActivities || stats.recentActivities.length === 0) ? (
|
||||||
|
<div className="p-12 text-center text-slate-400 font-medium">
|
||||||
|
لا توجد نشاطات حديثة بعد.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<table className="w-full text-right">
|
||||||
|
<thead className="bg-slate-50 border-b border-slate-100">
|
||||||
|
<tr>
|
||||||
|
<th className="px-6 py-4 text-xs font-bold text-slate-500">الفاتورة</th>
|
||||||
|
<th className="px-6 py-4 text-xs font-bold text-slate-500">الشركة</th>
|
||||||
|
<th className="px-6 py-4 text-xs font-bold text-slate-500">المبلغ</th>
|
||||||
|
<th className="px-6 py-4 text-xs font-bold text-slate-500">الحالة</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-slate-100">
|
||||||
|
{stats.recentActivities.map((inv: any) => (
|
||||||
|
<tr key={inv.id} className="hover:bg-slate-50/50 transition-colors">
|
||||||
|
<td className="px-6 py-4 font-bold text-slate-800">{inv.invoice_number || 'OCR_PENDING'}</td>
|
||||||
|
<td className="px-6 py-4 text-slate-600">{inv.company?.name}</td>
|
||||||
|
<td className="px-6 py-4 font-mono font-bold text-slate-700">{Number(inv.total_amount || 0).toFixed(3)}</td>
|
||||||
|
<td className="px-6 py-4">
|
||||||
|
<span className={`px-2 py-1 rounded-md text-[10px] font-bold ${
|
||||||
|
inv.status === 'approved' ? 'bg-emerald-50 text-emerald-600' : 'bg-amber-50 text-amber-600'
|
||||||
|
}`}>
|
||||||
|
{inv.status === 'approved' ? 'مصدقة' : 'قيد المعالجة'}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Quick Actions ────────────────────────────────────── */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-xl font-bold text-slate-900 px-2">إجراءات سريعة</h3>
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<button className="flex items-center gap-4 p-4 rounded-2xl bg-primary-600 text-white shadow-xl shadow-primary-500/25 hover:bg-primary-700 transition-all group">
|
||||||
|
<div className="w-10 h-10 rounded-xl bg-white/20 flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||||
|
<FileText className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="font-bold">رفع فاتورة جديدة</div>
|
||||||
|
<div className="text-xs text-white/70">معالجة فورية بالذكاء الاصطناعي</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="flex items-center gap-4 p-4 rounded-2xl bg-white border border-slate-200 text-slate-800 hover:border-primary-500 transition-all group">
|
||||||
|
<div className="w-10 h-10 rounded-xl bg-slate-50 flex items-center justify-center group-hover:bg-primary-50 group-hover:text-primary-600 transition-all">
|
||||||
|
<Building2 className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="font-bold">إضافة شركة</div>
|
||||||
|
<div className="text-xs text-slate-500">تسجيل عميل جديد في المكتب</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,10 +37,13 @@ export const InvoicesPage = () => {
|
|||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
const [invRes, compRes] = await Promise.all([
|
const [invRes, compRes] = await Promise.all([
|
||||||
apiClient.get('/invoices'),
|
apiClient.get('/invoices'),
|
||||||
apiClient.get('/companies')
|
apiClient.get('/companies')
|
||||||
]);
|
]);
|
||||||
|
console.log('Fetched Invoices:', invRes.data);
|
||||||
|
console.log('Fetched Companies:', compRes.data);
|
||||||
setInvoices(invRes.data);
|
setInvoices(invRes.data);
|
||||||
setCompanies(compRes.data);
|
setCompanies(compRes.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user