fix: provide visible prepared Excel download link
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
09:28:30 itibarıyla 5 ihale için Excel hazır.Excel dosyasını indir — ihaleist_ihale_listesi_1788676110092.xlsx
|
||||||
|
İndirme bağlantısı doğrudan tıklandı. Diskte dosya doğrulaması yapılmadı.
|
||||||
@@ -272,3 +272,10 @@ SQLite snapshot, referanslı belge kopyası, SHA-256 manifesti, bütünlük kont
|
|||||||
- Kanıtlar: `evidence/qa-integrated-search.txt`, `evidence/qa-integrated-form.txt` (yalnız yapay test firma verisi).
|
- Kanıtlar: `evidence/qa-integrated-search.txt`, `evidence/qa-integrated-form.txt` (yalnız yapay test firma verisi).
|
||||||
- Excel düğmesi sonrası arayüz hata göstermedi; ancak download olayı 3 saniye ve yeniden denemede 15 saniye içinde gözlenemedi. İndirme GEÇTİ sayılmadı; tarayıcı olay izlemesi mi uygulama indirme yolu mu ayrıştırılmalı. Önceki dosya üretme/yeniden okuma testi bu uçtan uca açığı kapatmaz.
|
- Excel düğmesi sonrası arayüz hata göstermedi; ancak download olayı 3 saniye ve yeniden denemede 15 saniye içinde gözlenemedi. İndirme GEÇTİ sayılmadı; tarayıcı olay izlemesi mi uygulama indirme yolu mu ayrıştırılmalı. Önceki dosya üretme/yeniden okuma testi bu uçtan uca açığı kapatmaz.
|
||||||
- Bu kanıt resmi beş ihale dosyası kabulü değildir; sentetik yerel regresyondur. Gerçek mobil viewport, gerçek belge ve resmi gönderim doğrulaması açık.
|
- Bu kanıt resmi beş ihale dosyası kabulü değildir; sentetik yerel regresyondur. Gerçek mobil viewport, gerçek belge ve resmi gönderim doğrulaması açık.
|
||||||
|
|
||||||
|
### D03 / D09 — Görünür Excel indirme bağlantısı
|
||||||
|
|
||||||
|
- Liste dışa aktarımı artık “Excel Hazırla” → dosya adı/kayıt sayısı/hazırlanma saati → doğrudan indirme bağlantısı akışını kullanıyor. Asenkron üretim sonrası görünmez programatik tıklamaya bağımlılık kaldırıldı; hazır dosya tekrar indirilebilir.
|
||||||
|
- Oluşan blob bağlantısı yeni dosya hazırlanırken veya ekran kapanırken temizleniyor. Oturum değişince eski hazır dosya bağlantısı gösterilmiyor. Çıktı tıklama anındaki sonuç kümesini temsil ediyor; yeni filtre için yeniden hazırlama gerekiyor.
|
||||||
|
- Tarayıcıda beş sentetik ihale için dosya hazır durumu ve `.xlsx` bağlantısı görüldü, doğrudan bağlantı tıklandı. Kanıt: `evidence/qa-excel-ready-link.txt`. Üretim derlemesi geçti; dosya içeriği için önceki XLSX roundtrip regresyonu geçerlidir.
|
||||||
|
- İndirme geçmişini kontrol etmek üzere chrome://downloads erişimi tarayıcı URL güvenlik politikası tarafından engellendi; başka yoldan bu geçmişe erişilmedi. Diskte nihai dosya ve masaüstü Excel açılışı doğrulanmış sayılmıyor. Önceki indirme olayı zaman aşımının kesin kök nedeni henüz belirlenmedi.
|
||||||
|
|||||||
@@ -101,12 +101,16 @@ export const TenderList = () => {
|
|||||||
|
|
||||||
const [exporting,setExporting] = useState(false);
|
const [exporting,setExporting] = useState(false);
|
||||||
const [exportError,setExportError] = useState(null);
|
const [exportError,setExportError] = useState(null);
|
||||||
|
const [preparedExport,setPreparedExport] = useState(null);
|
||||||
|
const preparedUrl = useRef(null);
|
||||||
const exportPending = useRef(false);
|
const exportPending = useRef(false);
|
||||||
const mounted = useRef(true);
|
const mounted = useRef(true);
|
||||||
useEffect(()=>{mounted.current=true;return()=>{mounted.current=false;};},[]);
|
useEffect(()=>{mounted.current=true;return()=>{mounted.current=false;if(preparedUrl.current)URL.revokeObjectURL(preparedUrl.current);};},[]);
|
||||||
const handleExportExcel = async () => {
|
const handleExportExcel = async () => {
|
||||||
if (exportPending.current) return;
|
if (exportPending.current) return;
|
||||||
exportPending.current = true; setExporting(true); setExportError(null);
|
exportPending.current = true; setExporting(true); setExportError(null);
|
||||||
|
if(preparedUrl.current) URL.revokeObjectURL(preparedUrl.current);
|
||||||
|
preparedUrl.current=null;setPreparedExport(null);
|
||||||
const session = getToken();
|
const session = getToken();
|
||||||
// Export the full filtered result at click time, including rows below the fold.
|
// Export the full filtered result at click time, including rows below the fold.
|
||||||
const selected = filteredTenders.map(t=>({...t}));
|
const selected = filteredTenders.map(t=>({...t}));
|
||||||
@@ -114,10 +118,9 @@ export const TenderList = () => {
|
|||||||
const bytes = await createTenderWorkbook(selected);
|
const bytes = await createTenderWorkbook(selected);
|
||||||
if (!mounted.current || getToken() !== session) return;
|
if (!mounted.current || getToken() !== session) return;
|
||||||
const url = URL.createObjectURL(new Blob([bytes], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}));
|
const url = URL.createObjectURL(new Blob([bytes], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}));
|
||||||
const link = document.createElement('a');
|
preparedUrl.current=url;
|
||||||
link.href = url; link.download = `ihaleist_ihale_listesi_${Date.now()}.xlsx`;
|
setPreparedExport({url,name:`ihaleist_ihale_listesi_${Date.now()}.xlsx`,count:selected.length,
|
||||||
document.body.appendChild(link); link.click(); link.remove();
|
query:searchQuery.trim(),session,createdAt:new Date().toLocaleTimeString('tr-TR')});
|
||||||
setTimeout(()=>URL.revokeObjectURL(url),1000);
|
|
||||||
} catch (error) { if (mounted.current && getToken() === session) setExportError(error.message); }
|
} catch (error) { if (mounted.current && getToken() === session) setExportError(error.message); }
|
||||||
finally { exportPending.current=false; if (mounted.current) setExporting(false); }
|
finally { exportPending.current=false; if (mounted.current) setExporting(false); }
|
||||||
};
|
};
|
||||||
@@ -165,12 +168,16 @@ export const TenderList = () => {
|
|||||||
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"
|
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" />
|
<Download className="w-4 h-4" />
|
||||||
<span>{exporting ? 'Excel hazırlanıyor…' : 'Excel İndir'}</span>
|
<span>{exporting ? 'Excel hazırlanıyor…' : 'Excel Hazırla'}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{sortBy === 'newest' && <p className="text-xs text-slate-500">Yayın tarihi bilinmeyen kayıtlar sonda gösterilir. Aynı gün içinde saati bilinen kayıtlar önce gelir.</p>}
|
{sortBy === 'newest' && <p className="text-xs text-slate-500">Yayın tarihi bilinmeyen kayıtlar sonda gösterilir. Aynı gün içinde saati bilinen kayıtlar önce gelir.</p>}
|
||||||
|
{preparedExport && preparedExport.session === getToken() && <div role="status" className="rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-xs space-y-1">
|
||||||
|
<p>{preparedExport.createdAt} itibarıyla {preparedExport.count} ihale için Excel hazır{preparedExport.query ? ` — arama: ${preparedExport.query}` : ''}.</p>
|
||||||
|
<a href={preparedExport.url} download={preparedExport.name} className="font-bold text-emerald-800 underline">Excel dosyasını indir — {preparedExport.name}</a>
|
||||||
|
</div>}
|
||||||
{exportError && <p role="alert" className="text-sm text-red-700">{exportError}</p>}
|
{exportError && <p role="alert" className="text-sm text-red-700">{exportError}</p>}
|
||||||
{/* Tender Cards List */}
|
{/* Tender Cards List */}
|
||||||
{gorunenTenders.length === 0 ? (
|
{gorunenTenders.length === 0 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user