Live Dashboard and debug logging
This commit is contained in:
@@ -1,116 +1,197 @@
|
||||
/**
|
||||
* ════════════════════════════════════════════════════════════
|
||||
* مُصادَق (Musadaq) — Dashboard Statistics Components
|
||||
* مُصادَق (Musadaq) — Dashboard Page
|
||||
* ════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
BarChart3,
|
||||
Users,
|
||||
FileText,
|
||||
TrendingUp,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
Clock,
|
||||
CheckCircle2,
|
||||
AlertCircle,
|
||||
TrendingUp,
|
||||
Wallet,
|
||||
ArrowUpRight
|
||||
Building2
|
||||
} from 'lucide-react';
|
||||
|
||||
const stats = [
|
||||
{ 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%' },
|
||||
];
|
||||
import { motion } from 'framer-motion';
|
||||
import apiClient from '../../api/client';
|
||||
|
||||
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 (
|
||||
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
||||
<header className="flex items-center justify-between">
|
||||
<div className="flex-1 flex justify-center items-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></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-bold text-slate-900">لوحة التحكم</h2>
|
||||
<p className="text-slate-500 mt-1">نظرة عامة على نشاطك الضريبي هذا الشهر.</p>
|
||||
<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="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 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">
|
||||
{stats.map((stat, i) => (
|
||||
{statCards.map((card, idx) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
key={card.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: i * 0.1 }}
|
||||
className="card-premium p-6 group cursor-pointer"
|
||||
transition={{ delay: idx * 0.1 }}
|
||||
className="card-premium p-6 group hover:scale-[1.02] transition-all cursor-default"
|
||||
>
|
||||
<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 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>
|
||||
<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}
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
{/* ── 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>
|
||||
{/* ── 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="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 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 () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const [invRes, compRes] = await Promise.all([
|
||||
apiClient.get('/invoices'),
|
||||
apiClient.get('/companies')
|
||||
]);
|
||||
console.log('Fetched Invoices:', invRes.data);
|
||||
console.log('Fetched Companies:', compRes.data);
|
||||
setInvoices(invRes.data);
|
||||
setCompanies(compRes.data);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user