diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 6f25b3d..ccf9259 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -18,7 +18,7 @@ import { useAuthStore } from '../../store/authStore'; const menuItems = [ { icon: LayoutDashboard, label: 'الرئيسية', path: '/dashboard' }, - { icon: Crown, label: 'لوحة النخبة', path: '/elite-dashboard' }, + { icon: Crown, label: 'المركز الضريبي الموحد', path: '/elite-dashboard' }, { icon: FileText, label: 'الفواتير', path: '/invoices' }, { icon: Building2, label: 'الشركات', path: '/companies' }, { icon: Users, label: 'الموظفون', path: '/staff' }, diff --git a/frontend/src/pages/companies/CompaniesPage.tsx b/frontend/src/pages/companies/CompaniesPage.tsx index 81c4d96..874ee47 100644 --- a/frontend/src/pages/companies/CompaniesPage.tsx +++ b/frontend/src/pages/companies/CompaniesPage.tsx @@ -1,11 +1,12 @@ /** * ════════════════════════════════════════════════════════════ - * مُصادَق (Musadaq) — Companies Management Page + * مُصادَق (Musadaq) — Companies Management Page (Premium Dark) * ════════════════════════════════════════════════════════════ */ import { useState, useEffect } from 'react'; -import { Building2, Plus, Search, MoreVertical, ShieldCheck, Key } from 'lucide-react'; +import { Building2, Plus, Search, MoreVertical, ShieldCheck, Key, Loader2, X, MapPin, Hash } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; import apiClient from '../../api/client'; export const CompaniesPage = () => { @@ -20,10 +21,12 @@ export const CompaniesPage = () => { const [name, setName] = useState(''); const [tin, setTin] = useState(''); const [address, setAddress] = useState(''); + const [isCreating, setIsCreating] = useState(false); // Form State (JoFotara) const [clientId, setClientId] = useState(''); const [secretKey, setSecretKey] = useState(''); + const [isLinking, setIsLinking] = useState(false); const fetchCompanies = async () => { try { @@ -42,6 +45,7 @@ export const CompaniesPage = () => { const handleCreateCompany = async (e: React.FormEvent) => { e.preventDefault(); + setIsCreating(true); try { await apiClient.post('/companies', { name, @@ -56,18 +60,21 @@ export const CompaniesPage = () => { } catch (error) { console.error('Failed to create company', error); alert('حدث خطأ أثناء إضافة الشركة'); + } finally { + setIsCreating(false); } }; const handleOpenJoFotara = (company: any) => { setSelectedCompany(company); - setClientId(''); // We don't fetch existing keys for security, user has to enter new ones if they want to update + setClientId(''); setSecretKey(''); setIsJoFotaraModalOpen(true); }; const handleSubmitJoFotara = async (e: React.FormEvent) => { e.preventDefault(); + setIsLinking(true); try { await apiClient.put(`/companies/${selectedCompany.id}/jofotara`, { clientId, @@ -75,27 +82,30 @@ export const CompaniesPage = () => { }); setIsJoFotaraModalOpen(false); setSelectedCompany(null); - fetchCompanies(); // Refresh to show "Linked" status + fetchCompanies(); } catch (error) { console.error('Failed to link JoFotara', error); alert('حدث خطأ أثناء ربط حساب جو فوترة'); + } finally { + setIsLinking(false); } }; const filteredCompanies = companies.filter(c => - c.name.includes(searchTerm) || c.tax_identification_number?.includes(searchTerm) + c.name.toLowerCase().includes(searchTerm.toLowerCase()) || + c.tax_identification_number?.includes(searchTerm) ); return ( -
+
-

إدارة الشركات

-

أضف عملائك وشركاتك لربط فواتيرهم بنظام جو فوترة.

+

إدارة الشركات

+

أضف عملائك وشركاتك لربط فواتيرهم بنظام جو فوترة.

{/* ── Search Bar ──────────────────────────────── */} -
-
- - setSearchTerm(e.target.value)} - /> -
+
+ + setSearchTerm(e.target.value)} + />
{/* ── Companies Grid ───────────────────────────────────── */} {isLoading ? ( -
-
+
+ +

جاري تحميل الشركات...

) : filteredCompanies.length === 0 ? ( -
-
- -
-

لا توجد شركات مسجلة

+
+ +

لا توجد شركات مسجلة

ابدأ بإضافة أول شركة لكي تتمكن من رفع فواتيرها ومعالجتها ضريبياً.

-
) : (
- {filteredCompanies.map((company) => ( -
-
-
+ {filteredCompanies.map((company, idx) => ( + + {/* Ambient Glow */} +
+ +
+
-
-

{company.name}

-

الرقم الضريبي: {company.tax_identification_number || 'غير محدد'}

+ +

{company.name}

-
-
- {company.jofotara_client_id ? ( - - مربوط بجو فوترة - - ) : ( - - غير مربوط - - )} +
+
+ + الرقم الضريبي: {company.tax_identification_number || '---'}
+
+ + {company.address || 'العنوان غير محدد'} +
+
+ +
+ {company.jofotara_client_id ? ( +
+ مربوط بجو فوترة +
+ ) : ( +
+ غير مربوط +
+ )}
-
+ ))}
)} {/* ── Add Company Modal ───────────────────────────────── */} - {isAddModalOpen && ( -
-
-

إضافة شركة جديدة

-
-
- - setName(e.target.value)} - className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 transition-all" - placeholder="مثال: صيدلية النجاح" - /> -
-
- - setTin(e.target.value)} - className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 transition-all font-mono" - placeholder="مثال: 123456789" - /> -
-
- - setAddress(e.target.value)} - className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 transition-all" - placeholder="مثال: عمان، شارع مكة" - /> -
-
- - -
-
+ + {isAddModalOpen && ( +
+ + + +

إضافة شركة جديدة

+
+
+ + setName(e.target.value)} + className="w-full bg-slate-800 border border-slate-700 rounded-xl px-4 py-3 outline-none focus:border-emerald-500/50 transition-all text-white placeholder-slate-600" + placeholder="مثال: صيدلية النجاح" + /> +
+
+ + setTin(e.target.value)} + className="w-full bg-slate-800 border border-slate-700 rounded-xl px-4 py-3 outline-none focus:border-emerald-500/50 transition-all text-white font-mono placeholder-slate-600" + placeholder="123456789" + /> +
+
+ + setAddress(e.target.value)} + className="w-full bg-slate-800 border border-slate-700 rounded-xl px-4 py-3 outline-none focus:border-emerald-500/50 transition-all text-white placeholder-slate-600" + placeholder="عمان، شارع مكة" + /> +
+
+ + +
+
+
-
- )} + )} + {/* ── JoFotara Link Modal ───────────────────────────────── */} - {isJoFotaraModalOpen && ( -
-
-
-
- + + {isJoFotaraModalOpen && ( +
+ + + +
+
+ +
+
+

ربط "جو فوترة"

+

{selectedCompany?.name}

+
-
-

ربط نظام جو فوترة

-

{selectedCompany?.name}

-
-
-
-
- - setClientId(e.target.value)} - className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 transition-all font-mono text-sm" - placeholder="أدخل Client ID..." - /> -
-
- - setSecretKey(e.target.value)} - className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 transition-all font-mono text-sm" - placeholder="أدخل Secret Key..." - /> -
-
- - -
-
+ +
+
+ + setClientId(e.target.value)} + className="w-full bg-slate-800 border border-slate-700 rounded-xl px-4 py-3 outline-none focus:border-emerald-500/50 transition-all text-white font-mono text-sm placeholder-slate-600" + placeholder="أدخل المعرف الخاص بك..." + /> +
+
+ + setSecretKey(e.target.value)} + className="w-full bg-slate-800 border border-slate-700 rounded-xl px-4 py-3 outline-none focus:border-emerald-500/50 transition-all text-white font-mono text-sm placeholder-slate-600" + placeholder="أدخل مفتاح السر..." + /> +
+
+

+ سيتم تشفير هذه البيانات وتخزينها بأمان. يرجى التأكد من دقة البيانات لضمان نجاح عملية إرسال الفواتير. +

+
+
+ + +
+
+
-
- )} + )} +
); }; diff --git a/frontend/src/pages/invoices/InvoicesPage.tsx b/frontend/src/pages/invoices/InvoicesPage.tsx index ea96d4d..74ab8ad 100644 --- a/frontend/src/pages/invoices/InvoicesPage.tsx +++ b/frontend/src/pages/invoices/InvoicesPage.tsx @@ -1,6 +1,6 @@ /** * ════════════════════════════════════════════════════════════ - * مُصادَق (Musadaq) — Invoices Management Page + * مُصادَق (Musadaq) — Invoices Management Page (Premium Dark) * ════════════════════════════════════════════════════════════ */ @@ -21,7 +21,9 @@ import { FileText, Send, Download, - Trash2 + Trash2, + Loader2, + X } from 'lucide-react'; import apiClient from '../../api/client'; @@ -46,23 +48,12 @@ export const InvoicesPage = () => { const fetchData = async () => { setIsLoading(true); try { - // Fetch companies first so the dropdown always works - try { - const compRes = await apiClient.get('/companies'); - console.log('Fetched Companies:', compRes.data); - setCompanies(compRes.data); - } catch (err) { - console.error('Failed to fetch companies', err); - } - - // Fetch invoices separately - try { - const invRes = await apiClient.get('/invoices'); - console.log('Fetched Invoices:', invRes.data); - setInvoices(invRes.data); - } catch (err) { - console.error('Failed to fetch invoices', err); - } + const [compRes, invRes] = await Promise.all([ + apiClient.get('/companies').catch(() => ({ data: [] })), + apiClient.get('/invoices').catch(() => ({ data: [] })) + ]); + setCompanies(compRes.data); + setInvoices(invRes.data); } finally { setIsLoading(false); } @@ -110,10 +101,6 @@ export const InvoicesPage = () => { }; const handleSubmitToJoFotara = async (inv: any) => { - if (inv.status !== 'validated' && inv.status !== 'extracted') { - alert('يجب أن تكون الفاتورة مدققة أو مستخرجة أولاً'); - return; - } setSubmitLoading(inv.id); try { await apiClient.post(`/invoices/${inv.id}/submit`); @@ -134,30 +121,23 @@ export const InvoicesPage = () => { const filteredInvoices = invoices.filter(inv => inv.invoice_number?.includes(searchTerm) || - inv.company?.name?.includes(searchTerm) + inv.company?.name?.toLowerCase().includes(searchTerm.toLowerCase()) ); const StatusBadge = ({ invoice }: { invoice: any }) => { const status = invoice.status; const config: any = { - approved: { color: 'text-emerald-700 bg-emerald-50 border-emerald-100', icon: CheckCircle2, label: 'تم التصديق' }, - validated: { color: 'text-blue-700 bg-blue-50 border-blue-100', icon: CheckCircle2, label: 'جاهز للإرسال' }, - extracted: { color: 'text-indigo-700 bg-indigo-50 border-indigo-100', icon: CheckCircle2, label: 'تم الاستخراج' }, - uploaded: { color: 'text-amber-700 bg-amber-50 border-amber-100', icon: Clock, label: 'قيد المعالجة AI' }, - extracting: { color: 'text-amber-700 bg-amber-50 border-amber-100', icon: Clock, label: 'قيد الاستخراج' }, - validation_failed: { color: 'text-red-700 bg-red-50 border-red-100', icon: AlertCircle, label: 'خطأ في التحقق' }, + approved: { color: 'text-emerald-400 bg-emerald-500/10 border-emerald-500/20', icon: CheckCircle2, label: 'تم التصديق' }, + validated: { color: 'text-blue-400 bg-blue-500/10 border-blue-500/20', icon: CheckCircle2, label: 'جاهز للإرسال' }, + extracted: { color: 'text-indigo-400 bg-indigo-500/10 border-indigo-500/20', icon: CheckCircle2, label: 'تم الاستخراج' }, + uploaded: { color: 'text-amber-400 bg-amber-500/10 border-amber-500/20', icon: Clock, label: 'قيد المعالجة AI' }, + extracting: { color: 'text-amber-400 bg-amber-500/10 border-amber-500/20', icon: Clock, label: 'قيد الاستخراج' }, + validation_failed: { color: 'text-red-400 bg-red-500/10 border-red-500/20', icon: AlertCircle, label: 'خطأ في التحقق' }, }; - const { color, icon: Icon, label } = config[status] || { color: 'text-slate-500 bg-slate-50', icon: Clock, label: status }; + const { color, icon: Icon, label } = config[status] || { color: 'text-slate-400 bg-slate-800', icon: Clock, label: status }; - const errorTitle = status === 'validation_failed' && invoice.validation_errors - ? invoice.validation_errors.join('\n') - : undefined; - return ( - + {label} @@ -165,15 +145,15 @@ export const InvoicesPage = () => { }; return ( -
+
-

إدارة الفواتير

-

عرض، معالجة، وإرسال الفواتير الضريبية لبوابة الضريبة.

+

إدارة الفواتير

+

عرض، معالجة، وإرسال الفواتير الضريبية لبوابة الضريبة.

{/* ── Invoices Table ───────────────────────────────────── */} -
+
{isLoading ? ( -
-
+
+ +

جاري جلب الفواتير...

) : filteredInvoices.length === 0 ? (
-
- +
+
-

لا توجد فواتير بعد

+

لا توجد فواتير بعد

ابدأ برفع أول فاتورة ليقوم محرك الذكاء الاصطناعي باستخراج بياناتها ومصادقتها ضريبياً.

-
) : (
- - +
+ - - - - - - + + + + + + - + {filteredInvoices.map((inv, idx) => ( { + setViewingInvoice(inv); + setIsViewModalOpen(true); + }} > - - + - - @@ -325,13 +261,13 @@ export const InvoicesPage = () => { {/* ── Pagination ───────────────────────────────────────── */} {!isLoading && filteredInvoices.length > 0 && ( -
+

عرض {filteredInvoices.length} فواتير

- -
@@ -342,26 +278,28 @@ export const InvoicesPage = () => { {/* ── Upload Modal ─────────────────────────────────────── */} {isUploadModalOpen && ( -
+
-
+ -

رفع فاتورة جديدة

-

اختر الشركة وملف الفاتورة (PDF أو صورة) وسيقوم الذكاء الاصطناعي بالباقي.

+

رفع فاتورة جديدة

+

اختر الشركة وملف الفاتورة (PDF أو صورة) وسيقوم الذكاء الاصطناعي بالباقي.

-
+
- + { accept=".pdf,image/*" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10" /> -
-
- -
-

+

+ +

{selectedFile ? selectedFile.name : 'اسحب الملف هنا أو انقر للاختيار'}

-

PDF, JPG, PNG (حد أقصى 10MB)

+

PDF, JPG, PNG (حد أقصى 10MB)

@@ -396,26 +332,17 @@ export const InvoicesPage = () => {
@@ -423,61 +350,45 @@ export const InvoicesPage = () => {
)} + {/* ── View Invoice Modal ─────────────────────────────────── */} {isViewModalOpen && viewingInvoice && ( -
+
-
+
-

معاينة الفاتورة

-

رقم: {viewingInvoice.invoice_number || '---'} • {viewingInvoice.company?.name}

+

معاينة الفاتورة

+

رقم: {viewingInvoice.invoice_number || '---'} • {viewingInvoice.company?.name}

- {viewingInvoice.validation_errors && viewingInvoice.validation_errors.length > 0 && ( -
- -
-

فشل التحقق الضريبي:

-
    - {viewingInvoice.validation_errors.map((err: string, i: number) => ( -
  • {err}
  • - ))} -
-
-
- )} - -
-
+
+
Invoice { - // Fallback for PDF or Error e.currentTarget.style.display = 'none'; const token = localStorage.getItem('access_token'); e.currentTarget.parentElement!.innerHTML = ` -
-
- -
-

تعذر عرض الصورة مباشرة

-

قد يكون الملف بتنسيق PDF أو حدث خطأ أثناء التحميل.

- فتح الملف في نافذة جديدة +
+ +

تعذر عرض الملف

+

قد يكون الملف PDF أو حدث خطأ في التحميل.

+ تحميل الملف لفتحه
`; }} @@ -485,15 +396,15 @@ export const InvoicesPage = () => {
-
-
+
+
-

المجموع الكلي

-

{Number(viewingInvoice.grand_total).toLocaleString('en-US', { minimumFractionDigits: 3 })} JOD

+

المجموع الكلي

+

{Number(viewingInvoice.grand_total).toLocaleString('en-US', { minimumFractionDigits: 3 })} JOD

-
+
-

الحالة

+

الحالة

@@ -503,19 +414,16 @@ export const InvoicesPage = () => { const token = localStorage.getItem('access_token'); window.open(`${apiClient.defaults.baseURL}/invoices/${viewingInvoice.id}/file?token=${token}`, '_blank'); }} - className="px-6 py-3 rounded-xl bg-white border border-slate-200 text-slate-700 font-bold flex items-center gap-2 hover:bg-slate-50 transition-all shadow-sm" + className="px-6 py-3 rounded-xl bg-slate-800 border border-slate-700 text-slate-300 font-bold flex items-center gap-2 hover:bg-slate-700 transition-all" > - تحميل الأصلي + تحميل
diff --git a/frontend/src/pages/settings/SettingsPage.tsx b/frontend/src/pages/settings/SettingsPage.tsx index a26b55a..f4cd12c 100644 --- a/frontend/src/pages/settings/SettingsPage.tsx +++ b/frontend/src/pages/settings/SettingsPage.tsx @@ -1,147 +1,142 @@ /** * ════════════════════════════════════════════════════════════ - * مُصادَق (Musadaq) — Settings Page + * مُصادَق (Musadaq) — Settings Page (Premium Dark) * ════════════════════════════════════════════════════════════ */ -import { useState, useEffect } from 'react'; -import { Building, Save, Shield, Globe } from 'lucide-react'; -import apiClient from '../../api/client'; +import { useState } from 'react'; +import { + Settings, + User, + Lock, + Bell, + Globe, + Shield, + CreditCard, + Save, + ChevronLeft, + Palette, + Moon +} from 'lucide-react'; +import { motion } from 'framer-motion'; export const SettingsPage = () => { - const [isSaving, setIsSaving] = useState(false); - - // Tenant Profile State - const [name, setName] = useState(''); - const [email, setEmail] = useState(''); - const [phone, setPhone] = useState(''); - const [address, setAddress] = useState(''); + const [activeTab, setActiveTab] = useState('profile'); - const fetchSettings = async () => { - try { - // Get current tenant info (usually from a profile or me endpoint) - const { data } = await apiClient.get('/auth/me'); // Assuming there's a me endpoint for the tenant - setName(data.tenant.name || ''); - setEmail(data.tenant.email || ''); - // phone and address might be null - } catch (error) { - console.error('Failed to fetch settings', error); - } - }; - - useEffect(() => { - fetchSettings(); - }, []); - - const handleSave = async (e: React.FormEvent) => { - e.preventDefault(); - setIsSaving(true); - try { - // await apiClient.put('/tenants/profile', { name, email, phone, address }); - alert('تم حفظ الإعدادات بنجاح'); - } catch (error) { - alert('فشل حفظ الإعدادات'); - } finally { - setIsSaving(false); - } - }; + const tabs = [ + { id: 'profile', label: 'الملف الشخصي', icon: User }, + { id: 'security', label: 'الأمان والخصوصية', icon: Lock }, + { id: 'office', label: 'إعدادات المكتب', icon: Settings }, + { id: 'notifications', label: 'التنبيهات', icon: Bell }, + { id: 'appearance', label: 'المظهر والنظام', icon: Palette }, + { id: 'subscription', label: 'الاشتراك والدفع', icon: CreditCard }, + ]; return ( -
+
-

إعدادات المكتب

-

إدارة الملف الشخصي لمكتب المحاسبة الخاص بك.

+

إعدادات النظام

+

تخصيص حسابك وتفضيلات مكتب المحاسبة الخاص بك.

-
- {/* ── Tabs/Navigation ──────────────────────────────── */} -
- - - -
+
+ {/* ── Tabs Sidebar ─────────────────────────── */} + - {/* ── Form Content ─────────────────────────────────── */} -
-
-
-
-
- - setName(e.target.value)} - className="w-full bg-slate-50 border border-slate-200 rounded-2xl px-5 py-4 outline-none focus:border-primary-500 transition-all" - /> + {/* ── Content Area ─────────────────────────── */} +
+
+ {activeTab === 'profile' && ( + +
+
+ H +
+
+

حمزة الغويريين

+

مدير مكتب • حساب احترافي

+
+
-
- - setEmail(e.target.value)} - className="w-full bg-slate-50 border border-slate-200 rounded-2xl px-5 py-4 outline-none focus:border-primary-500 transition-all" - /> + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
-
+
+ )} -
- - setPhone(e.target.value)} - className="w-full bg-slate-50 border border-slate-200 rounded-2xl px-5 py-4 outline-none focus:border-primary-500 transition-all" - placeholder="+962 7X XXX XXXX" - /> -
+ {activeTab === 'appearance' && ( + +

المظهر والنظام

+
+
+ + الوضع الداكن (مفعل) +
+
+
+ الوضع الفاتح +
+
+ + )} -
- -
رقم الفاتورةالشركةالتاريخالمجموع (JOD)الحالةإجراءاترقم الفاتورةالشركةالتاريخالمجموع (JOD)الحالةإجراءات
{inv.invoice_number || '---'} + {inv.invoice_number || '---'}
-
- -
- {inv.company?.name || 'شركة غير معروفة'} + + {inv.company?.name || '---'}
{inv.invoice_date ? new Date(inv.invoice_date).toLocaleDateString('ar-JO') : '---'} + {Number(inv.grand_total).toLocaleString('en-US', { minimumFractionDigits: 3 })} -
+
+
e.stopPropagation()}> - -
- - - {/* Dropdown Menu */} -
- - -
- -
-
+