Files
ihaleist/src/components/TenderList.jsx
T

173 lines
7.4 KiB
React
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.
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>
);
};