fix: export filtered tender list as real XLSX

This commit is contained in:
2026-09-06 09:19:41 +03:00
parent 9c7055a543
commit f593d5e372
4 changed files with 75 additions and 26 deletions
+26 -26
View File
@@ -2,7 +2,8 @@ import React, { useState, useMemo, useEffect, useRef, useCallback } from 'react'
import { useApp } from '../context/AppContext';
import { TenderCard } from './TenderCard';
import { Download, SlidersHorizontal, Layers, Loader2 } from 'lucide-react';
import confetti from 'canvas-confetti';
import { createTenderWorkbook } from '../services/tenderWorkbook';
import { getToken } from '../services/auth';
import { useDeadlineClock } from '../hooks/useDeadlineClock';
import { acikIhale, hizliFiltreGecer, GECMIS_FILTRELER, compareTenderDeadlines } from '../services/ihaleTarih';
@@ -107,29 +108,27 @@ export const TenderList = () => {
return () => gozlemci.disconnect();
}, [dahaVar, dahaGoster]);
const handleExportExcel = () => {
confetti({ particleCount: 80, spread: 60, origin: { y: 0.6 } });
const headers = ['IKN', 'Baslik', 'Kurum', 'Sehir', 'Tarih', 'Alim Turu', 'Sektor', 'CPV'];
const rows = filteredTenders.map(t => [
t.ikn,
`"${t.baslik.replace(/"/g, '""')}"`,
`"${t.kurum.replace(/"/g, '""')}"`,
t.sehir,
t.tarih,
t.alimTuru,
t.sektor,
`"${(t.cpvLabel || (t.cpv || []).join(', ') || '').replace(/"/g, '""')}"`
]);
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);
const [exporting,setExporting] = useState(false);
const [exportError,setExportError] = useState(null);
const exportPending = useRef(false);
const mounted = useRef(true);
useEffect(()=>{mounted.current=true;return()=>{mounted.current=false;};},[]);
const handleExportExcel = async () => {
if (exportPending.current) return;
exportPending.current = true; setExporting(true); setExportError(null);
const session = getToken();
// Export the full filtered result at click time, including rows below the fold.
const selected = filteredTenders.map(t=>({...t}));
try {
const bytes = await createTenderWorkbook(selected);
if (!mounted.current || getToken() !== session) return;
const url = URL.createObjectURL(new Blob([bytes], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}));
const link = document.createElement('a');
link.href = url; link.download = `ihaleist_ihale_listesi_${Date.now()}.xlsx`;
document.body.appendChild(link); link.click(); link.remove();
setTimeout(()=>URL.revokeObjectURL(url),1000);
} catch (error) { if (mounted.current && getToken() === session) setExportError(error.message); }
finally { exportPending.current=false; if (mounted.current) setExporting(false); }
};
return (
@@ -171,15 +170,16 @@ export const TenderList = () => {
</select>
<button
onClick={handleExportExcel}
onClick={handleExportExcel} disabled={exporting || !filteredTenders.length}
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>
<span>{exporting ? 'Excel hazırlanıyor…' : 'Excel İndir'}</span>
</button>
</div>
</div>
{exportError && <p role="alert" className="text-sm text-red-700">{exportError}</p>}
{/* Tender Cards List */}
{gorunenTenders.length === 0 ? (
<div className="bg-white rounded-xl border border-slate-200 p-12 text-center shadow-xs">
+21
View File
@@ -0,0 +1,21 @@
// User/source text stays a string cell, including leading '=' and embedded newlines.
export async function createTenderWorkbook(tenders) {
if (!Array.isArray(tenders) || !tenders.length) throw new Error('Dışa aktarılacak ihale bulunamadı.');
const {default:ExcelJS} = await import('exceljs');
const book = new ExcelJS.Workbook();
book.creator = 'İhaleist';
const sheet = book.addWorksheet('İhale Listesi');
sheet.columns = [
['İKN',20],['Başlık',65],['Kurum',45],['Şehir',20],['İhale tarihi',18],
['İhale saati',15],['Alım türü',25],['Sektör',30],['CPV',35],['Kaynak',55],
].map(([header,width])=>({header,width}));
for (const t of tenders) {
sheet.addRow([t.ikn,t.baslik,t.kurum,t.sehir,t.tarih,t.saat,t.alimTuru,t.sektor,
t.cpvLabel || (Array.isArray(t.cpv) ? t.cpv.join(', ') : t.cpv),t.kaynakUrl].map(v=>String(v ?? '')));
}
sheet.views = [{state:'frozen',ySplit:1}];
sheet.autoFilter = {from:'A1',to:`J${tenders.length+1}`};
sheet.getRow(1).font = {bold:true};
sheet.eachRow(row=>{row.alignment={vertical:'top',wrapText:true};});
return book.xlsx.writeBuffer();
}