Files
musadeq/frontend/src/pages/companies/CompaniesPage.tsx
2026-04-18 00:34:34 +03:00

198 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Companies Management Page
* ════════════════════════════════════════════════════════════
*/
import { useState, useEffect } from 'react';
import { Building2, Plus, Search, MoreVertical, ShieldCheck, Key } from 'lucide-react';
import apiClient from '../../api/client';
export const CompaniesPage = () => {
const [companies, setCompanies] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
// Form State
const [name, setName] = useState('');
const [tin, setTin] = useState('');
const [address, setAddress] = useState('');
const fetchCompanies = async () => {
try {
const { data } = await apiClient.get('/companies');
setCompanies(data);
} catch (error) {
console.error('Failed to fetch companies', error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchCompanies();
}, []);
const handleCreateCompany = async (e: React.FormEvent) => {
e.preventDefault();
try {
await apiClient.post('/companies', { name, tin, address });
setIsAddModalOpen(false);
setName('');
setTin('');
setAddress('');
fetchCompanies();
} catch (error) {
console.error('Failed to create company', error);
alert('حدث خطأ أثناء إضافة الشركة');
}
};
const filteredCompanies = companies.filter(c =>
c.name.includes(searchTerm) || c.tin?.includes(searchTerm)
);
return (
<div className="space-y-8 h-full flex flex-col animate-in fade-in slide-in-from-bottom-4 duration-700">
<header className="flex items-center justify-between">
<div>
<h2 className="text-3xl font-bold text-slate-900">إدارة الشركات</h2>
<p className="text-slate-500 mt-1">أضف عملائك وشركاتك لربط فواتيرهم بنظام جو فوترة.</p>
</div>
<button
onClick={() => setIsAddModalOpen(true)}
className="btn-primary py-3 px-8 rounded-2xl flex items-center gap-2 shadow-xl shadow-primary-500/25 active:scale-95 transition-all"
>
<Plus className="w-5 h-5" />
إضافة شركة جديدة
</button>
</header>
{/* ── Search Bar ──────────────────────────────── */}
<div className="flex gap-4">
<div className="flex-1 glass border-slate-200 rounded-2xl px-4 py-3 flex items-center gap-3">
<Search className="w-5 h-5 text-slate-400" />
<input
type="text"
placeholder="ابحث باسم الشركة أو الرقم الضريبي..."
className="bg-transparent border-none outline-none flex-1 text-slate-800 text-sm"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
{/* ── Companies Grid ───────────────────────────────────── */}
{isLoading ? (
<div className="flex-1 flex justify-center items-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
</div>
) : filteredCompanies.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center p-20 text-center bg-white rounded-3xl border border-slate-100 shadow-sm">
<div className="w-24 h-24 bg-slate-50 rounded-full flex items-center justify-center mb-6 border border-slate-100">
<Building2 className="w-10 h-10 text-slate-300" />
</div>
<h3 className="text-xl font-bold text-slate-900 mb-2">لا توجد شركات مسجلة</h3>
<p className="text-slate-500 max-w-sm mb-8">ابدأ بإضافة أول شركة لكي تتمكن من رفع فواتيرها ومعالجتها ضريبياً.</p>
<button onClick={() => setIsAddModalOpen(true)} className="btn-primary py-3 px-8 rounded-2xl flex items-center gap-2">
<Plus className="w-5 h-5" />
إضافة شركتك الأولى
</button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredCompanies.map((company, idx) => (
<div key={company.id} className="card-premium p-6 hover:shadow-lg transition-all border border-slate-100">
<div className="flex items-start justify-between mb-4">
<div className="w-12 h-12 rounded-xl bg-primary-50 text-primary-600 flex items-center justify-center">
<Building2 className="w-6 h-6" />
</div>
<button className="p-2 text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded-lg transition-all">
<MoreVertical className="w-5 h-5" />
</button>
</div>
<h3 className="text-xl font-bold text-slate-900 mb-1">{company.name}</h3>
<p className="text-sm text-slate-500 mb-6">الرقم الضريبي: <span className="font-mono font-bold text-slate-700">{company.tin || 'غير محدد'}</span></p>
<div className="pt-4 border-t border-slate-100 flex items-center justify-between">
<div className="flex items-center gap-2 text-xs font-bold text-slate-500">
{company.jofotara_client_id ? (
<span className="flex items-center gap-1 text-emerald-600 bg-emerald-50 px-2 py-1 rounded-md">
<ShieldCheck className="w-3.5 h-3.5" /> مربوط بجو فوترة
</span>
) : (
<span className="flex items-center gap-1 text-amber-600 bg-amber-50 px-2 py-1 rounded-md">
<Key className="w-3.5 h-3.5" /> غير مربوط
</span>
)}
</div>
<button className="text-primary-600 text-sm font-bold hover:underline">
التفاصيل
</button>
</div>
</div>
))}
</div>
)}
{/* ── Add Company Modal ───────────────────────────────── */}
{isAddModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/50 backdrop-blur-sm animate-in fade-in duration-200">
<div className="bg-white rounded-3xl p-8 w-full max-w-md shadow-2xl animate-in zoom-in-95 duration-200">
<h3 className="text-2xl font-bold text-slate-900 mb-6">إضافة شركة جديدة</h3>
<form onSubmit={handleCreateCompany} className="space-y-4">
<div>
<label className="block text-sm font-bold text-slate-700 mb-1">اسم الشركة / العميل *</label>
<input
type="text"
required
value={name}
onChange={e => 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="مثال: صيدلية النجاح"
/>
</div>
<div>
<label className="block text-sm font-bold text-slate-700 mb-1">الرقم الضريبي (TIN)</label>
<input
type="text"
value={tin}
onChange={e => 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"
/>
</div>
<div>
<label className="block text-sm font-bold text-slate-700 mb-1">العنوان</label>
<input
type="text"
value={address}
onChange={e => 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="مثال: عمان، شارع مكة"
/>
</div>
<div className="flex gap-3 pt-4">
<button
type="button"
onClick={() => setIsAddModalOpen(false)}
className="flex-1 bg-slate-100 text-slate-700 font-bold py-3 rounded-xl hover:bg-slate-200 transition-all"
>
إلغاء
</button>
<button
type="submit"
className="flex-1 btn-primary py-3 rounded-xl"
>
حفظ الشركة
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
};