Require explicit persisted acknowledgement of notifications
This commit is contained in:
+38
-17
@@ -1,3 +1,4 @@
|
||||
import {mergeServerNotifications,acknowledgeNotifications} from '../services/notifications';
|
||||
import { searchTender } from '../services/searchTender';
|
||||
import { normalizeTenderSearch, matchingItemResults } from '../services/tenderSearch';
|
||||
import React, {createContext, useContext, useState, useEffect, useMemo, useRef } from 'react';
|
||||
@@ -159,6 +160,7 @@ export const AppProvider = ({ children }) => {
|
||||
|
||||
// Giriş yapınca kullanıcı verisini SUNUCUDAN yükle (localStorage'ı ezer)
|
||||
useEffect(() => {
|
||||
notificationVersion.current++;notificationMutation.current=null;seenServerNotifications.current.clear();setNotificationError(null);setNotificationBusy(false);
|
||||
aiRunRef.current=null;setAiAnalyzing(false);setAiJobStatus('');
|
||||
setVeriSenkron(false); setPlan(null); setSaveError(null);
|
||||
setFavoriteKurumlar([]); setTeamMembers([]); setSavedFilters([]); setCompetitorWatchlist([]);
|
||||
@@ -564,11 +566,17 @@ export const AppProvider = ({ children }) => {
|
||||
};
|
||||
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [notificationError,setNotificationError]=useState(null);
|
||||
const [notificationBusy,setNotificationBusy]=useState(false);
|
||||
const notificationVersion=useRef(0),notificationMutation=useRef(null),seenServerNotifications=useRef(new Set());
|
||||
|
||||
// Gerçek bildirim: listeye ekler; sessiz saat dışında ve push açıksa tarayıcı
|
||||
// bildirimi (Notification API) da gösterir.
|
||||
const notify = (title, text) => {
|
||||
setNotifications((prev) => [{ id: 'n-' + Date.now(), title, text, time: 'az önce', read: false }, ...prev].slice(0, 30));
|
||||
pushNotification(title,text);
|
||||
};
|
||||
const pushNotification=(title,text)=>{
|
||||
if (isQuietNow() || !reminderSettings.pushEnabled) return;
|
||||
try {
|
||||
if ('Notification' in window && Notification.permission === 'granted') {
|
||||
@@ -581,7 +589,31 @@ export const AppProvider = ({ children }) => {
|
||||
if (Notification.permission === 'granted') return 'granted';
|
||||
try { return await Notification.requestPermission(); } catch { return 'denied'; }
|
||||
};
|
||||
const markNotificationsRead = () => setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
|
||||
const refreshNotifications=async()=>{
|
||||
const session=getToken(),version=++notificationVersion.current;
|
||||
setNotificationError(null);
|
||||
try {
|
||||
const rows=await bildirimleriGetir();
|
||||
if(getToken()!==session||version!==notificationVersion.current)return;
|
||||
const checked=mergeServerNotifications([],rows);
|
||||
const fresh=checked.filter(row=>!row.read&&!seenServerNotifications.current.has(row.id));
|
||||
checked.forEach(row=>seenServerNotifications.current.add(row.id));
|
||||
fresh.slice(0,5).forEach(row=>pushNotification(row.title,row.text));
|
||||
setNotifications(current=>[...checked,...current.filter(row=>!row.serverId)].slice(0,80));
|
||||
}catch(error){if(getToken()===session&&version===notificationVersion.current)setNotificationError(error.message);}
|
||||
};
|
||||
const markNotificationsRead=async()=>{
|
||||
if(notificationMutation.current)return;
|
||||
const operation={session:getToken()};notificationMutation.current=operation;
|
||||
notificationVersion.current++;setNotificationBusy(true);setNotificationError(null);
|
||||
try {
|
||||
const ids=await acknowledgeNotifications(notifications,bildirimOkundu);
|
||||
if(getToken()!==operation.session||notificationMutation.current!==operation)return;
|
||||
notificationVersion.current++;
|
||||
setNotifications(current=>current.map(row=>ids.has(row.id)?{...row,read:true}:row));
|
||||
}catch(error){if(getToken()===operation.session&¬ificationMutation.current===operation)setNotificationError(error.message);}
|
||||
finally {if(notificationMutation.current===operation){notificationMutation.current=null;setNotificationBusy(false);}}
|
||||
};
|
||||
|
||||
// Persist state to localstorage
|
||||
// Havuz on binlerce kayda çıkabiliyor; tamamı localStorage kotasını (≈5 MB)
|
||||
@@ -645,22 +677,10 @@ export const AppProvider = ({ children }) => {
|
||||
// Sunucu bildirimleri (zeyilname / sonuç): 5 dk'da bir çek, yenileri göster
|
||||
useEffect(() => {
|
||||
if (!veriSenkron) return;
|
||||
let iptal = false;
|
||||
const session = getToken();
|
||||
const cek = async () => {
|
||||
try {
|
||||
const liste = await bildirimleriGetir();
|
||||
const yeni = liste.filter((b) => !b.okundu);
|
||||
if (iptal || getToken() !== session || !yeni.length) return;
|
||||
for (const b of yeni.slice(0, 5)) notify(b.baslik, b.metin);
|
||||
await bildirimOkundu(yeni.map((b) => b.id));
|
||||
} catch { /* sessiz */ }
|
||||
};
|
||||
cek();
|
||||
const z = setInterval(cek, 5 * 60 * 1000);
|
||||
return () => { iptal = true; clearInterval(z); };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [veriSenkron]);
|
||||
void refreshNotifications();
|
||||
const timer=setInterval(refreshNotifications,5*60*1000);
|
||||
return()=>{notificationVersion.current++;clearInterval(timer);};
|
||||
}, [veriSenkron,authUser?.email]);
|
||||
|
||||
// Şirket belgesi süre uyarısı: süresi dolmuş / 30 gün içinde dolacak
|
||||
// belgeler için günde bir kez bildirim (kimse teklif hazırlanana kadar
|
||||
@@ -807,6 +827,7 @@ export const AppProvider = ({ children }) => {
|
||||
notify,
|
||||
requestPushPermission,
|
||||
markNotificationsRead,
|
||||
refreshNotifications,notificationError,notificationBusy,
|
||||
isQuietNow,
|
||||
favoriteKurumlar,
|
||||
toggleFavoriteKurum,
|
||||
|
||||
Reference in New Issue
Block a user