| | <!DOCTYPE html> |
| | <html lang="ar" dir="rtl"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>إدارة مالية النادي - الواجهة الذكية</title> |
| | <script src="https://cdn.tailwindcss.com"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> |
| | <script src="https://unpkg.com/lucide@latest"></script> |
| | <style> |
| | @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;800&display=swap'); |
| | body { font-family: 'Cairo', sans-serif; font-size: 13px; } |
| | .animate-fade-in { animation: fadeIn 0.3s ease-out; } |
| | @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } |
| | ::-webkit-scrollbar { width: 4px; } |
| | ::-webkit-scrollbar-track { background: #f1f1f1; } |
| | ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; } |
| | </style> |
| | </head> |
| | <body class="bg-slate-50 text-right text-slate-700"> |
| | <div id="root"></div> |
| |
|
| | <script type="text/babel"> |
| | const { useState, useEffect } = React; |
| | |
| | const App = () => { |
| | const STORAGE_KEYS = { |
| | MEMBERS: 'club_members_data', |
| | TRIPS: 'club_trips_data', |
| | ACTIVITIES: 'club_activities_data', |
| | TRIP_ONE: 'club_trip_one_data' |
| | }; |
| | |
| | const [isAdmin, setIsAdmin] = useState(false); |
| | const [showLoginModal, setShowLoginModal] = useState(false); |
| | const [passwordInput, setPasswordInput] = useState(''); |
| | const [loginError, setLoginError] = useState(false); |
| | const [activeTab, setActiveTab] = useState('dashboard'); |
| | |
| | const [members, setMembers] = useState(() => { |
| | const saved = localStorage.getItem(STORAGE_KEYS.MEMBERS); |
| | return saved ? JSON.parse(saved) : []; |
| | }); |
| | const [trips, setTrips] = useState(() => { |
| | const saved = localStorage.getItem(STORAGE_KEYS.TRIPS); |
| | return saved ? JSON.parse(saved) : []; |
| | }); |
| | const [activities, setActivities] = useState(() => { |
| | const saved = localStorage.getItem(STORAGE_KEYS.ACTIVITIES); |
| | return saved ? JSON.parse(saved) : []; |
| | }); |
| | const [tripOneRegistrations, setTripOneRegistrations] = useState(() => { |
| | const saved = localStorage.getItem(STORAGE_KEYS.TRIP_ONE); |
| | return saved ? JSON.parse(saved) : []; |
| | }); |
| | |
| | useEffect(() => localStorage.setItem(STORAGE_KEYS.MEMBERS, JSON.stringify(members)), [members]); |
| | useEffect(() => localStorage.setItem(STORAGE_KEYS.TRIPS, JSON.stringify(trips)), [trips]); |
| | useEffect(() => localStorage.setItem(STORAGE_KEYS.ACTIVITIES, JSON.stringify(activities)), [activities]); |
| | useEffect(() => localStorage.setItem(STORAGE_KEYS.TRIP_ONE, JSON.stringify(tripOneRegistrations)), [tripOneRegistrations]); |
| | |
| | const [memberForm, setMemberForm] = useState({ name: '', amount: '', date: '' }); |
| | const [tripForm, setTripForm] = useState({ destination: '', cost: '', income: '', date: '' }); |
| | const [activityForm, setActivityForm] = useState({ title: '', expenses: '', date: '' }); |
| | const [tripOneForm, setTripOneForm] = useState({ name: '', fee: '', paid: false }); |
| | |
| | const CORRECT_PASSWORD = "CLUB@2976"; |
| | const WHATSAPP_LINK = "https://wa.me/message/P3DXRV6NTZ63L1"; |
| | |
| | const handleLogin = (e) => { |
| | e.preventDefault(); |
| | if (passwordInput === CORRECT_PASSWORD) { |
| | setIsAdmin(true); |
| | setShowLoginModal(false); |
| | setLoginError(false); |
| | setPasswordInput(''); |
| | } else { |
| | setLoginError(true); |
| | } |
| | }; |
| | |
| | const totalMemberIncome = members.reduce((acc, curr) => acc + (parseFloat(curr.amount) || 0), 0); |
| | const tripOneIncome = tripOneRegistrations.reduce((acc, curr) => curr.paid ? acc + (parseFloat(curr.fee) || 0) : acc, 0); |
| | const totalTripBalance = trips.reduce((acc, curr) => acc + ((parseFloat(curr.income) || 0) - (parseFloat(curr.cost) || 0)), 0); |
| | const totalActivityExpenses = activities.reduce((acc, curr) => acc + (parseFloat(curr.expenses) || 0), 0); |
| | const netBalance = totalMemberIncome + tripOneIncome + totalTripBalance - totalActivityExpenses; |
| | |
| | useEffect(() => { lucide.createIcons(); }, [activeTab, isAdmin, showLoginModal]); |
| | |
| | const SidebarItem = ({ id, iconName, label, color = "blue", isExternal = false, href = "" }) => { |
| | const baseClass = `w-full flex items-center space-x-reverse space-x-2 px-3 py-2 rounded-lg transition-all text-xs font-semibold`; |
| | if (isExternal) { |
| | return ( |
| | <a href={href} target="_blank" rel="noopener noreferrer" className={`${baseClass} text-emerald-600 hover:bg-emerald-50`}> |
| | <i data-lucide={iconName} className="w-4 h-4"></i> |
| | <span>{label}</span> |
| | </a> |
| | ); |
| | } |
| | return ( |
| | <button onClick={() => setActiveTab(id)} className={`${baseClass} ${activeTab === id ? `bg-${color}-600 text-white shadow-md` : `text-slate-500 hover:bg-slate-100`}`}> |
| | <i data-lucide={iconName} className="w-4 h-4"></i> |
| | <span>{label}</span> |
| | </button> |
| | ); |
| | }; |
| | |
| | return ( |
| | <div className="min-h-screen bg-slate-50 flex flex-col md:flex-row-reverse" dir="rtl"> |
| | {/* Login Modal */} |
| | {showLoginModal && ( |
| | <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"> |
| | <div className="bg-white rounded-3xl p-8 shadow-2xl animate-fade-in text-center max-w-xs w-full relative"> |
| | <button onClick={() => {setShowLoginModal(false); setLoginError(false);}} className="absolute top-4 left-4 text-slate-400 hover:text-slate-600"> |
| | <i data-lucide="x" className="w-5 h-5"></i> |
| | </button> |
| | <div className="bg-blue-600 w-12 h-12 rounded-xl flex items-center justify-center text-white mx-auto mb-4"> |
| | <i data-lucide="shield-check" className="w-6 h-6"></i> |
| | </div> |
| | <h2 className="text-lg font-extrabold text-slate-800 mb-1">دخول المكلف</h2> |
| | <p className="text-[10px] text-slate-400 mb-4 font-bold">يرجى إدخال رمز الوصول للإدارة</p> |
| | <form onSubmit={handleLogin} className="space-y-3"> |
| | <input type="password" placeholder="كلمة السر" value={passwordInput} onChange={(e) => setPasswordInput(e.target.value)} |
| | className="w-full p-3 bg-slate-50 rounded-xl border border-slate-100 outline-none text-center font-bold text-sm" autoFocus /> |
| | <button type="submit" className="w-full bg-blue-600 text-white py-3 rounded-xl font-bold text-sm shadow-lg hover:bg-blue-700">تأكيد الدخول</button> |
| | </form> |
| | {loginError && <p className="text-rose-500 text-[10px] mt-2 font-bold">الرمز غير صحيح!</p>} |
| | </div> |
| | </div> |
| | )} |
| | |
| | {/* Sidebar */} |
| | <aside className="w-full md:w-56 bg-white border-l border-slate-200 p-4 flex flex-col gap-1 shrink-0 z-10"> |
| | <div className="flex items-center gap-2 mb-6 px-2"> |
| | <div className="bg-blue-600 p-1.5 rounded-lg text-white shadow-sm"> |
| | <i data-lucide="wallet" className="w-4 h-4"></i> |
| | </div> |
| | <h1 className="text-base font-extrabold text-slate-800">المالية الذكية</h1> |
| | </div> |
| | <nav className="space-y-1"> |
| | <SidebarItem id="dashboard" iconName="layout-dashboard" label="لوحة التحكم" /> |
| | <SidebarItem id="members" iconName="users" label="المنخرطون" /> |
| | <SidebarItem id="trip_one" iconName="map" label="الرحلة 1" /> |
| | <SidebarItem id="trips" iconName="navigation" label="رحلات أخرى" color="purple" /> |
| | <SidebarItem id="activities" iconName="calendar" label="الأنشطة" color="rose" /> |
| | <SidebarItem isExternal={true} href={WHATSAPP_LINK} iconName="message-circle" label="تواصل معنا" /> |
| | </nav> |
| | <div className="mt-auto pt-4 border-t border-slate-100 space-y-2"> |
| | {isAdmin ? ( |
| | <button onClick={() => setIsAdmin(false)} className="w-full text-rose-500 text-[11px] font-bold flex items-center gap-2 hover:bg-rose-50 p-2 rounded-lg"> |
| | <i data-lucide="log-out" className="w-3.5 h-3.5"></i> خروج من الإدارة |
| | </button> |
| | ) : ( |
| | <button onClick={() => setShowLoginModal(true)} className="w-full text-blue-600 text-[11px] font-bold flex items-center gap-2 hover:bg-blue-50 p-2 rounded-lg"> |
| | <i data-lucide="user-check" className="w-3.5 h-3.5"></i> دخول المكلف |
| | </button> |
| | )} |
| | <div className={`text-[9px] text-center font-bold px-2 py-1 rounded ${isAdmin ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-100 text-slate-500'}`}> |
| | {isAdmin ? 'وضع التعديل نشط' : 'وضع العرض فقط'} |
| | </div> |
| | </div> |
| | </aside> |
| | |
| | {/* Main Content */} |
| | <main className="flex-1 p-4 md:p-6 overflow-y-auto animate-fade-in"> |
| | {activeTab === 'dashboard' && ( |
| | <div className="space-y-6"> |
| | <header className="flex justify-between items-center"> |
| | <div> |
| | <h2 className="text-xl font-extrabold text-slate-800 leading-tight">ملخص النادي</h2> |
| | <p className="text-[11px] text-slate-400 font-bold uppercase tracking-wider">البيانات المالية المحدثة</p> |
| | </div> |
| | </header> |
| | |
| | <div className="grid grid-cols-2 lg:grid-cols-4 gap-3"> |
| | {[ |
| | { label: "انخراطات", val: totalMemberIncome, bg: "bg-emerald-50", text: "text-emerald-700" }, |
| | { label: "الرحلة 1", val: tripOneIncome, bg: "bg-blue-50", text: "text-blue-700" }, |
| | { label: "أرباح الرحلات", val: totalTripBalance, bg: "bg-purple-50", text: "text-purple-700" }, |
| | { label: "المصاريف", val: totalActivityExpenses, bg: "bg-rose-50", text: "text-rose-700" } |
| | ].map((card, i) => ( |
| | <div key={i} className={`p-3 rounded-2xl ${card.bg} border border-white shadow-sm`}> |
| | <p className={`text-[10px] font-bold mb-0.5 ${card.text}`}>{card.label}</p> |
| | <h3 className={`text-lg font-extrabold ${card.text}`}>{card.val} <span className="text-[10px]">MAD</span></h3> |
| | </div> |
| | ))} |
| | </div> |
| | |
| | <div className="bg-slate-900 text-white p-6 rounded-3xl shadow-xl relative overflow-hidden"> |
| | <div className="absolute top-0 right-0 w-32 h-32 bg-blue-500/10 rounded-full blur-2xl"></div> |
| | <p className="text-slate-400 text-xs font-bold mb-1">صافي الرصيد المتوفر</p> |
| | <h2 className="text-4xl font-extrabold tracking-tight">{netBalance} <span className="text-sm text-slate-500 font-bold">درهم مغربي</span></h2> |
| | </div> |
| | </div> |
| | )} |
| | |
| | {activeTab === 'members' && ( |
| | <div className="space-y-4"> |
| | <h2 className="text-lg font-extrabold text-slate-800">سجل المنخرطين</h2> |
| | {isAdmin && ( |
| | <div className="bg-white p-4 rounded-2xl shadow-sm grid grid-cols-1 md:grid-cols-4 gap-2 border"> |
| | <input type="text" placeholder="الاسم" value={memberForm.name} onChange={(e)=>setMemberForm({...memberForm, name: e.target.value})} className="p-2 bg-slate-50 rounded-lg outline-none border border-slate-100 text-xs font-semibold" /> |
| | <input type="number" placeholder="المبلغ" value={memberForm.amount} onChange={(e)=>setMemberForm({...memberForm, amount: e.target.value})} className="p-2 bg-slate-50 rounded-lg outline-none border border-slate-100 text-xs font-semibold" /> |
| | <input type="date" value={memberForm.date} onChange={(e)=>setMemberForm({...memberForm, date: e.target.value})} className="p-2 bg-slate-50 rounded-lg outline-none border border-slate-100 text-xs font-semibold" /> |
| | <button onClick={()=>{if(!memberForm.name) return; setMembers([...members, {...memberForm, id: Date.now()}]); setMemberForm({name:'',amount:'',date:''})}} className="bg-blue-600 text-white p-2 rounded-lg font-bold text-xs">إضافة</button> |
| | </div> |
| | )} |
| | <div className="bg-white rounded-2xl shadow-sm border overflow-hidden"> |
| | <table className="w-full text-right"> |
| | <thead className="bg-slate-50 border-b text-[11px] text-slate-400 uppercase font-extrabold"> |
| | <tr><th className="px-4 py-3">الاسم</th><th className="px-4 py-3 text-center">المبلغ</th>{isAdmin && <th className="px-4 py-3 text-center w-16">إجراء</th>}</tr> |
| | </thead> |
| | <tbody className="divide-y divide-slate-50 text-xs font-semibold"> |
| | {members.map(m => ( |
| | <tr key={m.id} className="hover:bg-slate-50"> |
| | <td className="px-4 py-2 text-slate-700">{m.name}</td> |
| | <td className="px-4 py-2 text-emerald-600 text-center font-bold">{m.amount} MAD</td> |
| | {isAdmin && <td className="px-4 py-2 text-center"><button onClick={()=>setMembers(members.filter(x=>x.id!==m.id))} className="text-rose-300 hover:text-rose-500 p-1"><i data-lucide="trash-2" className="w-3.5 h-3.5"></i></button></td>} |
| | </tr> |
| | ))} |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | )} |
| | |
| | {activeTab === 'trip_one' && ( |
| | <div className="space-y-4"> |
| | <h2 className="text-lg font-extrabold text-blue-800 text-center md:text-right">لائحة المشاركين في الرحلة 1</h2> |
| | {isAdmin && ( |
| | <div className="bg-white p-4 rounded-2xl shadow-sm grid grid-cols-1 md:grid-cols-4 gap-2 border border-blue-50"> |
| | <input type="text" placeholder="الاسم الكامل" value={tripOneForm.name} onChange={(e)=>setTripOneForm({...tripOneForm, name: e.target.value})} className="p-2 bg-blue-50/50 rounded-lg outline-none text-xs font-semibold" /> |
| | <input type="number" placeholder="المبلغ" value={tripOneForm.fee} onChange={(e)=>setTripOneForm({...tripOneForm, fee: e.target.value})} className="p-2 bg-blue-50/50 rounded-lg outline-none text-xs font-semibold" /> |
| | <div className="flex items-center gap-2 px-1"> |
| | <input type="checkbox" checked={tripOneForm.paid} onChange={(e)=>setTripOneForm({...tripOneForm, paid: e.target.checked})} className="w-4 h-4 accent-blue-600" /> |
| | <span className="text-[11px] font-bold">مدفوع</span> |
| | </div> |
| | <button onClick={()=>{if(!tripOneForm.name) return; setTripOneRegistrations([...tripOneRegistrations, {...tripOneForm, id: Date.now()}]); setTripOneForm({name:'',fee:'',paid:false})}} className="bg-blue-700 text-white p-2 rounded-lg font-bold text-xs shadow-sm">تسجيل مشارك</button> |
| | </div> |
| | )} |
| | <div className="bg-white rounded-2xl shadow-sm border overflow-hidden"> |
| | <table className="w-full text-right"> |
| | <thead className="bg-blue-700 text-white text-[11px] font-bold"> |
| | <tr><th className="px-4 py-2.5">المشارك</th><th className="px-4 py-2.5 text-center">الواجب</th><th className="px-4 py-2.5 text-center">الحالة</th>{isAdmin && <th className="px-4 py-2.5 text-center w-16">إجراء</th>}</tr> |
| | </thead> |
| | <tbody className="text-xs font-semibold divide-y divide-blue-50"> |
| | {tripOneRegistrations.map(reg => ( |
| | <tr key={reg.id} className="hover:bg-blue-50/30"> |
| | <td className="px-4 py-2">{reg.name}</td> |
| | <td className="px-4 py-2 text-center font-bold">{reg.fee} MAD</td> |
| | <td className="px-4 py-2 text-center"> |
| | <span onClick={() => isAdmin && setTripOneRegistrations(tripOneRegistrations.map(r => r.id === reg.id ? {...r, paid: !r.paid} : r))} |
| | className={`px-2 py-0.5 rounded-full text-[9px] font-extrabold border ${isAdmin ? 'cursor-pointer' : ''} ${reg.paid ? 'bg-emerald-50 text-emerald-600 border-emerald-100' : 'bg-amber-50 text-amber-600 border-amber-100'}`}> |
| | {reg.paid ? 'تم الدفع' : 'في الانتظار'} |
| | </span> |
| | </td> |
| | {isAdmin && <td className="px-4 py-2 text-center"><button onClick={()=>setTripOneRegistrations(tripOneRegistrations.filter(x=>x.id!==reg.id))} className="text-blue-200 hover:text-rose-500"><i data-lucide="trash-2" className="w-3.5 h-3.5"></i></button></td>} |
| | </tr> |
| | ))} |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | )} |
| | |
| | {activeTab === 'trips' && ( |
| | <div className="space-y-4"> |
| | <h2 className="text-lg font-extrabold text-purple-800">بيانات الرحلات</h2> |
| | {isAdmin && ( |
| | <div className="bg-white p-4 rounded-2xl shadow-sm grid grid-cols-1 md:grid-cols-4 gap-2 border border-purple-50"> |
| | <input type="text" placeholder="الوجهة" value={tripForm.destination} onChange={(e)=>setTripForm({...tripForm, destination: e.target.value})} className="p-2 bg-purple-50/50 rounded-lg outline-none text-xs font-semibold" /> |
| | <input type="number" placeholder="المصاريف" value={tripForm.cost} onChange={(e)=>setTripForm({...tripForm, cost: e.target.value})} className="p-2 bg-purple-50/50 rounded-lg outline-none text-xs font-semibold" /> |
| | <input type="number" placeholder="المداخيل" value={tripForm.income} onChange={(e)=>setTripForm({...tripForm, income: e.target.value})} className="p-2 bg-purple-50/50 rounded-lg outline-none text-xs font-semibold" /> |
| | <button onClick={()=>{if(!tripForm.destination) return; setTrips([...trips, {...tripForm, id: Date.now()}]); setTripForm({destination:'',cost:'',income:'',date:''})}} className="bg-purple-600 text-white p-2 rounded-lg font-bold text-xs shadow-sm">حفظ الرحلة</button> |
| | </div> |
| | )} |
| | <div className="bg-white rounded-2xl shadow-sm border overflow-hidden"> |
| | <table className="w-full text-right text-xs"> |
| | <thead className="bg-purple-50 text-purple-900 border-b text-[11px] font-bold"> |
| | <tr><th className="px-4 py-3">الوجهة</th><th className="px-4 py-3 text-center">مصاريف</th><th className="px-4 py-3 text-center">مداخيل</th><th className="px-4 py-3 text-center">الصافي</th>{isAdmin && <th className="px-4 py-3 text-center">حذف</th>}</tr> |
| | </thead> |
| | <tbody className="divide-y divide-purple-50 font-semibold"> |
| | {trips.map(t => ( |
| | <tr key={t.id} className="hover:bg-purple-50/30"> |
| | <td className="px-4 py-2">{t.destination}</td> |
| | <td className="px-4 py-2 text-rose-500 text-center">{t.cost}</td> |
| | <td className="px-4 py-2 text-emerald-600 text-center">{t.income}</td> |
| | <td className="px-4 py-2 text-center font-extrabold text-slate-800">{(t.income - t.cost)} MAD</td> |
| | {isAdmin && <td className="px-4 py-2 text-center"><button onClick={()=>setTrips(trips.filter(x=>x.id!==t.id))} className="text-purple-200 hover:text-rose-500"><i data-lucide="trash-2" className="w-3.5 h-3.5"></i></button></td>} |
| | </tr> |
| | ))} |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | )} |
| | |
| | {activeTab === 'activities' && ( |
| | <div className="space-y-4"> |
| | <h2 className="text-lg font-extrabold text-rose-800">سجل الأنشطة</h2> |
| | {isAdmin && ( |
| | <div className="bg-white p-4 rounded-2xl shadow-sm grid grid-cols-1 md:grid-cols-3 gap-2 border border-rose-50"> |
| | <input type="text" placeholder="اسم النشاط" value={activityForm.title} onChange={(e)=>setActivityForm({...activityForm, title: e.target.value})} className="p-2 bg-rose-50/50 rounded-lg outline-none text-xs font-semibold" /> |
| | <input type="number" placeholder="الميزانية المستهلكة" value={activityForm.expenses} onChange={(e)=>setActivityForm({...activityForm, expenses: e.target.value})} className="p-2 bg-rose-50/50 rounded-lg outline-none text-xs font-semibold" /> |
| | <button onClick={()=>{if(!activityForm.title) return; setActivities([...activities, {...activityForm, id: Date.now()}]); setActivityForm({title:'',expenses:'',date:''})}} className="bg-rose-600 text-white p-2 rounded-lg font-bold text-xs shadow-sm">إضافة نشاط</button> |
| | </div> |
| | )} |
| | <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2"> |
| | {activities.map(a => ( |
| | <div key={a.id} className="p-3 bg-white border border-rose-50 rounded-xl flex justify-between items-center shadow-sm"> |
| | <div> |
| | <h4 className="font-extrabold text-xs text-slate-800 leading-tight">{a.title}</h4> |
| | <p className="text-rose-600 font-bold text-[11px]">{a.expenses} MAD</p> |
| | </div> |
| | {isAdmin && <button onClick={()=>setActivities(activities.filter(x=>x.id!==a.id))} className="text-slate-200 hover:text-rose-500"><i data-lucide="trash-2" className="w-3.5 h-3.5"></i></button>} |
| | </div> |
| | ))} |
| | {activities.length === 0 && <p className="text-slate-400 text-center col-span-full py-10 text-[11px] font-bold italic">لا توجد أنشطة مسجلة بعد</p>} |
| | </div> |
| | </div> |
| | )} |
| | </main> |
| | </div> |
| | ); |
| | }; |
| | |
| | const root = ReactDOM.createRoot(document.getElementById('root')); |
| | root.render(<App />); |
| | </script> |
| | |
| | <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> |
| | <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> |
| | <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> |
| | </body> |
| | </html> |