diff --git a/backend/src/modules/invoices/invoice.controller.ts b/backend/src/modules/invoices/invoice.controller.ts index de6528e..af64f6f 100644 --- a/backend/src/modules/invoices/invoice.controller.ts +++ b/backend/src/modules/invoices/invoice.controller.ts @@ -28,6 +28,14 @@ import { CurrentUser } from '../../common/decorators/current-user.decorator'; @UseGuards(JwtAuthGuard, RolesGuard) export class InvoicesController { constructor(private invoicesService: InvoicesService) {} + + /** + * قائمة جميع الفواتير للمكتب + */ + @Get() + async findAllByTenant(@CurrentUser() user: any) { + return this.invoicesService.findAllByTenant(user.tenantId); + } /** * رفع فاتورة لشركة محددة diff --git a/backend/src/modules/invoices/invoice.service.ts b/backend/src/modules/invoices/invoice.service.ts index a7c96e9..207ede5 100644 --- a/backend/src/modules/invoices/invoice.service.ts +++ b/backend/src/modules/invoices/invoice.service.ts @@ -89,6 +89,17 @@ export class InvoicesService { }); } + /** + * قائمة جميع الفواتير للمكتب + */ + async findAllByTenant(tenantId: string): Promise { + return this.invoiceRepository.find({ + where: { tenant_id: tenantId }, + relations: ['company'], + order: { created_at: 'DESC' }, + }); + } + /** * تفاصيل فاتورة */ diff --git a/frontend/src/pages/invoices/InvoicesPage.tsx b/frontend/src/pages/invoices/InvoicesPage.tsx index e248938..891f232 100644 --- a/frontend/src/pages/invoices/InvoicesPage.tsx +++ b/frontend/src/pages/invoices/InvoicesPage.tsx @@ -4,8 +4,8 @@ * ════════════════════════════════════════════════════════════ */ -import { useState } from 'react'; -import { motion } from 'framer-motion'; +import { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; import { Upload, Search, @@ -16,43 +16,108 @@ import { AlertCircle, MoreVertical, ChevronLeft, - ChevronRight + ChevronRight, + Building2, + FileText, + Send } from 'lucide-react'; - -const invoices = [ - { id: '1', number: 'INV-2024-001', company: 'شركة الأمل', date: '2024-04-15', total: '150.000', status: 'approved', type: 'cash' }, - { id: '2', number: 'INV-2024-002', company: 'سوبرماركت المدينة', date: '2024-04-16', total: '2,400.000', status: 'validated', type: 'credit' }, - { id: '3', number: 'OCR_PENDING', company: 'مخبز السلام', date: '2024-04-16', total: '0.000', status: 'extracting', type: 'cash' }, - { id: '4', number: 'INV-2024-003', company: 'مكتبة النجاح', date: '2024-04-14', total: '85.250', status: 'validation_failed', type: 'cash' }, -]; - -const StatusBadge = ({ status }: { status: string }) => { - 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: Clock, label: 'جاهز للإرسال' }, - extracting: { color: 'text-amber-700 bg-amber-50 border-amber-100', icon: Clock, label: 'قيد الاستخراج AI' }, - validation_failed: { color: 'text-red-700 bg-red-50 border-red-100', icon: AlertCircle, label: 'خطأ في التحقق' }, - }; - const { color, icon: Icon, label } = config[status] || config.extracting; - return ( - - - {label} - - ); -}; +import apiClient from '../../api/client'; export const InvoicesPage = () => { + const [invoices, setInvoices] = useState([]); + const [companies, setCompanies] = useState([]); + const [isLoading, setIsLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); + const [isUploadModalOpen, setIsUploadModalOpen] = useState(false); + + // Upload Form State + const [selectedCompanyId, setSelectedCompanyId] = useState(''); + const [selectedFile, setSelectedFile] = useState(null); + const [isUploading, setIsUploading] = useState(false); + + const fetchData = async () => { + try { + const [invRes, compRes] = await Promise.all([ + apiClient.get('/invoices'), + apiClient.get('/companies') + ]); + setInvoices(invRes.data); + setCompanies(compRes.data); + } catch (error) { + console.error('Failed to fetch data', error); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchData(); + }, []); + + const handleUpload = async (e: React.FormEvent) => { + e.preventDefault(); + if (!selectedCompanyId || !selectedFile) return; + + setIsUploading(true); + const formData = new FormData(); + formData.append('file', selectedFile); + + try { + await apiClient.post(`/invoices/upload/${selectedCompanyId}`, formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }); + setIsUploadModalOpen(false); + setSelectedFile(null); + setSelectedCompanyId(''); + fetchData(); + } catch (error) { + console.error('Upload failed', error); + alert('حدث خطأ أثناء رفع الفاتورة'); + } finally { + setIsUploading(false); + } + }; + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + setSelectedFile(e.target.files[0]); + } + }; + + const filteredInvoices = invoices.filter(inv => + inv.invoice_number?.includes(searchTerm) || + inv.company?.name?.includes(searchTerm) + ); + + const StatusBadge = ({ status }: { status: string }) => { + 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: 'خطأ في التحقق' }, + }; + const { color, icon: Icon, label } = config[status] || { color: 'text-slate-500 bg-slate-50', icon: Clock, label: status }; + return ( + + + {label} + + ); + }; return ( -
+

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

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

- @@ -72,87 +137,187 @@ export const InvoicesPage = () => {
{/* ── Invoices Table ───────────────────────────────────── */} -
-
- - - - - - - - - - - - - - {invoices.map((inv, idx) => ( - - - - - - - - - - ))} - -
رقم الفاتورةالشركة المصدرةالتاريخالنوعالمجموع (JOD)الحالةإجراءات
{inv.number}{inv.company}{inv.date} - - {inv.type === 'cash' ? 'نقدي' : 'ذمم'} - - {inv.total} -
- - -
-
-
- - {/* ── Empty State Mock (Hidden if data exists) ───────────── */} - {invoices.length === 0 && ( +
+ {isLoading ? ( +
+
+
+ ) : filteredInvoices.length === 0 ? (
- +

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

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

-
+ ) : ( +
+ + + + + + + + + + + + + {filteredInvoices.map((inv, idx) => ( + + + + + + + + + ))} + +
رقم الفاتورةالشركةالتاريخالمجموع (JOD)الحالةإجراءات
{inv.invoice_number || '---'} +
+
+ +
+ {inv.company?.name || 'شركة غير معروفة'} +
+
+ {inv.issue_date ? new Date(inv.issue_date).toLocaleDateString('ar-JO') : '---'} + + {Number(inv.total_amount).toLocaleString('en-US', { minimumFractionDigits: 3 })} + +
+ + {inv.status === 'validated' && ( + + )} + +
+
+
)} {/* ── Pagination ───────────────────────────────────────── */} -
-

عرض 1-10 من أصل 1,280 فاتورة

-
- - -
-
+ {!isLoading && filteredInvoices.length > 0 && ( +
+

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

+
+ + +
+
+ )}
+ + {/* ── Upload Modal ─────────────────────────────────────── */} + + {isUploadModalOpen && ( +
+ +
+ +

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

+

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

+ +
+
+ + +
+ +
+ +
+ +
+
+ +
+

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

+

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

+
+
+
+ +
+ + +
+
+ +
+ )} +
); };