fix: 12 bin ihalelik havuz localStorage kotasını aşıp uygulamayı beyaz ekranla çökertiyordu

This commit is contained in:
2026-08-29 14:43:20 +03:00
parent 4b4f8a6951
commit 351884ac02
+53 -12
View File
@@ -6,11 +6,44 @@ import { getAllUserData, setUserData, getPlan } from '../services/account';
const AppContext = createContext(); const AppContext = createContext();
// İhale önbelleği: tam havuz (10.000+ kayıt) localStorage kotasına sığmaz.
const TENDER_CACHE_KEY = 'ihaleist_tenders';
const TENDER_CACHE_LIMIT = 300;
// localStorage her zaman yazılabilir değildir (kota dolu, gizli sekme, site
// verisi kapalı). Yazma hatası uygulamayı çökertmemeli.
const yerelKaydet = (anahtar, deger) => {
try {
localStorage.setItem(anahtar, JSON.stringify(deger));
return true;
} catch {
return false;
}
};
const yerelOku = (anahtar, varsayilan) => {
try {
const ham = localStorage.getItem(anahtar);
if (!ham) return varsayilan;
const v = JSON.parse(ham);
return v == null ? varsayilan : v;
} catch {
return varsayilan;
}
};
export const AppProvider = ({ children }) => { export const AppProvider = ({ children }) => {
const [activeTab, setActiveTab] = useState('list'); // 'list', 'bulten', 'sonuc', 'takvim', 'sozlesme', 'kdv', 'fiyat', 'kanban', 'smart_filter_builder' const [activeTab, setActiveTab] = useState('list'); // 'list', 'bulten', 'sonuc', 'takvim', 'sozlesme', 'kdv', 'fiyat', 'kanban', 'smart_filter_builder'
const [tenders, setTenders] = useState(() => { const [tenders, setTenders] = useState(() => {
const saved = localStorage.getItem('ihaleist_tenders'); // Sadece ilk boyama için küçük bir önbellek; asıl kaynak sunucudur.
return saved ? JSON.parse(saved) : []; // Bozuk/eski kayıt uygulamayı çökertmemeli.
try {
const saved = localStorage.getItem(TENDER_CACHE_KEY);
const parsed = saved ? JSON.parse(saved) : null;
return Array.isArray(parsed) ? parsed : [];
} catch {
try { localStorage.removeItem(TENDER_CACHE_KEY); } catch { /* yok say */ }
return [];
}
}); });
const [liveStats, setLiveStats] = useState({ toplamIhale: 0, bugunEklenen: 0, arsiv: 0 }); const [liveStats, setLiveStats] = useState({ toplamIhale: 0, bugunEklenen: 0, arsiv: 0 });
@@ -23,8 +56,8 @@ export const AppProvider = ({ children }) => {
const [quickFilter, setQuickFilter] = useState('all'); const [quickFilter, setQuickFilter] = useState('all');
const [savedFilters, setSavedFilters] = useState(() => { const [savedFilters, setSavedFilters] = useState(() => {
const saved = localStorage.getItem('ihaleist_saved_filters'); const v = yerelOku('ihaleist_saved_filters', []);
return saved ? JSON.parse(saved) : []; return Array.isArray(v) ? v : [];
}); });
const [isLiveMode, setIsLiveMode] = useState(true); const [isLiveMode, setIsLiveMode] = useState(true);
@@ -142,7 +175,7 @@ export const AppProvider = ({ children }) => {
try { return JSON.parse(localStorage.getItem('ihaleist_rakip_izleme') || '[]'); } catch { return []; } try { return JSON.parse(localStorage.getItem('ihaleist_rakip_izleme') || '[]'); } catch { return []; }
}); });
useEffect(() => { useEffect(() => {
localStorage.setItem('ihaleist_rakip_izleme', JSON.stringify(competitorWatchlist)); yerelKaydet('ihaleist_rakip_izleme', competitorWatchlist);
if (veriSenkron) sunucuyaYaz('rakip_izleme', competitorWatchlist); if (veriSenkron) sunucuyaYaz('rakip_izleme', competitorWatchlist);
}, [competitorWatchlist, veriSenkron]); }, [competitorWatchlist, veriSenkron]);
@@ -151,7 +184,7 @@ export const AppProvider = ({ children }) => {
try { return JSON.parse(localStorage.getItem('ihaleist_favori_kurumlar') || '[]'); } catch { return []; } try { return JSON.parse(localStorage.getItem('ihaleist_favori_kurumlar') || '[]'); } catch { return []; }
}); });
useEffect(() => { useEffect(() => {
localStorage.setItem('ihaleist_favori_kurumlar', JSON.stringify(favoriteKurumlar)); yerelKaydet('ihaleist_favori_kurumlar', favoriteKurumlar);
if (veriSenkron) sunucuyaYaz('favori_kurumlar', favoriteKurumlar); if (veriSenkron) sunucuyaYaz('favori_kurumlar', favoriteKurumlar);
}, [favoriteKurumlar, veriSenkron]); }, [favoriteKurumlar, veriSenkron]);
const toggleFavoriteKurum = (kurum) => { const toggleFavoriteKurum = (kurum) => {
@@ -171,7 +204,7 @@ export const AppProvider = ({ children }) => {
]; ];
}); });
useEffect(() => { useEffect(() => {
localStorage.setItem('ihaleist_ekip', JSON.stringify(teamMembers)); yerelKaydet('ihaleist_ekip', teamMembers);
if (veriSenkron) sunucuyaYaz('ekip', teamMembers); if (veriSenkron) sunucuyaYaz('ekip', teamMembers);
}, [teamMembers, veriSenkron]); }, [teamMembers, veriSenkron]);
const addTeamMember = (m) => setTeamMembers((prev) => [...prev, { id: 'tm-' + Date.now(), ...m }]); const addTeamMember = (m) => setTeamMembers((prev) => [...prev, { id: 'tm-' + Date.now(), ...m }]);
@@ -182,7 +215,7 @@ export const AppProvider = ({ children }) => {
try { return JSON.parse(localStorage.getItem('ihaleist_belgeler') || '[]'); } catch { return []; } try { return JSON.parse(localStorage.getItem('ihaleist_belgeler') || '[]'); } catch { return []; }
}); });
useEffect(() => { useEffect(() => {
localStorage.setItem('ihaleist_belgeler', JSON.stringify(vaultDocuments)); yerelKaydet('ihaleist_belgeler', vaultDocuments);
}, [vaultDocuments]); }, [vaultDocuments]);
const [reminderSettings, setReminderSettings] = useState(() => { const [reminderSettings, setReminderSettings] = useState(() => {
@@ -198,7 +231,7 @@ export const AppProvider = ({ children }) => {
} catch { return def; } } catch { return def; }
}); });
useEffect(() => { useEffect(() => {
localStorage.setItem('ihaleist_reminder', JSON.stringify(reminderSettings)); yerelKaydet('ihaleist_reminder', reminderSettings);
if (veriSenkron) sunucuyaYaz('reminder', reminderSettings); if (veriSenkron) sunucuyaYaz('reminder', reminderSettings);
}, [reminderSettings, veriSenkron]); }, [reminderSettings, veriSenkron]);
@@ -266,7 +299,7 @@ export const AppProvider = ({ children }) => {
}); });
useEffect(() => { useEffect(() => {
localStorage.setItem('ihaleist_ai_model_config', JSON.stringify(aiModelConfig)); yerelKaydet('ihaleist_ai_model_config', aiModelConfig);
}, [aiModelConfig]); }, [aiModelConfig]);
const getModelById = (id) => AI_MODEL_CATALOG.find(m => m.id === id); const getModelById = (id) => AI_MODEL_CATALOG.find(m => m.id === id);
@@ -353,12 +386,20 @@ export const AppProvider = ({ children }) => {
const markNotificationsRead = () => setNotifications((prev) => prev.map((n) => ({ ...n, read: true }))); const markNotificationsRead = () => setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
// Persist state to localstorage // Persist state to localstorage
// Havuz on binlerce kayda çıkabiliyor; tamamı localStorage kotasını (≈5 MB)
// aşar ve yakalanmazsa uygulama beyaz ekranla çöker. Bu yüzden yalnızca ilk
// boyamaya yetecek kadarını, hataya dayanıklı biçimde saklıyoruz.
useEffect(() => { useEffect(() => {
localStorage.setItem('ihaleist_tenders', JSON.stringify(tenders)); try {
localStorage.setItem(TENDER_CACHE_KEY, JSON.stringify(tenders.slice(0, TENDER_CACHE_LIMIT)));
} catch {
// Kota dolduysa önbelleği bırak — veri zaten sunucudan geliyor.
try { localStorage.removeItem(TENDER_CACHE_KEY); } catch { /* yok say */ }
}
}, [tenders]); }, [tenders]);
useEffect(() => { useEffect(() => {
localStorage.setItem('ihaleist_saved_filters', JSON.stringify(savedFilters)); yerelKaydet('ihaleist_saved_filters', savedFilters);
if (veriSenkron) sunucuyaYaz('saved_filters', savedFilters); if (veriSenkron) sunucuyaYaz('saved_filters', savedFilters);
}, [savedFilters, veriSenkron]); }, [savedFilters, veriSenkron]);