import React, { useState, useEffect, useRef } from 'react';
import { initializeApp } from 'firebase/app';
import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth';
import { getFirestore, collection, addDoc, onSnapshot, query, orderBy, deleteDoc, doc, updateDoc, serverTimestamp } from 'firebase/firestore';
// Ensure __app_id, __firebase_config, and __initial_auth_token are defined in the environment
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {};
const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;
// Tailwind CSS is assumed to be available, no import needed.
// Lucide React for icons (example: search, edit, delete)
// Shadcn UI components for styling (buttons, inputs, table, dialog)
function App() {
const [db, setDb] = useState(null);
const [auth, setAuth] = useState(null);
const [userId, setUserId] = useState(null);
const [transfers, setTransfers] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [isAuthReady, setIsAuthReady] = useState(false);
const [showAddModal, setShowAddModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [currentTransfer, setCurrentTransfer] = useState(null);
const [formErrors, setFormErrors] = useState({});
const [loading, setLoading] = useState(true);
const [message, setMessage] = useState('');
const messageTimeoutRef = useRef(null);
// Initialize Firebase and set up authentication
useEffect(() => {
try {
const app = initializeApp(firebaseConfig);
const firestore = getFirestore(app);
const authentication = getAuth(app);
setDb(firestore);
setAuth(authentication);
const unsubscribe = onAuthStateChanged(authentication, async (user) => {
if (user) {
setUserId(user.uid);
setIsAuthReady(true);
} else {
try {
if (initialAuthToken) {
await signInWithCustomToken(authentication, initialAuthToken);
} else {
await signInAnonymously(authentication);
}
} catch (error) {
console.error("Error signing in:", error);
setMessage("حدث خطأ أثناء تسجيل الدخول. يرجى المحاولة مرة أخرى.");
setIsAuthReady(true); // Still set ready to attempt data fetch
}
}
});
return () => unsubscribe();
} catch (error) {
console.error("Error initializing Firebase:", error);
setMessage("فشل تهيئة Firebase. يرجى التحقق من الإعدادات.");
setLoading(false);
}
}, []);
// Fetch data from Firestore once authenticated and ready
useEffect(() => {
if (db && userId && isAuthReady) {
const transfersColRef = collection(db, `artifacts/${appId}/public/data/transfers`);
// Note: orderBy is commented out to avoid potential index issues as per instructions.
// Data will be sorted client-side.
const q = query(transfersColRef); //, orderBy('transactionDate', 'desc'));
const unsubscribe = onSnapshot(q, (snapshot) => {
const fetchedTransfers = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
// Sort client-side by transactionDate (descending)
fetchedTransfers.sort((a, b) => {
const dateA = a.transactionDate ? new Date(a.transactionDate.toDate()) : new Date(0);
const dateB = b.transactionDate ? new Date(b.transactionDate.toDate()) : new Date(0);
return dateB - dateA;
});
setTransfers(fetchedTransfers);
setLoading(false);
}, (error) => {
console.error("Error fetching transfers:", error);
setMessage("حدث خطأ أثناء جلب التحويلات.");
setLoading(false);
});
return () => unsubscribe();
} else if (!isAuthReady && !userId) {
// Still waiting for auth to be ready
setLoading(true);
}
}, [db, userId, isAuthReady]);
// Function to show transient messages
const showMessage = (msg) => {
setMessage(msg);
if (messageTimeoutRef.current) {
clearTimeout(messageTimeoutRef.current);
}
messageTimeoutRef.current = setTimeout(() => {
setMessage('');
}, 3000); // Message disappears after 3 seconds
};
// Form validation
const validateForm = (data) => {
const errors = {};
if (!data.receiverName || data.receiverName.trim() === '') {
errors.receiverName = 'اسم المحول إليه مطلوب.';
}
if (!data.bankName || data.bankName.trim() === '') {
errors.bankName = 'اسم البنك مطلوب.';
}
if (!data.amount || isNaN(parseFloat(data.amount)) || parseFloat(data.amount) <= 0) {
errors.amount = 'المبلغ بالدولار يجب أن يكون رقمًا موجبًا.';
}
// Corrected validation for transactionDate
if (!data.transactionDate || !(data.transactionDate instanceof Date) || isNaN(data.transactionDate.getTime())) {
errors.transactionDate = 'تاريخ العملية مطلوب وصحيح.';
}
return errors;
};
// Add new transfer
const handleAddTransfer = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const newTransfer = {
receiverName: formData.get('receiverName'),
bankName: formData.get('bankName'),
amount: parseFloat(formData.get('amount')),
transactionDate: new Date(formData.get('transactionDate')), // Convert to Date object
userId: userId, // Store the user ID for ownership (even for public data)
createdAt: serverTimestamp() // Add creation timestamp
};
const errors = validateForm(newTransfer);
if (Object.keys(errors).length > 0) {
setFormErrors(errors);
return;
}
try {
if (db) {
await addDoc(collection(db, `artifacts/${appId}/public/data/transfers`), newTransfer);
setShowAddModal(false);
e.target.reset(); // Clear form
setFormErrors({});
showMessage("تمت إضافة التحويل بنجاح!");
}
} catch (error) {
console.error("Error adding document: ", error);
showMessage("حدث خطأ أثناء إضافة التحويل.");
}
};
// Update existing transfer
const handleUpdateTransfer = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const updatedTransfer = {
receiverName: formData.get('receiverName'),
bankName: formData.get('bankName'),
amount: parseFloat(formData.get('amount')),
transactionDate: new Date(formData.get('transactionDate')), // Convert to Date object
updatedAt: serverTimestamp() // Add update timestamp
};
const errors = validateForm(updatedTransfer);
if (Object.keys(errors).length > 0) {
setFormErrors(errors);
return;
}
try {
if (db && currentTransfer) {
const transferDocRef = doc(db, `artifacts/${appId}/public/data/transfers`, currentTransfer.id);
await updateDoc(transferDocRef, updatedTransfer);
setShowEditModal(false);
setCurrentTransfer(null);
setFormErrors({});
showMessage("تم تحديث التحويل بنجاح!");
}
} catch (error) {
console.error("Error updating document: ", error);
showMessage("حدث خطأ أثناء تحديث التحويل.");
}
};
// Delete transfer
const handleDeleteTransfer = async (id) => {
if (!window.confirm("هل أنت متأكد أنك تريد حذف هذا التحويل؟")) { // Using window.confirm for simplicity, but custom modal is preferred
return;
}
try {
if (db) {
await deleteDoc(doc(db, `artifacts/${appId}/public/data/transfers`, id));
showMessage("تم حذف التحويل بنجاح!");
}
} catch (error) {
console.error("Error deleting document: ", error);
showMessage("حدث خطأ أثناء حذف التحويل.");
}
};
// Filter transfers based on search term
const filteredTransfers = transfers.filter(transfer => {
const searchLower = searchTerm.toLowerCase();
return (
transfer.receiverName.toLowerCase().includes(searchLower) ||
transfer.bankName.toLowerCase().includes(searchLower) ||
transfer.amount.toString().includes(searchLower) ||
(transfer.transactionDate && new Date(transfer.transactionDate.toDate()).toLocaleDateString('ar-EG').includes(searchLower))
);
});
if (loading) {
return (
);
}
return (
{/* Global Message Display */}
{message && (
{message}
)}
{/* Header */}
نظام إدارة التحويلات المالية
معرف المستخدم:
{userId}
{/* Main Content Area */}
{/* Add Transfer Section */}
إضافة تحويل جديد
{/* Transfers List Section */}
{filteredTransfers.length === 0 ? (
لا توجد تحويلات لعرضها أو لا توجد نتائج للبحث.
) : (
|
المحول إليه
|
البنك
|
المبلغ (دولار)
|
التاريخ
|
الإجراءات
|
{filteredTransfers.map((transfer) => (
|
{transfer.receiverName}
|
{transfer.bankName}
|
${transfer.amount.toFixed(2)}
|
{transfer.transactionDate ? new Date(transfer.transactionDate.toDate()).toLocaleDateString('ar-EG') : 'غير محدد'}
|
|
))}
)}
{/* Add Transfer Modal */}
{showAddModal && (
)}
{/* Edit Transfer Modal */}
{showEditModal && currentTransfer && (
)}
{/* Custom CSS for animations and general styling */}
);
}
export default App;