import { getToken } from '../../services/auth'; import { sessionGuard } from '../../services/sessionGuard'; import { readableDocument, retryDocumentExtraction } from '../../services/storedDocument'; import { documentProcessingLabel } from '../../services/documentProcessing'; import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useApp } from '../../context/AppContext'; import { listDocuments, uploadDocument, deleteDocument, downloadDocument, formatBayt, updateDocumentMeta } from '../../services/account'; import { FolderKanban, Upload, Trash2, Download, Loader2, AlertTriangle, FileText, HardDrive, Layers, Package, CheckCircle2, XCircle, CalendarClock } from 'lucide-react'; const KATEGORILER = ['Yasal Belgeler', 'Kalite Belgeleri', 'Mali Belgeler', 'Resmi Kayıtlar', 'Teknik Belgeler', 'Diğer']; /** * Gerçek belge deposu — dosyalar sunucudaki kalıcı diske yazılır, * boyut gerçek bayt cinsindendir ve plan kotası sunucu tarafında uygulanır. * (Önceden dosya hiç yüklenmiyordu; boyut Math.random ile uyduruluyordu.) */ export const DocumentVault = () => { const { authUser } = useApp(); return ; }; const DocumentVaultSession = ({signedIn}) => { const live = useRef(true); const readLifetime = useRef(new AbortController()); const session = useRef(sessionGuard(getToken)); const current = () => live.current && session.current(); useEffect(() => { live.current = true; readLifetime.current = new AbortController(); return () => { live.current = false; readLifetime.current.abort(); }; }, []); const useOwnedState = (initial) => { const [value, setValue] = useState(initial); const setOwned = useCallback((next) => { if (current()) setValue(next); }, []); return [value, setOwned]; }; const [durum, setDurum] = useOwnedState(null); const [busy, setBusy] = useOwnedState(false); const [hata, setHata] = useOwnedState(null); const [islemeBilgi, setIslemeBilgi] = useOwnedState(null); const [kategori, setKategori] = useOwnedState(KATEGORILER[0]); const dosyaRef = useRef(null); const downloads = useRef(new Set()); const [downloading,setDownloading] = useOwnedState([]); const indir = async file => { if (!current() || downloads.current.has(file.id)) return; downloads.current.add(file.id);setDownloading([...downloads.current]);setHata(null); try { await downloadDocument(file.id,file.ad,{signal:readLifetime.current.signal}); } catch(error) { if(!readLifetime.current.signal.aborted)setHata(error.message); } finally { downloads.current.delete(file.id);setDownloading([...downloads.current]); } }; const yukle = () => { if (!signedIn || !current()) return; setHata(null); listDocuments().then(setDurum).catch((e) => setHata(e.message)); }; useEffect(yukle, [signedIn]); const yenidenIsle = async (document) => { if (!current()) return; setBusy(true); setHata(null); setIslemeBilgi(null); try { const result = await retryDocumentExtraction(document,{signal:readLifetime.current.signal}); setIslemeBilgi(`${document.ad}: ${result.sayi || 0} kalem çıkarıldı.`); } catch (error) { setHata(error.message); } finally { try { if (current()) setDurum(await listDocuments()); } catch { setHata(previous => [previous, 'Belge listesi yenilenemedi.'].filter(Boolean).join(' ')); } setBusy(false); } }; const dosyaSecildi = async (e) => { const f = e.target.files?.[0]; if (!f) return; setBusy(true); setHata(null); try { setDurum(await uploadDocument(f, kategori)); } catch (err) { setHata(err.message); } finally { setBusy(false); if (dosyaRef.current) dosyaRef.current.value = ''; } }; // Toplu ihale dokümanı: her dosyanın İKN'si METNİNDEN bulunur, kullanıcıya // sorulmaz; kalemler otomatik havuza yazılır. docx/xlsx/zip sunucuda, // PDF tarayıcıda (pdfjs) okunur. Onlarca şartnameyi tek seferde atmak için. const topluRef = useRef(null); const [toplu, setToplu] = useOwnedState([]); // [{ad, durum, ikn, kalem, hata}] const topluSecildi = async (e) => { const dosyalar = [...(e.target.files || [])]; if (!dosyalar.length) return; setBusy(true); setHata(null); setToplu(dosyalar.map((f) => ({ ad: f.name, durum: 'bekliyor' }))); const guncelle = (i, veri) => setToplu((o) => o.map((x, j) => (j === i ? { ...x, ...veri } : x))); for (let i = 0; i < dosyalar.length; i++) { if (!current()) break; const f = dosyalar[i]; guncelle(i, { durum: 'yukleniyor' }); try { if (f.size > 10 * 1024 * 1024) throw new Error('10 MB sınırı'); const pdfGibi = /\.(pdf|txt|md)$/i.test(f.name); let ikn = null; let metin = null; if (pdfGibi) { const { extractText } = await import('../../services/pdfText'); const { iknCikar } = await import('../../services/kalem'); metin = (await extractText(f)).text; ikn = iknCikar(metin); } if (!current()) break; const cevap = await uploadDocument(f, 'İhale Dokümanı', ikn, { otoIkn: true, documentType:'ihale' }); if (!current()) break; ikn = cevap?.ikn || ikn; let kalem = cevap?.kalem?.sayi || null; let kalemHata = cevap.processing?.status === 'error' ? cevap.processing.error : null; if (ikn && !kalem && metin) { try { const { kalemCikar } = await import('../../services/kalem'); if (!current()) break; kalem = (await kalemCikar({ ikn, metin, dosya: f.name, documentId:cevap.id })).sayi || null; } catch (error) { kalemHata = error.message; } } guncelle(i, { durum: 'tamam', ikn, kalem, kalemHata }); setDurum(cevap); try { if (current()) setDurum(await listDocuments()); } catch { setHata('Dosya kayıtlı; güncel belge listesi alınamadı. Listeyi yeniden yükleyin.'); } } catch (err) { guncelle(i, { durum: 'hata', hata: err.message }); } } setBusy(false); if (topluRef.current) topluRef.current.value = ''; }; // Şirket belgesi süre takibi: imza sirküleri, oda kayıt, borcu yoktur — // hepsinin süresi dolar, kimse teklif hazırlanana kadar fark etmez. const sureYaz = async (id, tarih) => { if (!current()) return; setHata(null); try { setDurum(await updateDocumentMeta(id, { sonKullanma: tarih || null })); } catch (e) { setHata(e.message); } }; const kalanGun = (b) => { const value=String(b.son_kullanma || ''); if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null; const date=new Date(value+'T00:00:00Z'); if (Number.isNaN(date.getTime()) || date.toISOString().slice(0,10)!==value) return null; return Math.ceil((date.getTime() - Date.now()) / 86400000); }; const gecersizSonKullanma = (b) => Boolean(b.son_kullanma) && kalanGun(b) === null; const suresiDolan = (durum?.belgeler || []).filter((b) => kalanGun(b) != null && kalanGun(b) < 0); const yakinda = (durum?.belgeler || []).filter((b) => kalanGun(b) != null && kalanGun(b) >= 0 && kalanGun(b) <= 30); const sil = async (id) => { if (!current()) return; setBusy(true); setHata(null); try { setDurum(await deleteDocument(id)); } catch (e) { setHata(e.message); } finally { setBusy(false); } }; const k = durum?.kullanim; const h = durum?.haklar; const doluluk = h?.bayt ? Math.min(100, Math.round(((k?.bayt || 0) / h.bayt) * 100)) : 0; return (
{hata && }

Belge Kasası

İhale dosyalarınıza her cihazdan erişin — belgeler sunucuda saklanır, tarayıcınızda değil.

{/* Kullanım */} {durum && (
Kullanım {k.adet}{h?.adet != null ? ` / ${h.adet}` : ''} belge · {formatBayt(k.bayt)} {h?.bayt != null && ` / ${formatBayt(h.bayt)}`}
{h?.bayt != null && (
90 ? 'bg-red-500' : doluluk > 70 ? 'bg-amber-500' : 'bg-teal-600'}`} style={{ width: `${doluluk}%` }} />
)}
)} {/* Yükleme */}

Belge Yükleyin

İmza sirküleri, sertifikalar, vergi levhası… (en fazla 10 MB)

{/* Süre uyarısı */} {(suresiDolan.length > 0 || yakinda.length > 0) && (
{suresiDolan.length > 0 && (

Süresi dolmuş: {suresiDolan.map((b) => b.ad).join(', ')}

)} {yakinda.length > 0 && (

30 gün içinde doluyor: {yakinda.map((b) => `${b.ad} (${kalanGun(b)} gün)`).join(', ')}

)}

Teklif zarfına süresi dolmuş belge girerse teklif değerlendirme dışı kalır. Yenileyip yeni tarihi girin.

)} {/* Toplu ihale dokümanı yükleme */}

İhale Dokümanlarını Toplu Yükle

EKAP'tan indirdiğiniz şartname/doküman dosyalarını (PDF, DOCX, XLSX, ZIP) hepsini birden seçin. Her dosyanın İKN'si metninden bulunur, ihaleye bağlanır ve teklif cetveli kalemleri otomatik çıkarılır — o ihale herkesin kartında kalemleriyle görünür.

{toplu.length > 0 && (
    {toplu.map((t, i) => (
  • {t.durum === 'tamam' ? : t.durum === 'hata' ? : } {t.ad} {t.durum === 'tamam' && ( {t.ikn ? {t.ikn} : İKN bulunamadı — ihaleye bağlanmadı} {t.kalemHata && Dosya kayıtlı; kalem aktarımı başarısız: {t.kalemHata}} {t.kalem ? {t.kalem} kalem : null} )} {t.durum === 'hata' && {t.hata}}
  • ))}
)}
{hata && (

{hata}

)} {/* Liste */} {!durum && !hata && !hata ? (
yükleniyor…
) : durum.belgeler.length === 0 ? (

Henüz belge yüklemediniz

) : (
{islemeBilgi &&

{islemeBilgi}

} {durum.belgeler.map((d) => (

{d.ad}

{d.tur === 'ihale' ? 'İhale dokümanı' : 'Özel firma belgesi'} · {d.kategori || 'Diğer'} · {formatBayt(d.boyut)}

{new Date(d.olusturma).toLocaleString('tr-TR')} {documentProcessingLabel(d.isleme)}

{d.tur === 'ihale' && d.ikn && readableDocument(d.ad) && ( )}
))}
)}
); };