173 lines
7.4 KiB
React
173 lines
7.4 KiB
React
import React, { useState, useMemo } from 'react';
|
||
import { useApp } from '../context/AppContext';
|
||
import { TenderCard } from './TenderCard';
|
||
import { Download, SlidersHorizontal, Layers, ChevronLeft, ChevronRight } from 'lucide-react';
|
||
import confetti from 'canvas-confetti';
|
||
|
||
export const TenderList = () => {
|
||
const {
|
||
tenders,
|
||
searchQuery,
|
||
selectedCity,
|
||
selectedAlimTuru,
|
||
selectedSektor,
|
||
selectedKurumTipi,
|
||
quickFilter,
|
||
onlyValidOnly
|
||
} = useApp();
|
||
|
||
const [sortBy, setSortBy] = useState('newest'); // 'newest', 'urgent', 'distant'
|
||
const [currentPage, setCurrentPage] = useState(1);
|
||
const itemsPerPage = 10;
|
||
|
||
// Filter logic
|
||
const filteredTenders = useMemo(() => {
|
||
return tenders.filter(tender => {
|
||
// Search Query
|
||
if (searchQuery) {
|
||
const q = searchQuery.toLowerCase();
|
||
const matchTitle = tender.baslik.toLowerCase().includes(q);
|
||
const matchKurum = tender.kurum.toLowerCase().includes(q);
|
||
const matchIkn = tender.ikn.toLowerCase().includes(q);
|
||
const matchItems = tender.items && tender.items.some(i => i.ad.toLowerCase().includes(q));
|
||
if (!matchTitle && !matchKurum && !matchIkn && !matchItems) return false;
|
||
}
|
||
|
||
// Quick Filter
|
||
if (quickFilter === 'dogrudan_temin' && tender.alimTuru !== 'Doğrudan Temin') return false;
|
||
if (quickFilter === 'bugun_eklenenler' && !(tender.eklenmeZamani.includes('önce') || tender.eklenmeZamani.includes('az'))) return false;
|
||
if (quickFilter === 'incelenenler' && !tender.incelendi) return false;
|
||
if (quickFilter === 'incelenmedi' && tender.incelendi) return false;
|
||
if (quickFilter === 'takiptekiler' && !tender.takipte) return false;
|
||
if (quickFilter === 'sozlesmeler' && tender.durum !== 'Sözleşme İmzalandı') return false;
|
||
if (quickFilter === 'sonuclananlar' && tender.durum !== 'Sonuçlandı') return false;
|
||
if (quickFilter === 'iptal' && tender.durum !== 'İptal') return false;
|
||
|
||
// Dropdown Filters
|
||
if (selectedCity && tender.sehir !== selectedCity) return false;
|
||
if (selectedAlimTuru && tender.alimTuru !== selectedAlimTuru) return false;
|
||
if (selectedSektor && tender.sektor !== selectedSektor) return false;
|
||
if (selectedKurumTipi && tender.kurumTipi !== selectedKurumTipi) return false;
|
||
|
||
return true;
|
||
}).sort((a, b) => {
|
||
if (sortBy === 'newest') return b.id.localeCompare(a.id);
|
||
if (sortBy === 'urgent') return a.tarih.localeCompare(b.tarih);
|
||
if (sortBy === 'distant') return b.tarih.localeCompare(a.tarih);
|
||
return 0;
|
||
});
|
||
}, [tenders, searchQuery, selectedCity, selectedAlimTuru, selectedSektor, selectedKurumTipi, quickFilter, sortBy]);
|
||
|
||
const totalPages = Math.max(1, Math.ceil(filteredTenders.length / itemsPerPage));
|
||
const paginatedTenders = filteredTenders.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
|
||
|
||
const handleExportExcel = () => {
|
||
confetti({ particleCount: 80, spread: 60, origin: { y: 0.6 } });
|
||
|
||
const headers = ['IKN', 'Baslik', 'Kurum', 'Sehir', 'Tarih', 'Alim Turu', 'Sektor'];
|
||
const rows = filteredTenders.map(t => [
|
||
t.ikn,
|
||
`"${t.baslik.replace(/"/g, '""')}"`,
|
||
`"${t.kurum.replace(/"/g, '""')}"`,
|
||
t.sehir,
|
||
t.tarih,
|
||
t.alimTuru,
|
||
t.sektor
|
||
]);
|
||
|
||
const csvContent = 'data:text/csv;charset=utf-8,\uFEFF' + [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
|
||
const encodedUri = encodeURI(csvContent);
|
||
const link = document.createElement('a');
|
||
link.setAttribute('href', encodedUri);
|
||
link.setAttribute('download', `ihaleist_ihale_listesi_${Date.now()}.csv`);
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
};
|
||
|
||
return (
|
||
<div className="flex-1 space-y-4">
|
||
|
||
{/* Header bar above list */}
|
||
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-xs flex flex-col sm:flex-row items-center justify-between gap-3">
|
||
<div className="flex items-center gap-2">
|
||
<Layers className="w-5 h-5 text-blue-600" />
|
||
<h2 className="font-extrabold text-base text-slate-900">
|
||
İhale Listesi <span className="text-slate-500 font-medium text-xs">({filteredTenders.length.toLocaleString('tr-TR')} Sonuç)</span>
|
||
</h2>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-3 w-full sm:w-auto">
|
||
<select
|
||
value={sortBy}
|
||
onChange={(e) => setSortBy(e.target.value)}
|
||
className="bg-slate-50 border border-slate-300 rounded-lg px-3 py-1.5 text-xs font-semibold text-slate-700 focus:ring-2 focus:ring-blue-500"
|
||
>
|
||
<option value="newest">En Yeni Eklenenler</option>
|
||
<option value="urgent">Tarihi Yaklaşanlar (Acil)</option>
|
||
<option value="distant">Tarihi Uzak Olanlar</option>
|
||
</select>
|
||
|
||
<button
|
||
onClick={handleExportExcel}
|
||
className="bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs px-3.5 py-2 rounded-lg flex items-center gap-1.5 transition-colors shadow-xs shrink-0"
|
||
>
|
||
<Download className="w-4 h-4" />
|
||
<span>Excel İndir</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Tender Cards List */}
|
||
{paginatedTenders.length === 0 ? (
|
||
<div className="bg-white rounded-xl border border-slate-200 p-12 text-center shadow-xs">
|
||
<SlidersHorizontal className="w-12 h-12 text-slate-300 mx-auto mb-3" />
|
||
<h3 className="text-base font-bold text-slate-800">Arama kriterlerine uygun ihale bulunamadı</h3>
|
||
<p className="text-xs text-slate-500 mt-1">Lütfen arama kelimenizi değiştirin veya filtreleri temizleyin.</p>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-3.5">
|
||
{paginatedTenders.map(tender => (
|
||
<TenderCard key={tender.id} tender={tender} />
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* Pagination Bar */}
|
||
{totalPages > 1 && (
|
||
<div className="bg-white rounded-xl border border-slate-200 p-3 shadow-xs flex items-center justify-between text-xs font-medium text-slate-600">
|
||
<span>Sayfa {currentPage} / {totalPages} (Toplam {filteredTenders.length} Kayıt)</span>
|
||
<div className="flex items-center gap-1">
|
||
<button
|
||
disabled={currentPage === 1}
|
||
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
|
||
className="p-1.5 rounded-lg border border-slate-300 disabled:opacity-40 hover:bg-slate-50"
|
||
>
|
||
<ChevronLeft className="w-4 h-4" />
|
||
</button>
|
||
{Array.from({ length: totalPages }).map((_, i) => (
|
||
<button
|
||
key={i}
|
||
onClick={() => setCurrentPage(i + 1)}
|
||
className={`w-8 h-8 rounded-lg font-bold transition-colors ${
|
||
currentPage === i + 1 ? 'bg-blue-600 text-white shadow-xs' : 'hover:bg-slate-100 text-slate-700'
|
||
}`}
|
||
>
|
||
{i + 1}
|
||
</button>
|
||
))}
|
||
<button
|
||
disabled={currentPage === totalPages}
|
||
onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
|
||
className="p-1.5 rounded-lg border border-slate-300 disabled:opacity-40 hover:bg-slate-50"
|
||
>
|
||
<ChevronRight className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
</div>
|
||
);
|
||
};
|