💎 Luxury dark redesign + API integration for Trojan Horse and Elite Dashboard

This commit is contained in:
Hamza-Ayed
2026-04-22 02:01:08 +03:00
parent 2e900e395f
commit 660c551098
8 changed files with 548 additions and 324 deletions

View File

@@ -1,6 +1,6 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Main Layout Shell
* مُصادَق (Musadaq) — Premium Dark Layout Shell
* ════════════════════════════════════════════════════════════
*/
@@ -13,36 +13,36 @@ export const MainLayout = () => {
const user = useAuthStore((state) => state.user);
return (
<div className="flex bg-slate-50 min-h-screen rtl overflow-hidden">
<div className="flex bg-slate-950 min-h-screen rtl overflow-hidden">
{/* ── Desktop Sidebar ───────────────────────────────────── */}
<Sidebar />
<div className="flex-1 flex flex-col h-screen overflow-y-auto">
{/* ── Top Navigation ──────────────────────────────────── */}
<header className="h-16 bg-white/80 backdrop-blur-md sticky top-0 z-30 border-b border-slate-100 px-8 flex items-center justify-between shadow-sm">
<div className="flex items-center gap-4 bg-slate-50 px-4 py-2 rounded-xl group focus-within:ring-2 focus-within:ring-primary-100 transition-all border border-transparent focus-within:border-primary-200">
<Search className="w-4 h-4 text-slate-400" />
<input
type="text"
placeholder="بحث سريع..."
className="bg-transparent border-none outline-none text-sm w-64 text-slate-900"
<header className="h-16 bg-slate-900/80 backdrop-blur-xl sticky top-0 z-30 border-b border-slate-800/60 px-8 flex items-center justify-between">
<div className="flex items-center gap-4 bg-slate-800/50 px-4 py-2 rounded-xl group focus-within:ring-2 focus-within:ring-emerald-500/20 transition-all border border-slate-700/50 focus-within:border-emerald-500/30">
<Search className="w-4 h-4 text-slate-500" />
<input
type="text"
placeholder="بحث سريع..."
className="bg-transparent border-none outline-none text-sm w-64 text-slate-300 placeholder-slate-500"
/>
</div>
<div className="flex items-center gap-6">
<button className="p-2 text-slate-400 hover:bg-slate-50 hover:text-primary-600 rounded-xl transition-all relative">
<button className="p-2 text-slate-400 hover:bg-slate-800 hover:text-emerald-400 rounded-xl transition-all relative">
<Bell className="w-5 h-5" />
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-red-500 rounded-full border-2 border-white"></span>
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-emerald-500 rounded-full border-2 border-slate-900"></span>
</button>
<div className="flex items-center gap-3 pl-2 border-r border-slate-100">
<div className="flex items-center gap-3 pl-2 border-r border-slate-800">
<div className="text-left">
<p className="text-sm font-semibold text-slate-900">{user?.name}</p>
<p className="text-[12px] text-slate-500 uppercase tracking-wider font-medium">
<p className="text-sm font-semibold text-white">{user?.name}</p>
<p className="text-[10px] text-slate-500 uppercase tracking-wider font-medium">
{user?.role}
</p>
</div>
<div className="w-10 h-10 bg-slate-100 rounded-full flex items-center justify-center border-2 border-white shadow-sm ring-1 ring-slate-100">
<div className="w-10 h-10 bg-slate-800 rounded-full flex items-center justify-center border-2 border-slate-700 ring-1 ring-slate-700/50">
<User className="text-slate-400 w-5 h-5" />
</div>
</div>

View File

@@ -1,16 +1,16 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Premium Sidebar
* مُصادَق (Musadaq) — Premium Dark Sidebar
* ════════════════════════════════════════════════════════════
*/
import { NavLink, useNavigate } from 'react-router-dom';
import {
LayoutDashboard,
FileText,
Building2,
Users,
Settings,
import {
LayoutDashboard,
FileText,
Building2,
Users,
Settings,
LogOut,
Crown
} from 'lucide-react';
@@ -35,16 +35,21 @@ export const Sidebar = () => {
};
return (
<aside className="w-64 h-screen glass border-l border-slate-200 sticky top-0 flex flex-col p-4">
<aside className="w-64 h-screen bg-slate-950 border-l border-slate-800/60 sticky top-0 flex flex-col p-4">
{/* Brand */}
<div className="flex items-center gap-3 px-2 py-6">
<div className="w-10 h-10 bg-primary-600 rounded-xl flex items-center justify-center shadow-lg shadow-primary-500/30">
<div className="w-10 h-10 bg-gradient-to-br from-emerald-500 to-emerald-600 rounded-xl flex items-center justify-center shadow-lg shadow-emerald-500/20">
<FileText className="text-white w-6 h-6" />
</div>
<h1 className="text-xl font-bold bg-gradient-to-br from-slate-900 to-slate-500 bg-clip-text text-transparent">
مُصادَق
</h1>
<div>
<h1 className="text-xl font-bold text-white">
مُصادَق
</h1>
<p className="text-[10px] text-slate-500 font-medium tracking-wider">ELITE ACCOUNTANT HUB</p>
</div>
</div>
{/* Navigation */}
<nav className="flex-1 mt-4 space-y-1">
{menuItems.map((item) => (
<NavLink
@@ -52,9 +57,9 @@ export const Sidebar = () => {
to={item.path}
className={({ isActive }) =>
`flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200 group ${
isActive
? 'bg-primary-50 text-primary-600 shadow-sm border border-primary-100'
: 'text-slate-500 hover:bg-slate-50 hover:text-slate-900'
isActive
? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'
: 'text-slate-400 hover:bg-slate-800/60 hover:text-slate-200 border border-transparent'
}`
}
>
@@ -64,10 +69,11 @@ export const Sidebar = () => {
))}
</nav>
<div className="pt-4 border-t border-slate-100">
{/* Logout */}
<div className="pt-4 border-t border-slate-800/60">
<button
onClick={handleLogout}
className="flex items-center gap-3 px-4 py-3 w-full rounded-xl text-red-500 hover:bg-red-50 transition-all group"
className="flex items-center gap-3 px-4 py-3 w-full rounded-xl text-red-400 hover:bg-red-500/10 transition-all group border border-transparent hover:border-red-500/20"
>
<LogOut className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
<span className="font-medium">تسجيل الخروج</span>

View File

@@ -19,25 +19,25 @@
@layer base {
body {
@apply bg-slate-50 text-slate-900 antialiased;
@apply bg-slate-950 text-slate-300 antialiased;
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
}
h1, h2, h3, h4, h5, h6 {
@apply font-semibold tracking-tight text-slate-900;
@apply font-semibold tracking-tight text-white;
}
}
@layer components {
.glass {
@apply bg-white/70 backdrop-blur-md border border-white/20 shadow-xl;
@apply bg-slate-900/50 backdrop-blur-xl border border-slate-800/60 shadow-xl;
}
.card-premium {
@apply bg-white border border-slate-200 shadow-sm hover:shadow-md transition-all duration-200 rounded-xl overflow-hidden;
@apply bg-slate-900/50 backdrop-blur-xl border border-slate-800/60 shadow-sm hover:shadow-md hover:border-slate-700 transition-all duration-200 rounded-xl overflow-hidden;
}
.btn-primary {
@apply bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg shadow-sm transition-all active:scale-95;
@apply bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-4 rounded-lg shadow-sm shadow-emerald-500/20 transition-all active:scale-95;
}
}

View File

@@ -1,11 +1,19 @@
import { useState, type DragEvent } from 'react';
import { UploadCloud, FileType, CheckCircle2, ArrowRight } from 'lucide-react';
import { useState, useRef, type DragEvent } from 'react';
import { UploadCloud, FileType, CheckCircle2, ArrowRight, AlertCircle, Loader2, Download } from 'lucide-react';
import { motion } from 'framer-motion';
import { Link } from 'react-router-dom';
import axios from 'axios';
const API_URL = import.meta.env.VITE_API_URL || '/api';
export const TrojanHorseConverter = () => {
const [dragActive, setDragActive] = useState(false);
const [file, setFile] = useState<File | null>(null);
const [status, setStatus] = useState<'idle' | 'uploading' | 'success'>('idle');
const [status, setStatus] = useState<'idle' | 'uploading' | 'success' | 'error'>('idle');
const [xmlContent, setXmlContent] = useState<string>('');
const [extractedData, setExtractedData] = useState<any>(null);
const [errorMessage, setErrorMessage] = useState('');
const fileInputRef = useRef<HTMLInputElement>(null);
const handleDrag = (e: DragEvent) => {
e.preventDefault();
@@ -26,76 +34,126 @@ export const TrojanHorseConverter = () => {
}
};
const handleConvert = () => {
const handleConvert = async () => {
if (!file) return;
setStatus('uploading');
// Simulate API call to the backend Trojan endpoint
setTimeout(() => {
setStatus('success');
}, 2000);
setErrorMessage('');
try {
const formData = new FormData();
formData.append('file', file);
const response = await axios.post(`${API_URL}/public/tools/pdf-to-xml`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
if (response.data.success) {
setXmlContent(response.data.xmlContent);
setExtractedData(response.data.data);
setStatus('success');
} else {
setErrorMessage(response.data.message || 'حدث خطأ أثناء التحويل');
setStatus('error');
}
} catch (err: any) {
setErrorMessage(
err.response?.data?.message || 'فشل الاتصال بالخادم. تأكد من رفع ملف صالح.'
);
setStatus('error');
}
};
const handleDownloadXml = () => {
if (!xmlContent) return;
const blob = new Blob([xmlContent], { type: 'application/xml;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `invoice_${Date.now()}.xml`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
const handleReset = () => {
setStatus('idle');
setFile(null);
setXmlContent('');
setExtractedData(null);
setErrorMessage('');
};
return (
<div className="min-h-screen bg-slate-50 text-slate-900 font-sans selection:bg-emerald-200">
<div className="min-h-screen bg-slate-950 text-slate-300 font-sans selection:bg-emerald-500/30">
{/* Top Banner */}
<div className="bg-slate-900 text-center py-3 text-sm text-slate-300">
هل تدير عشرات الشركات؟ <span className="text-white font-medium ml-2 cursor-pointer hover:text-emerald-400 transition-colors">اكتشف لوحة تحكم محاسبي إيليت <ArrowRight className="inline w-4 h-4" /></span>
<div className="bg-gradient-to-r from-emerald-900/40 to-slate-900 text-center py-3 text-sm text-slate-400 border-b border-slate-800">
هل تدير عشرات الشركات؟{' '}
<Link to="/login" className="text-emerald-400 font-medium hover:text-emerald-300 transition-colors">
اكتشف لوحة تحكم محاسبي إيليت <ArrowRight className="inline w-4 h-4" />
</Link>
</div>
<div className="max-w-4xl mx-auto px-6 py-20">
{/* Header */}
<div className="text-center mb-16">
<div className="inline-block px-4 py-1.5 rounded-full bg-emerald-100 text-emerald-800 text-xs font-bold mb-6">
أداة مجانية 100%
<div className="inline-block px-4 py-1.5 rounded-full bg-emerald-500/10 text-emerald-400 text-xs font-bold mb-6 border border-emerald-500/20">
أداة مجانية 100% بدون تسجيل
</div>
<h1 className="text-4xl md:text-5xl font-extrabold tracking-tight mb-6 text-slate-900">
حوّل فواتيرك إلى صيغة <span className="text-emerald-600">جو فوترة</span> في ثوانٍ
<h1 className="text-4xl md:text-5xl font-extrabold tracking-tight mb-6 text-white">
حوّل فواتيرك إلى صيغة <span className="text-emerald-400">جو فوترة</span> في ثوانٍ
</h1>
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
<p className="text-lg text-slate-400 max-w-2xl mx-auto leading-relaxed">
قم برفع فاتورة الـ PDF الخاصة بك وسيقوم الذكاء الاصطناعي باستخراج البيانات وتحويلها إلى صيغة XML المتوافقة تماماً مع نظام دائرة ضريبة الدخل والمبيعات الأردنية.
</p>
</div>
{/* Upload Area */}
<motion.div
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-3xl shadow-xl p-8 border border-slate-100"
className="bg-slate-900/50 backdrop-blur-xl rounded-3xl shadow-2xl p-8 border border-slate-800"
>
{status === 'idle' && (
<div
<div
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
className={`border-2 border-dashed rounded-2xl p-12 text-center transition-all ${
dragActive ? 'border-emerald-500 bg-emerald-50/50' : 'border-slate-200 hover:border-slate-300'
className={`border-2 border-dashed rounded-2xl p-12 text-center transition-all cursor-pointer ${
dragActive ? 'border-emerald-500 bg-emerald-500/5' : 'border-slate-700 hover:border-slate-600'
}`}
onClick={() => !file && fileInputRef.current?.click()}
>
<UploadCloud className={`w-16 h-16 mx-auto mb-6 ${dragActive ? 'text-emerald-500' : 'text-slate-400'}`} />
<UploadCloud className={`w-16 h-16 mx-auto mb-6 transition-colors ${dragActive ? 'text-emerald-400' : 'text-slate-600'}`} />
{file ? (
<div>
<p className="text-lg font-medium text-slate-900 mb-2">{file.name}</p>
<p className="text-lg font-medium text-white mb-2">{file.name}</p>
<p className="text-sm text-slate-500 mb-8">{(file.size / 1024 / 1024).toFixed(2)} MB</p>
<button
onClick={handleConvert}
className="px-8 py-3 bg-slate-900 hover:bg-slate-800 text-white rounded-xl font-medium shadow-lg transition-all w-full md:w-auto"
<button
onClick={(e) => { e.stopPropagation(); handleConvert(); }}
className="px-8 py-3.5 bg-emerald-500 hover:bg-emerald-600 text-slate-950 rounded-xl font-bold shadow-lg shadow-emerald-500/20 transition-all"
>
بدء التحويل إلى XML
</button>
</div>
) : (
<div>
<p className="text-xl font-medium text-slate-900 mb-2">اسحب وأفلت الفاتورة هنا</p>
<p className="text-xl font-medium text-white mb-2">اسحب وأفلت الفاتورة هنا</p>
<p className="text-sm text-slate-500 mb-8">يدعم PDF, PNG, JPG</p>
<label className="px-8 py-3 bg-emerald-500 hover:bg-emerald-600 text-white rounded-xl font-medium shadow-lg shadow-emerald-500/30 transition-all cursor-pointer inline-block">
<span className="px-8 py-3.5 bg-emerald-500 hover:bg-emerald-600 text-slate-950 rounded-xl font-bold shadow-lg shadow-emerald-500/30 transition-all inline-block">
اختر ملف
<input type="file" className="hidden" onChange={(e) => e.target.files && setFile(e.target.files[0])} accept=".pdf,.png,.jpg" />
</label>
</span>
<input
ref={fileInputRef}
type="file"
className="hidden"
onChange={(e) => e.target.files && setFile(e.target.files[0])}
accept=".pdf,.png,.jpg,.jpeg"
/>
</div>
)}
</div>
@@ -103,52 +161,104 @@ export const TrojanHorseConverter = () => {
{status === 'uploading' && (
<div className="py-20 text-center">
<div className="w-16 h-16 border-4 border-slate-200 border-t-emerald-500 rounded-full animate-spin mx-auto mb-6"></div>
<h3 className="text-xl font-medium text-slate-900 mb-2">جاري استخراج البيانات...</h3>
<p className="text-slate-500">يقوم الذكاء الاصطناعي بقراءة الفاتورة وتنسيقها</p>
<Loader2 className="w-16 h-16 text-emerald-400 animate-spin mx-auto mb-6" />
<h3 className="text-xl font-medium text-white mb-2">جاري استخراج البيانات...</h3>
<p className="text-slate-400">يقوم الذكاء الاصطناعي بقراءة الفاتورة وتنسيقها. قد يستغرق الأمر عدة ثوانٍ.</p>
</div>
)}
{status === 'success' && (
<motion.div
{status === 'error' && (
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
className="py-12 text-center"
>
<div className="w-20 h-20 bg-emerald-100 rounded-full flex items-center justify-center mx-auto mb-6">
<CheckCircle2 className="w-10 h-10 text-emerald-600" />
<div className="w-20 h-20 bg-red-500/10 rounded-full flex items-center justify-center mx-auto mb-6 border border-red-500/20">
<AlertCircle className="w-10 h-10 text-red-400" />
</div>
<h3 className="text-2xl font-bold text-slate-900 mb-4">تم التحويل بنجاح!</h3>
<div className="flex flex-col sm:flex-row gap-4 justify-center mt-8">
<button className="px-6 py-3 bg-emerald-500 hover:bg-emerald-600 text-white rounded-xl font-medium flex items-center justify-center gap-2">
<FileType className="w-5 h-5" /> تحميل ملف XML
<h3 className="text-2xl font-bold text-white mb-4">حدث خطأ</h3>
<p className="text-slate-400 mb-8 max-w-md mx-auto">{errorMessage}</p>
<button
onClick={handleReset}
className="px-6 py-3 bg-slate-800 hover:bg-slate-700 text-white rounded-xl font-medium border border-slate-700 transition-all"
>
حاول مرة أخرى
</button>
</motion.div>
)}
{status === 'success' && (
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
className="py-12 text-center"
>
<div className="w-20 h-20 bg-emerald-500/10 rounded-full flex items-center justify-center mx-auto mb-6 border border-emerald-500/20">
<CheckCircle2 className="w-10 h-10 text-emerald-400" />
</div>
<h3 className="text-2xl font-bold text-white mb-2">تم التحويل بنجاح!</h3>
{/* Extracted Data Preview */}
{extractedData && (
<div className="mt-6 mb-8 bg-slate-800/50 rounded-xl p-4 text-right max-w-md mx-auto border border-slate-700/50">
<div className="grid grid-cols-2 gap-3 text-sm">
{extractedData.supplier_name && (
<>
<span className="text-slate-500">المورد:</span>
<span className="text-white font-medium">{extractedData.supplier_name}</span>
</>
)}
{extractedData.invoice_number && (
<>
<span className="text-slate-500">رقم الفاتورة:</span>
<span className="text-white font-medium">{extractedData.invoice_number}</span>
</>
)}
{extractedData.grand_total && (
<>
<span className="text-slate-500">المبلغ الإجمالي:</span>
<span className="text-emerald-400 font-bold">{Number(extractedData.grand_total).toFixed(3)} JOD</span>
</>
)}
</div>
</div>
)}
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<button
onClick={handleDownloadXml}
className="px-6 py-3.5 bg-emerald-500 hover:bg-emerald-600 text-slate-950 rounded-xl font-bold flex items-center justify-center gap-2 shadow-lg shadow-emerald-500/20 transition-all"
>
<Download className="w-5 h-5" /> تحميل ملف XML
</button>
<button
onClick={() => { setStatus('idle'); setFile(null); }}
className="px-6 py-3 bg-white border border-slate-200 hover:bg-slate-50 text-slate-700 rounded-xl font-medium"
<button
onClick={handleReset}
className="px-6 py-3 bg-slate-800 hover:bg-slate-700 text-white rounded-xl font-medium border border-slate-700 transition-all"
>
تحويل فاتورة أخرى
</button>
</div>
{/* Upsell Banner for Accountants */}
<div className="mt-12 bg-slate-900 rounded-2xl p-6 text-left flex flex-col md:flex-row items-center justify-between gap-6">
<div className="mt-12 bg-gradient-to-r from-slate-800 to-slate-900 rounded-2xl p-6 text-right flex flex-col md:flex-row items-center justify-between gap-6 border border-slate-700/50">
<div>
<h4 className="text-white font-bold mb-1 flex items-center gap-2">
<span className="text-emerald-400">مُصادَق</span> | هل أنت محاسب؟
</h4>
<p className="text-slate-400 text-sm">توقف عن تحويل الفواتير واحدة تلو الأخرى. جرب لوحة تحكم إيليت وأتمت عملك لـ 50 شركة بنقرة واحدة.</p>
</div>
<button className="whitespace-nowrap px-6 py-2.5 bg-white text-slate-900 rounded-lg font-medium hover:bg-slate-100">
<Link
to="/register"
className="whitespace-nowrap px-6 py-2.5 bg-emerald-500 text-slate-950 rounded-lg font-bold hover:bg-emerald-400 transition-all shadow-lg shadow-emerald-500/20"
>
اكتشف إيليت
</button>
</Link>
</div>
</motion.div>
)}
</motion.div>
</div>
</div>
);

View File

@@ -1,6 +1,6 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Premium Login Page
* مُصادَق (Musadaq) — Premium Dark Login Page
* ════════════════════════════════════════════════════════════
*/
@@ -10,7 +10,7 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { motion, AnimatePresence } from 'framer-motion';
import { LogIn, Mail, Lock, AlertCircle, Loader2 } from 'lucide-react';
import { LogIn, Mail, Lock, AlertCircle, Loader2, Zap } from 'lucide-react';
import apiClient from '../../api/client';
import { useAuthStore } from '../../store/authStore';
@@ -47,34 +47,34 @@ export default function LoginPage() {
};
return (
<div className="min-h-screen bg-slate-50 flex items-center justify-center p-6 relative overflow-hidden rtl">
<div className="min-h-screen bg-slate-950 flex items-center justify-center p-6 relative overflow-hidden rtl">
{/* ── Background Aesthetics ────────────────────────────────── */}
<div className="absolute top-0 left-0 w-full h-full opacity-10 pointer-events-none">
<div className="absolute top-[-20%] left-[-10%] w-[600px] h-[600px] bg-primary-300 rounded-full blur-[120px]" />
<div className="absolute bottom-[-20%] right-[-10%] w-[600px] h-[600px] bg-blue-300 rounded-full blur-[120px]" />
<div className="absolute top-0 left-0 w-full h-full pointer-events-none">
<div className="absolute top-[-20%] left-[-10%] w-[600px] h-[600px] bg-emerald-500/5 rounded-full blur-[150px]" />
<div className="absolute bottom-[-20%] right-[-10%] w-[600px] h-[600px] bg-blue-500/5 rounded-full blur-[150px]" />
</div>
<motion.div
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="w-full max-w-md glass p-10 rounded-3xl shadow-2xl relative z-10"
className="w-full max-w-md bg-slate-900/50 backdrop-blur-xl border border-slate-800/60 p-10 rounded-3xl shadow-2xl relative z-10"
>
<div className="text-center mb-10">
<div className="w-16 h-16 bg-primary-600 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-xl shadow-primary-500/20">
<div className="w-16 h-16 bg-gradient-to-br from-emerald-500 to-emerald-600 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-xl shadow-emerald-500/20">
<LogIn className="text-white w-8 h-8" />
</div>
<h1 className="text-3xl font-bold text-slate-900 mb-2">أهلاً بك في مُصادَق</h1>
<p className="text-slate-500">منصة أتمتة الفواتير الضريبية الأردنية</p>
<h1 className="text-3xl font-bold text-white mb-2">أهلاً بك في مُصادَق</h1>
<p className="text-slate-400">منصة أتمتة الفواتير الضريبية الأردنية</p>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<AnimatePresence mode="wait">
{error && (
<motion.div
<motion.div
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 10 }}
className="bg-red-50 border border-red-100 p-4 rounded-xl flex items-center gap-3 text-red-600 text-sm font-medium"
className="bg-red-500/10 border border-red-500/20 p-4 rounded-xl flex items-center gap-3 text-red-400 text-sm font-medium"
>
<AlertCircle className="w-5 h-5 flex-shrink-0" />
<span>{error}</span>
@@ -83,55 +83,56 @@ export default function LoginPage() {
</AnimatePresence>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2 mr-1">البريد الإلكتروني</label>
<label className="block text-sm font-semibold text-slate-300 mb-2 mr-1">البريد الإلكتروني</label>
<div className="relative group">
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400 group-focus-within:text-primary-500 transition-colors" />
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500 group-focus-within:text-emerald-400 transition-colors" />
<input
{...register('email')}
className="w-full bg-slate-50/50 border border-slate-200 rounded-xl py-3 pl-4 pr-11 focus:ring-4 focus:ring-primary-500/10 focus:border-primary-500 outline-none transition-all"
className="w-full bg-slate-800/50 border border-slate-700/50 rounded-xl py-3 pl-4 pr-11 focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/50 outline-none transition-all text-white placeholder-slate-500"
placeholder="name@company.com"
/>
</div>
{errors.email && <p className="text-red-500 text-[12px] mt-1 mr-1">{errors.email.message}</p>}
{errors.email && <p className="text-red-400 text-[12px] mt-1 mr-1">{errors.email.message}</p>}
</div>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2 mr-1">كلمة المرور</label>
<label className="block text-sm font-semibold text-slate-300 mb-2 mr-1">كلمة المرور</label>
<div className="relative group">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400 group-focus-within:text-primary-500 transition-colors" />
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500 group-focus-within:text-emerald-400 transition-colors" />
<input
{...register('password')}
type="password"
className="w-full bg-slate-50/50 border border-slate-200 rounded-xl py-3 pl-4 pr-11 focus:ring-4 focus:ring-primary-500/10 focus:border-primary-500 outline-none transition-all"
className="w-full bg-slate-800/50 border border-slate-700/50 rounded-xl py-3 pl-4 pr-11 focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/50 outline-none transition-all text-white placeholder-slate-500"
placeholder="••••••••"
/>
</div>
{errors.password && <p className="text-red-500 text-[12px] mt-1 mr-1">{errors.password.message}</p>}
{errors.password && <p className="text-red-400 text-[12px] mt-1 mr-1">{errors.password.message}</p>}
</div>
<button
type="submit"
disabled={isLoading}
className="w-full btn-primary h-14 text-lg mt-4 flex items-center justify-center gap-3 shadow-lg shadow-primary-500/25"
className="w-full bg-gradient-to-r from-emerald-500 to-emerald-600 hover:from-emerald-600 hover:to-emerald-700 text-white h-14 text-lg rounded-xl font-bold mt-4 flex items-center justify-center gap-3 shadow-lg shadow-emerald-500/20 transition-all disabled:opacity-50"
>
{isLoading ? <Loader2 className="w-6 h-6 animate-spin" /> : 'تسجيل الدخول'}
</button>
</form>
<div className="mt-8 text-center text-slate-500 text-sm">
<div className="mt-8 text-center text-slate-400 text-sm">
ليس لديك حساب؟{' '}
<Link to="/register" className="text-primary-600 font-bold hover:underline">
<Link to="/register" className="text-emerald-400 font-bold hover:underline">
أنشئ حساباً جديداً
</Link>
</div>
{/* Trojan Horse Marketing Link */}
<div className="mt-6 pt-6 border-t border-slate-100 text-center">
<Link
to="/free-converter"
className="inline-flex items-center justify-center gap-2 px-6 py-3 bg-emerald-50 text-emerald-600 rounded-xl font-bold hover:bg-emerald-100 transition-colors w-full border border-emerald-200 shadow-sm"
<div className="mt-6 pt-6 border-t border-slate-800/60 text-center">
<Link
to="/free-converter"
className="inline-flex items-center justify-center gap-2 px-6 py-3 bg-emerald-500/10 text-emerald-400 rounded-xl font-bold hover:bg-emerald-500/20 transition-colors w-full border border-emerald-500/20"
>
🚀 جرب أداة تحويل فواتير PDF إلى XML مجاناً
<Zap className="w-4 h-4" />
جرب أداة تحويل فواتير PDF إلى XML مجاناً
</Link>
</div>
</motion.div>

View File

@@ -1,6 +1,6 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Premium Register Page
* مُصادَق (Musadaq) — Premium Dark Register Page
* ════════════════════════════════════════════════════════════
*/
@@ -51,32 +51,32 @@ export default function RegisterPage() {
};
return (
<div className="min-h-screen bg-slate-50 flex items-center justify-center p-6 relative overflow-hidden rtl font-sans">
<div className="min-h-screen bg-slate-950 flex items-center justify-center p-6 relative overflow-hidden rtl font-sans">
{/* ── Background Aesthetics ────────────────────────────────── */}
<div className="absolute top-0 left-0 w-full h-full opacity-10 pointer-events-none">
<div className="absolute top-[-20%] right-[-10%] w-[600px] h-[600px] bg-primary-300 rounded-full blur-[120px]" />
<div className="absolute bottom-[-20%] left-[-10%] w-[600px] h-[600px] bg-blue-300 rounded-full blur-[120px]" />
<div className="absolute top-0 left-0 w-full h-full pointer-events-none">
<div className="absolute top-[-20%] right-[-10%] w-[600px] h-[600px] bg-emerald-500/5 rounded-full blur-[150px]" />
<div className="absolute bottom-[-20%] left-[-10%] w-[600px] h-[600px] bg-blue-500/5 rounded-full blur-[150px]" />
</div>
<motion.div
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="w-full max-w-lg glass p-10 rounded-3xl shadow-2xl relative z-10"
className="w-full max-w-lg bg-slate-900/50 backdrop-blur-xl border border-slate-800/60 p-10 rounded-3xl shadow-2xl relative z-10"
>
<div className="text-center mb-8">
<div className="w-16 h-16 bg-primary-600 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-xl shadow-primary-500/20">
<div className="w-16 h-16 bg-gradient-to-br from-emerald-500 to-emerald-600 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-xl shadow-emerald-500/20">
<UserPlus className="text-white w-8 h-8" />
</div>
<h1 className="text-3xl font-bold text-slate-900 mb-2">إنشاء حساب جديد</h1>
<p className="text-slate-500">ابدأ رحلتك في أتمتة الفواتير الضريبية</p>
<h1 className="text-3xl font-bold text-white mb-2">إنشاء حساب جديد</h1>
<p className="text-slate-400">ابدأ رحلتك في أتمتة الفواتير الضريبية</p>
</div>
{/* ── Progress Indicator ─────────────────────────────────── */}
<div className="flex gap-2 mb-8 items-center justify-center">
{[1, 2].map((i) => (
<div
<div
key={i}
className={`h-1.5 rounded-full transition-all duration-300 ${step >= i ? 'w-12 bg-primary-600' : 'w-4 bg-slate-200'}`}
className={`h-1.5 rounded-full transition-all duration-300 ${step >= i ? 'w-12 bg-emerald-500' : 'w-4 bg-slate-700'}`}
/>
))}
</div>
@@ -84,7 +84,7 @@ export default function RegisterPage() {
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<AnimatePresence mode="wait">
{step === 1 ? (
<motion.div
<motion.div
key="step1"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
@@ -92,25 +92,25 @@ export default function RegisterPage() {
className="space-y-6"
>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2 mr-1">اسم مكتب المحاسبة</label>
<label className="block text-sm font-semibold text-slate-300 mb-2 mr-1">اسم مكتب المحاسبة</label>
<div className="relative group">
<Building className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400 group-focus-within:text-primary-500 transition-colors" />
<Building className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500 group-focus-within:text-emerald-400 transition-colors" />
<input
{...register('tenantName')}
className="w-full bg-slate-50/50 border border-slate-200 rounded-xl py-3 pl-4 pr-11 focus:ring-4 focus:ring-primary-500/10 focus:border-primary-500 outline-none transition-all"
className="w-full bg-slate-800/50 border border-slate-700/50 rounded-xl py-3 pl-4 pr-11 focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/50 outline-none transition-all text-white placeholder-slate-500"
placeholder="شركة الفوترة للمحاسبة"
/>
</div>
{errors.tenantName && <p className="text-red-500 text-[12px] mt-1 mr-1">{errors.tenantName.message}</p>}
{errors.tenantName && <p className="text-red-400 text-[12px] mt-1 mr-1">{errors.tenantName.message}</p>}
</div>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2 mr-1">رقم الهاتف (اختياري)</label>
<label className="block text-sm font-semibold text-slate-300 mb-2 mr-1">رقم الهاتف (اختياري)</label>
<div className="relative group">
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400 group-focus-within:text-primary-500 transition-colors" />
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500 group-focus-within:text-emerald-400 transition-colors" />
<input
{...register('phone')}
className="w-full bg-slate-50/50 border border-slate-200 rounded-xl py-3 pl-4 pr-11 focus:ring-4 focus:ring-primary-500/10 focus:border-primary-500 outline-none transition-all"
className="w-full bg-slate-800/50 border border-slate-700/50 rounded-xl py-3 pl-4 pr-11 focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/50 outline-none transition-all text-white placeholder-slate-500"
placeholder="079 XXXXXXX"
/>
</div>
@@ -119,14 +119,14 @@ export default function RegisterPage() {
<button
type="button"
onClick={nextStep}
className="w-full btn-primary h-14 text-lg flex items-center justify-center gap-3"
className="w-full bg-gradient-to-r from-emerald-500 to-emerald-600 hover:from-emerald-600 hover:to-emerald-700 text-white h-14 text-lg rounded-xl font-bold flex items-center justify-center gap-3 shadow-lg shadow-emerald-500/20 transition-all"
>
التالي
<ArrowLeft className="w-5 h-5" />
</button>
</motion.div>
) : (
<motion.div
<motion.div
key="step2"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
@@ -134,49 +134,49 @@ export default function RegisterPage() {
className="space-y-6"
>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2 mr-1">الاسم الكامل (للمدير)</label>
<label className="block text-sm font-semibold text-slate-300 mb-2 mr-1">الاسم الكامل (للمدير)</label>
<div className="relative group">
<input
{...register('name')}
className="w-full bg-slate-50/50 border border-slate-200 rounded-xl py-3 px-4 focus:ring-4 focus:ring-primary-500/10 focus:border-primary-500 outline-none transition-all"
className="w-full bg-slate-800/50 border border-slate-700/50 rounded-xl py-3 px-4 focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/50 outline-none transition-all text-white placeholder-slate-500"
placeholder="أحمد محمد"
/>
</div>
{errors.name && <p className="text-red-500 text-[12px] mt-1 mr-1">{errors.name.message}</p>}
{errors.name && <p className="text-red-400 text-[12px] mt-1 mr-1">{errors.name.message}</p>}
</div>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2 mr-1">البريد الإلكتروني</label>
<label className="block text-sm font-semibold text-slate-300 mb-2 mr-1">البريد الإلكتروني</label>
<div className="relative group">
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400 group-focus-within:text-primary-500 transition-colors" />
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500 group-focus-within:text-emerald-400 transition-colors" />
<input
{...register('email')}
className="w-full bg-slate-50/50 border border-slate-200 rounded-xl py-3 pl-4 pr-11 focus:ring-4 focus:ring-primary-500/10 focus:border-primary-500 outline-none transition-all"
className="w-full bg-slate-800/50 border border-slate-700/50 rounded-xl py-3 pl-4 pr-11 focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/50 outline-none transition-all text-white placeholder-slate-500"
placeholder="admin@office.com"
/>
</div>
{errors.email && <p className="text-red-500 text-[12px] mt-1 mr-1">{errors.email.message}</p>}
{errors.email && <p className="text-red-400 text-[12px] mt-1 mr-1">{errors.email.message}</p>}
</div>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-2 mr-1">كلمة المرور</label>
<label className="block text-sm font-semibold text-slate-300 mb-2 mr-1">كلمة المرور</label>
<div className="relative group">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400 group-focus-within:text-primary-500 transition-colors" />
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500 group-focus-within:text-emerald-400 transition-colors" />
<input
{...register('password')}
type="password"
className="w-full bg-slate-50/50 border border-slate-200 rounded-xl py-3 pl-4 pr-11 focus:ring-4 focus:ring-primary-500/10 focus:border-primary-500 outline-none transition-all"
className="w-full bg-slate-800/50 border border-slate-700/50 rounded-xl py-3 pl-4 pr-11 focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/50 outline-none transition-all text-white placeholder-slate-500"
placeholder="••••••••"
/>
</div>
{errors.password && <p className="text-red-500 text-[12px] mt-1 mr-1">{errors.password.message}</p>}
{errors.password && <p className="text-red-400 text-[12px] mt-1 mr-1">{errors.password.message}</p>}
</div>
<div className="flex gap-4">
<button
type="button"
onClick={() => setStep(1)}
className="flex-1 bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold py-4 rounded-xl transition-all flex items-center justify-center gap-2"
className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 font-bold py-4 rounded-xl transition-all flex items-center justify-center gap-2 border border-slate-700/50"
>
<ArrowRight className="w-5 h-5" />
السابق
@@ -184,7 +184,7 @@ export default function RegisterPage() {
<button
type="submit"
disabled={isLoading}
className="flex-[2] btn-primary py-4 flex items-center justify-center gap-2"
className="flex-[2] bg-gradient-to-r from-emerald-500 to-emerald-600 hover:from-emerald-600 hover:to-emerald-700 text-white py-4 rounded-xl font-bold flex items-center justify-center gap-2 shadow-lg shadow-emerald-500/20 transition-all disabled:opacity-50"
>
{isLoading ? <Loader2 className="w-6 h-6 animate-spin" /> : 'إنشاء الحساب'}
</button>
@@ -194,9 +194,9 @@ export default function RegisterPage() {
</AnimatePresence>
</form>
<div className="mt-8 text-center text-slate-500 text-sm">
<div className="mt-8 text-center text-slate-400 text-sm">
لديك حساب بالفعل؟{' '}
<Link to="/login" className="text-primary-600 font-bold hover:underline">
<Link to="/login" className="text-emerald-400 font-bold hover:underline">
سجل دخولك من هنا
</Link>
</div>

View File

@@ -1,22 +1,23 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Dashboard Page
* مُصادَق (Musadaq) — Premium Dark Dashboard Page
* ════════════════════════════════════════════════════════════
*/
import { useState, useEffect } from 'react';
import {
FileText,
TrendingUp,
ArrowUpRight,
ArrowDownRight,
import {
FileText,
TrendingUp,
ArrowUpRight,
Clock,
CheckCircle2,
Building2
Building2,
Loader2,
Upload,
AlertTriangle,
} from 'lucide-react';
import { motion } from 'framer-motion';
import apiClient from '../../api/client';
import { useNavigate } from 'react-router-dom';
export const DashboardPage = () => {
@@ -41,44 +42,41 @@ export const DashboardPage = () => {
if (isLoading) {
return (
<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 className="flex-1 flex flex-col justify-center items-center py-32">
<Loader2 className="w-12 h-12 text-emerald-400 animate-spin mb-4" />
<p className="text-slate-400">جاري تحميل البيانات...</p>
</div>
);
}
const statCards = [
{
title: 'إجمالي الفواتير',
value: stats?.totalInvoices || 0,
icon: FileText,
color: 'bg-blue-500',
trend: '+12%',
isUp: true
{
title: 'إجمالي الفواتير',
value: stats?.stats?.totalInvoices || 0,
icon: FileText,
gradient: 'from-blue-500 to-blue-600',
glow: 'shadow-blue-500/20',
},
{
title: 'الفواتير المصدقة',
value: stats?.approvedInvoices || 0,
icon: CheckCircle2,
color: 'bg-emerald-500',
trend: '+8%',
isUp: true
{
title: 'الفواتير المصدقة',
value: stats?.stats?.approvedInvoices || 0,
icon: CheckCircle2,
gradient: 'from-emerald-500 to-emerald-600',
glow: 'shadow-emerald-500/20',
},
{
title: 'إجمالي الشركات',
value: stats?.companiesCount || 0,
icon: Building2,
color: 'bg-purple-500',
trend: '+2',
isUp: true
{
title: 'إجمالي الشركات',
value: stats?.stats?.companiesCount || 0,
icon: Building2,
gradient: 'from-purple-500 to-purple-600',
glow: 'shadow-purple-500/20',
},
{
title: 'إجمالي الضرائب (JOD)',
value: Number(stats?.totalTax || 0).toLocaleString('ar-JO', { minimumFractionDigits: 3 }),
icon: TrendingUp,
color: 'bg-amber-500',
trend: '+5%',
isUp: true
{
title: 'إجمالي الضرائب (JOD)',
value: Number(stats?.stats?.totalTax || 0).toLocaleString('ar-JO', { minimumFractionDigits: 3 }),
icon: TrendingUp,
gradient: 'from-amber-500 to-amber-600',
glow: 'shadow-amber-500/20',
},
];
@@ -86,11 +84,11 @@ export const DashboardPage = () => {
<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>
<h2 className="text-3xl font-black text-white tracking-tight">لوحة التحكم</h2>
<p className="text-slate-400 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">
<button className="bg-slate-800/60 border border-slate-700/50 px-5 py-2.5 rounded-xl text-slate-300 font-bold text-sm hover:bg-slate-800 transition-all">
آخر 30 يوم
</button>
</div>
@@ -99,24 +97,28 @@ export const DashboardPage = () => {
{/* ── Stats Grid ────────────────────────────────────────── */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{statCards.map((card, idx) => (
<motion.div
<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"
className="bg-slate-900/50 backdrop-blur-xl border border-slate-800/60 p-6 rounded-2xl group hover:border-slate-700 transition-all relative overflow-hidden"
>
<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" />
{/* Subtle ambient glow on hover */}
<div className={`absolute -inset-10 opacity-0 group-hover:opacity-10 blur-3xl transition-opacity duration-500 bg-gradient-to-r ${card.gradient}`} />
<div className="relative z-10">
<div className="flex justify-between items-start mb-4">
<div className={`w-12 h-12 bg-gradient-to-br ${card.gradient} rounded-2xl flex items-center justify-center text-white shadow-lg ${card.glow}`}>
<card.icon className="w-6 h-6" />
</div>
<span className="flex items-center gap-1 text-xs font-bold text-emerald-400 bg-emerald-500/10 px-2 py-1 rounded-lg border border-emerald-500/20">
<ArrowUpRight className="w-3 h-3" />
</span>
</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>
<h3 className="text-slate-400 font-bold text-sm mb-1">{card.title}</h3>
<div className="text-2xl font-black text-white tracking-tight">{card.value}</div>
</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>
@@ -125,39 +127,49 @@ export const DashboardPage = () => {
{/* ── 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 className="text-xl font-bold text-white flex items-center gap-2">
<Clock className="w-5 h-5 text-emerald-400" />
آخر الفواتير المرفوعة
</h3>
<button className="text-primary-600 text-sm font-bold hover:underline">عرض الكل</button>
<button
onClick={() => navigate('/invoices')}
className="text-emerald-400 text-sm font-bold hover:underline"
>
عرض الكل
</button>
</div>
<div className="card-premium overflow-hidden bg-white border border-slate-100">
<div className="bg-slate-900/50 backdrop-blur-xl border border-slate-800/60 rounded-2xl overflow-hidden">
{(!stats?.recentActivities || stats.recentActivities.length === 0) ? (
<div className="p-12 text-center text-slate-400 font-medium">
<div className="p-12 text-center text-slate-500 font-medium">
لا توجد نشاطات حديثة بعد.
</div>
) : (
<table className="w-full text-right">
<thead className="bg-slate-50 border-b border-slate-100">
<thead className="bg-slate-800/50 border-b border-slate-700/50">
<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>
<th className="px-6 py-4 text-xs font-bold text-slate-400">الفاتورة</th>
<th className="px-6 py-4 text-xs font-bold text-slate-400">الشركة</th>
<th className="px-6 py-4 text-xs font-bold text-slate-400">الحالة</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
<tbody className="divide-y divide-slate-800/50">
{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>
<tr key={inv.id} className="hover:bg-slate-800/30 transition-colors">
<td className="px-6 py-4 font-bold text-white">{inv.number || 'قيد الاستخراج...'}</td>
<td className="px-6 py-4 text-slate-400">{inv.company || '—'}</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'
<span className={`px-2.5 py-1 rounded-md text-[10px] font-bold ${
inv.status === 'approved'
? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'
: inv.status === 'validation_failed' || inv.status === 'rejected'
? 'bg-red-500/10 text-red-400 border border-red-500/20'
: 'bg-amber-500/10 text-amber-400 border border-amber-500/20'
}`}>
{inv.status === 'approved' ? 'مصدقة' : 'قيد المعالجة'}
{inv.status === 'approved' ? 'مصدقة' :
inv.status === 'validation_failed' ? 'مرفوضة' :
inv.status === 'rejected' ? 'مرفوضة' :
'قيد المعالجة'}
</span>
</td>
</tr>
@@ -170,33 +182,46 @@ export const DashboardPage = () => {
{/* ── Quick Actions ────────────────────────────────────── */}
<div className="space-y-4">
<h3 className="text-xl font-bold text-slate-900 px-2">إجراءات سريعة</h3>
<h3 className="text-xl font-bold text-white px-2">إجراءات سريعة</h3>
<div className="grid grid-cols-1 gap-4">
<button
<button
onClick={() => navigate('/invoices')}
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"
className="flex items-center gap-4 p-4 rounded-2xl bg-gradient-to-r from-emerald-500 to-emerald-600 text-white shadow-xl shadow-emerald-500/20 hover:shadow-emerald-500/30 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" />
<Upload 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
<button
onClick={() => navigate('/companies')}
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"
className="flex items-center gap-4 p-4 rounded-2xl bg-slate-900/50 border border-slate-800/60 text-slate-300 hover:border-emerald-500/30 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">
<div className="w-10 h-10 rounded-xl bg-slate-800 flex items-center justify-center group-hover:bg-emerald-500/10 group-hover:text-emerald-400 transition-all">
<Building2 className="w-5 h-5" />
</div>
<div className="text-right">
<div className="font-bold">إضافة شركة</div>
<div className="font-bold text-white">إضافة شركة</div>
<div className="text-xs text-slate-500">تسجيل عميل جديد في المكتب</div>
</div>
</button>
<button
onClick={() => navigate('/elite-dashboard')}
className="flex items-center gap-4 p-4 rounded-2xl bg-slate-900/50 border border-slate-800/60 text-slate-300 hover:border-emerald-500/30 transition-all group"
>
<div className="w-10 h-10 rounded-xl bg-slate-800 flex items-center justify-center group-hover:bg-amber-500/10 group-hover:text-amber-400 transition-all">
<AlertTriangle className="w-5 h-5" />
</div>
<div className="text-right">
<div className="font-bold text-white">مراقبة المخاطر</div>
<div className="text-xs text-slate-500">لوحة النخبة ودرجات الخطر الضريبي</div>
</div>
</button>
</div>
</div>
</div>

View File

@@ -1,22 +1,38 @@
import { Building2, TrendingUp, AlertTriangle, ChevronDown } from 'lucide-react';
import { useState, useEffect } from 'react';
import { Building2, TrendingUp, AlertTriangle, ChevronDown, Loader2, RefreshCw, Crown } from 'lucide-react';
import { motion } from 'framer-motion';
import apiClient from '../../api/client';
// Mock data matching the API structure we built in the backend
const mockCompanies = [
{ id: '1', name: 'Apex Innovations', totalInvoices: 24, pendingAmount: 78450, totalTax: 12500, failedCount: 2, riskScore: 85, status: 'High' },
{ id: '2', name: 'Quantum Solutions', totalInvoices: 26, pendingAmount: 112000, totalTax: 18000, failedCount: 0, riskScore: 32, status: 'Low' },
{ id: '3', name: 'Nomad Ventures', totalInvoices: 45, pendingAmount: 319000, totalTax: 45000, failedCount: 5, riskScore: 68, status: 'Medium' },
{ id: '4', name: 'Elevate Tech', totalInvoices: 12, pendingAmount: 188000, totalTax: 30000, failedCount: 1, riskScore: 85, status: 'High' },
{ id: '5', name: 'Horizon Group', totalInvoices: 33, pendingAmount: 95000, totalTax: 14000, failedCount: 3, riskScore: 68, status: 'Medium' },
];
interface CompanyStats {
id: string;
name: string;
taxId: string;
totalInvoices: number;
totalTax: number;
failedCount: number;
riskScore: number;
}
const RiskGauge = ({ score, status }: { score: number, status: string }) => {
const getRiskStatus = (score: number) => {
if (score >= 70) return 'High';
if (score >= 30) return 'Medium';
return 'Low';
};
const RiskGauge = ({ score }: { score: number }) => {
const status = getRiskStatus(score);
const getColor = () => {
if (status === 'High') return 'text-red-500';
if (status === 'Medium') return 'text-orange-500';
return 'text-emerald-500';
};
const getLabel = () => {
if (status === 'High') return 'مرتفع';
if (status === 'Medium') return 'متوسط';
return 'منخفض';
};
return (
<div className="flex flex-col items-center">
<div className="relative w-16 h-16">
@@ -41,104 +57,170 @@ const RiskGauge = ({ score, status }: { score: number, status: string }) => {
{score}
</div>
</div>
<span className={`text-xs mt-1 ${getColor()}`}>{status}</span>
<span className={`text-xs mt-1 ${getColor()}`}>{getLabel()}</span>
</div>
);
};
export const MultiEntityDashboard = () => {
const [companies, setCompanies] = useState<CompanyStats[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
setIsLoading(true);
setError(null);
try {
const { data } = await apiClient.get('/dashboard/multi-entity');
setCompanies(data);
} catch (err: any) {
console.error('Failed to fetch multi-entity stats', err);
setError('فشل في جلب بيانات الشركات. تأكد من تسجيل الدخول.');
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
if (isLoading) {
return (
<div className="flex-1 flex flex-col justify-center items-center py-32">
<Loader2 className="w-12 h-12 text-emerald-400 animate-spin mb-4" />
<p className="text-slate-400">جاري تحميل بيانات الشركات...</p>
</div>
);
}
return (
<div className="min-h-screen bg-slate-950 text-slate-300 font-sans p-8 selection:bg-emerald-500/30">
<div className="space-y-8 animate-in fade-in duration-700">
{/* Header */}
<header className="flex justify-between items-center mb-10">
<header className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-light tracking-tight text-white flex items-center gap-3">
<span className="font-semibold text-emerald-400">مُصادَق</span> | لوحة تحكم الشركات
</h1>
<p className="text-slate-400 mt-2 text-sm">نظرة عامة على الموقف الضريبي لجميع عملائك (Elite View)</p>
<h2 className="text-3xl font-light tracking-tight text-slate-900 dark:text-white flex items-center gap-3">
<Crown className="w-7 h-7 text-emerald-500" />
<span className="font-semibold text-emerald-500">مُصادَق</span> | لوحة تحكم الشركات
</h2>
<p className="text-slate-500 mt-2 text-sm">نظرة عامة على الموقف الضريبي لجميع عملائك (Elite View)</p>
</div>
<div className="flex gap-4">
<button className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-white rounded-lg text-sm border border-slate-700 transition-colors flex items-center gap-2">
آخر 30 يوم <ChevronDown className="w-4 h-4" />
<button
onClick={fetchData}
className="px-4 py-2 bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 text-slate-700 dark:text-white rounded-lg text-sm border border-slate-200 dark:border-slate-700 transition-colors flex items-center gap-2"
>
<RefreshCw className="w-4 h-4" /> تحديث
</button>
<button className="px-5 py-2 bg-emerald-500 hover:bg-emerald-600 text-slate-950 font-medium rounded-lg text-sm shadow-[0_0_15px_rgba(16,185,129,0.3)] transition-all">
+ إضافة شركة جديدة
<button className="px-4 py-2 bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 text-slate-700 dark:text-white rounded-lg text-sm border border-slate-200 dark:border-slate-700 transition-colors flex items-center gap-2">
آخر 30 يوم <ChevronDown className="w-4 h-4" />
</button>
</div>
</header>
{/* Error State */}
{error && (
<div className="bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/20 rounded-xl p-4 flex items-center gap-3 text-red-600 dark:text-red-400">
<AlertTriangle className="w-5 h-5 flex-shrink-0" />
<span className="text-sm font-medium">{error}</span>
<button onClick={fetchData} className="mr-auto text-sm underline hover:no-underline">إعادة المحاولة</button>
</div>
)}
{/* Empty State */}
{!error && companies.length === 0 && (
<div className="text-center py-20">
<Building2 className="w-16 h-16 text-slate-300 dark:text-slate-600 mx-auto mb-6" />
<h3 className="text-xl font-bold text-slate-900 dark:text-white mb-2">لا توجد شركات بعد</h3>
<p className="text-slate-500 mb-6">أضف شركات عملائك لتبدأ بمتابعة الموقف الضريبي لكل شركة.</p>
<button className="px-5 py-2.5 bg-emerald-500 hover:bg-emerald-600 text-white font-medium rounded-lg text-sm shadow-lg shadow-emerald-500/20 transition-all">
+ إضافة شركة جديدة
</button>
</div>
)}
{/* Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{mockCompanies.map((company, index) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
key={company.id}
className="bg-slate-900/50 backdrop-blur-xl border border-slate-800 rounded-2xl p-6 relative overflow-hidden hover:border-slate-700 transition-colors group"
>
{/* Ambient glow */}
<div className={`absolute -inset-20 opacity-0 group-hover:opacity-20 blur-3xl transition-opacity duration-500 rounded-full
${company.status === 'High' ? 'bg-red-500' : company.status === 'Medium' ? 'bg-orange-500' : 'bg-emerald-500'}
`} />
{companies.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{companies.map((company, index) => {
const status = getRiskStatus(company.riskScore);
const approvedEstimate = Math.max(0, company.totalInvoices - company.failedCount);
const approvedPct = company.totalInvoices > 0 ? Math.round((approvedEstimate / company.totalInvoices) * 100) : 0;
const failedPct = company.totalInvoices > 0 ? Math.round((company.failedCount / company.totalInvoices) * 100) : 0;
<div className="relative z-10">
<div className="flex justify-between items-start mb-6">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-slate-800 flex items-center justify-center border border-slate-700">
<Building2 className="w-5 h-5 text-emerald-400" />
</div>
<div>
<h3 className="text-white font-medium text-lg">{company.name}</h3>
<p className="text-xs text-slate-500">الرقم الضريبي: 00{company.id}829471</p>
</div>
</div>
</div>
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.08 }}
key={company.id}
className="card-premium p-6 relative overflow-hidden group"
>
{/* Ambient glow */}
<div className={`absolute -inset-20 opacity-0 group-hover:opacity-10 blur-3xl transition-opacity duration-500 rounded-full
${status === 'High' ? 'bg-red-500' : status === 'Medium' ? 'bg-orange-500' : 'bg-emerald-500'}
`} />
<div className="grid grid-cols-2 gap-4 mb-6">
<div>
<p className="text-xs text-slate-400 mb-1">التدفق النقدي (المبيعات)</p>
<p className="text-2xl font-light text-white">${(company.pendingAmount / 1000).toFixed(1)}k</p>
<div className="flex items-center gap-1 mt-1 text-xs text-emerald-400">
<TrendingUp className="w-3 h-3" /> مستقر
<div className="relative z-10">
<div className="flex justify-between items-start mb-6">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center border border-slate-200 dark:border-slate-700">
<Building2 className="w-5 h-5 text-emerald-500" />
</div>
<div>
<h3 className="text-slate-900 dark:text-white font-medium text-lg">{company.name}</h3>
<p className="text-xs text-slate-400">{company.taxId || '—'}</p>
</div>
</div>
</div>
</div>
<div className="flex justify-end">
<div>
<p className="text-xs text-slate-400 mb-1 text-center">درجة الخطر الضريبي</p>
<RiskGauge score={company.riskScore} status={company.status} />
</div>
</div>
</div>
<div className="pt-4 border-t border-slate-800/50">
<div className="flex justify-between items-center mb-2">
<p className="text-sm text-slate-300">الفواتير المعلقة</p>
<span className="text-xs text-slate-500">{company.totalInvoices} فاتورة | ${company.totalTax} ضرائب</span>
<div className="grid grid-cols-2 gap-4 mb-6">
<div>
<p className="text-xs text-slate-500 mb-1">إجمالي الضريبة</p>
<p className="text-2xl font-light text-slate-900 dark:text-white">
{company.totalTax > 0 ? `${(company.totalTax / 1000).toFixed(1)}k` : '0'}
</p>
<div className="flex items-center gap-1 mt-1 text-xs text-emerald-500">
<TrendingUp className="w-3 h-3" /> JOD
</div>
</div>
<div className="flex justify-end">
<div>
<p className="text-xs text-slate-500 mb-1 text-center">درجة الخطر الضريبي</p>
<RiskGauge score={company.riskScore} />
</div>
</div>
</div>
<div className="pt-4 border-t border-slate-100 dark:border-slate-800/50">
<div className="flex justify-between items-center mb-2">
<p className="text-sm text-slate-600 dark:text-slate-300">{company.totalInvoices} فاتورة</p>
{company.failedCount > 0 && (
<span className="text-xs text-red-400 flex items-center gap-1">
<AlertTriangle className="w-3 h-3" /> {company.failedCount} مرفوضة
</span>
)}
</div>
{/* Progress Bar */}
<div className="h-1.5 w-full bg-slate-100 dark:bg-slate-800 rounded-full overflow-hidden flex">
<div className="h-full bg-emerald-500 transition-all" style={{ width: `${approvedPct}%` }}></div>
<div className="h-full bg-red-500 transition-all" style={{ width: `${failedPct}%` }}></div>
</div>
<div className="flex justify-between mt-2 text-[10px] text-slate-400">
<span>ناجحة ({approvedPct}%)</span>
{company.failedCount > 0 && <span className="text-red-400">مرفوضة ({failedPct}%)</span>}
</div>
</div>
</div>
{/* Minimal Progress Bar */}
<div className="h-1.5 w-full bg-slate-800 rounded-full overflow-hidden flex">
<div className="h-full bg-emerald-500" style={{ width: '60%' }}></div>
<div className="h-full bg-orange-500" style={{ width: '25%' }}></div>
<div className="h-full bg-red-500" style={{ width: '15%' }}></div>
</div>
<div className="flex justify-between mt-2 text-[10px] text-slate-500">
<span>تم الرفع (60%)</span>
{company.failedCount > 0 && (
<span className="text-red-400 flex items-center gap-1">
<AlertTriangle className="w-3 h-3" /> {company.failedCount} مرفوضة
</span>
)}
</div>
</div>
</div>
</motion.div>
))}
</div>
</motion.div>
);
})}
</div>
)}
</div>
);
};