2026-03-11 11:03:12 +05:30

230 lines
9.9 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import DataTable from '../../../Components/DataTable';
import Toast from '../Components/Toast';
import {
Search,
Plus,
Eye,
Filter,
Calendar,
Building,
CreditCard,
ArrowUpRight,
X,
ChevronRight,
SearchIcon,
CalendarIcon
} from 'lucide-react';
import AddCollectionModal from './AddCollectionModal';
export default function CollectionsIndex() {
const [collections, setCollections] = useState([]);
const [branches, setBranches] = useState([]);
const [collectionTypes, setCollectionTypes] = useState([]);
const [loading, setLoading] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false);
const [toast, setToast] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
// Filter States
const [filterFromDate, setFilterFromDate] = useState('');
const [filterToDate, setFilterToDate] = useState('');
const [filterBranch, setFilterBranch] = useState(window.__APP_DATA__?.branch?.id || '');
const [filterMethod, setFilterMethod] = useState('');
const isReceptionist = window.__APP_DATA__?.role === 'receptionist';
useEffect(() => {
fetchMetadata();
fetchCollections();
}, [filterBranch, filterFromDate, filterToDate, filterMethod]);
const fetchMetadata = async () => {
try {
const [bRes, tRes] = await Promise.all([
fetch('/api/branches'),
fetch('/api/masters/collection')
]);
if (bRes.ok) setBranches(await bRes.json());
if (tRes.ok) setCollectionTypes(await tRes.json());
} catch (error) {
console.error('Error fetching metadata:', error);
}
};
const fetchCollections = async () => {
setLoading(true);
try {
let url = '/api/collections?';
if (filterBranch) url += `branch_id=${filterBranch}&`;
if (filterFromDate) url += `start_date=${filterFromDate}&`;
if (filterToDate) url += `end_date=${filterToDate}&`;
if (filterMethod) url += `payment_method=${filterMethod}&`;
const response = await fetch(url);
const data = await response.json();
setCollections(data);
} catch (error) {
console.error('Error fetching collections:', error);
} finally {
setLoading(false);
}
};
const columns = [
{
header: 'DATE',
render: (row) => (
<span className="text-sm font-bold text-gray-500">{new Date(row.date).toLocaleDateString()}</span>
)
},
{
header: 'BRANCH',
render: (row) => (
<span className="text-gray-500 font-bold text-sm tracking-tight">{row.branch?.name}</span>
)
},
{
header: 'ITEMS',
render: (row) => (
<div className="flex flex-col">
<span className="font-black text-[#111827] text-sm">
{row.type?.name === 'Product sale' || row.type?.name === 'Product saled'
? (row.items && row.items.length > 0 ? row.items[0].product?.name : 'Product Sale')
: row.type?.name
}
</span>
{row.items && row.items.length > 1 && (
<span className="text-[10px] text-gray-400 font-bold">+{row.items.length - 1} more items</span>
)}
</div>
)
},
{
header: 'PAYMENT METHOD',
render: (row) => (
<span className="text-gray-500 font-bold text-sm">{row.payment_method}</span>
)
},
{
header: 'AMOUNT',
render: (row) => (
<span className="font-black text-[#10B981] text-sm">{parseFloat(row.amount).toFixed(2)} AED</span>
)
},
{
header: 'REMARKS',
render: (row) => (
<span className="text-xs text-gray-400 font-medium italic max-w-[250px] truncate block">{row.remarks || '-'}</span>
)
}
];
const filteredCollections = collections.filter(c =>
(c.remarks?.toLowerCase().includes(searchTerm.toLowerCase()) ||
c.type?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
c.items?.some(i => i.product?.name.toLowerCase().includes(searchTerm.toLowerCase())))
);
return (
<>
{toast && <Toast message={toast.message} type={toast.type} onClose={() => setToast(null)} />}
<main className="px-10 py-8 max-w-[1600px] mx-auto space-y-8">
<div className="flex items-center justify-between">
<h1 className="text-[40px] font-black text-[#111827] tracking-tighter">Collections</h1>
<div className="flex items-center gap-3">
{!isReceptionist && (
<div className="flex items-center bg-white border border-gray-100 rounded-xl px-4 py-2 text-sm font-bold text-gray-500 shadow-sm">
<Building size={16} className="text-gray-300 mr-2" />
<select
className="outline-none bg-transparent appearance-none cursor-pointer"
value={filterBranch}
onChange={e => setFilterBranch(e.target.value)}
>
<option value="">All Branche</option>
{branches.map(b => <option key={b.id} value={b.id}>{b.name}</option>)}
</select>
</div>
)}
<div className="flex items-center bg-white border border-gray-100 rounded-xl px-4 py-2 text-sm font-bold text-gray-500 shadow-sm">
<CreditCard size={16} className="text-gray-300 mr-2" />
<select
className="outline-none bg-transparent appearance-none cursor-pointer"
value={filterMethod}
onChange={e => setFilterMethod(e.target.value)}
>
<option value="">All Methods</option>
<option value="Cash">Cash</option>
<option value="Card">Card</option>
<option value="Online">Online</option>
</select>
</div>
<div className="flex items-center gap-2 bg-white border border-gray-100 rounded-xl px-4 py-2 text-sm font-bold text-gray-500 shadow-sm">
<input
type="date"
className="outline-none bg-transparent text-xs"
value={filterFromDate}
onChange={e => setFilterFromDate(e.target.value)}
/>
<span className="text-gray-300">-</span>
<input
type="date"
className="outline-none bg-transparent text-xs"
value={filterToDate}
onChange={e => setFilterToDate(e.target.value)}
/>
</div>
<div className="relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-300" size={18} />
<input
className="pl-12 pr-6 py-2.5 bg-white border border-gray-100 rounded-xl outline-none focus:ring-2 focus:ring-red-500/5 focus:border-red-500 transition-all font-bold text-sm text-gray-900 shadow-sm w-[300px]"
placeholder="Search items, remark"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
</div>
<button
onClick={() => setIsModalOpen(true)}
className="flex items-center gap-2 px-6 py-2.5 bg-[#FF4D4D] text-white rounded-xl font-black text-sm hover:bg-[#E60000] transition-all shadow-lg shadow-red-100"
>
<Plus size={20} />
<span>Add Collection</span>
</button>
</div>
</div>
<div className="bg-white rounded-[32px] border border-gray-100 shadow-sm overflow-hidden animate-in fade-in slide-in-from-bottom-4 duration-700">
<DataTable
columns={columns}
data={filteredCollections}
loading={loading}
actions={(row) => (
<button className="p-2 hover:bg-gray-50 rounded-lg text-blue-500 transition-all">
<Eye size={18} />
</button>
)}
emptyMessage="No collection entries found."
/>
</div>
</main>
<AddCollectionModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onSave={(newCol) => {
setCollections([newCol, ...collections]);
setToast({ message: 'Collection recorded successfully!', type: 'success' });
}}
branches={branches}
types={collectionTypes}
/>
</>
);
}