fix: retain server notifications when local alerts arrive

This commit is contained in:
2026-09-06 14:56:21 +03:00
parent 120657758c
commit 2353170c40
4 changed files with 36 additions and 5 deletions
+7
View File
@@ -1138,3 +1138,10 @@ Taslak türü seçicisine erişilebilir ad eklendi. Üretim derlemesi geçti. Ö
- getUpdates doğrulaması HTTP başarısı, boolean ok:true ve gerçek sonuç dizisi gerektiriyor. Eksik/yanlış tipte sonuçlar artık boş liste gibi 'kod bulunamadı' sayılmıyor; sağlayıcı yanıt hatası olarak bildiriliyor.
- 269 test ve build başarılı. Yeni testler kaydedilmiş bağlantı + yapılandırılmamış hizmetin HTTP 503 vermesi, kimliksiz test kabulünün istemcide reddi/tekrarlanmaması ve bozuk güncelleme yanıtlarını kapsıyor. HTTP test sunucusunda Telegram tokenı açıkça boş bırakılıyor; yalnız geçici veri deposu kullanılıyor. Gerçek Telegram mesajı gönderilmedi.
- Test gönderimi yanıtında id alanı zorunlu hale geldi; ön yüz/sunucu birlikte güncellenmeli. Test mesajı düğmesi için kalıcı istek tekrar engeli ve gerçek sağlayıcı uçtan uca kabulü henüz doğrulanmadı. Genel plan açık.
### 2026-09-06 — Yerel uyarılar sunucu bildirimlerini düşürmesin
- Yeni bir yerel uyarı ekleyen `notify` işlevi tüm listeyi ilk 30 kayıtla sınırlıyordu. Bu, sunucudan yüklenen ve henüz okunmamış ihale bildirimlerini de ekrandan düşürüyordu. Yeni ortak birleştirme işlevi yalnız son 30 yerel uyarıyı tutuyor; yüklenmiş sunucu kayıtları sayı sınırına dahil edilmiyor.
- Sunucu yenilemesindeki toplam 80 kayıt kesintisi de kaldırıldı; yenileme sunucunun gönderdiği kayıtları/okunma durumunu esas alıp yerel uyarıları kendi sınırı içinde koruyor. Yerel bildirim kimlikleri aynı milisaniyede çakışabilen Date.now yerine UUID kullanıyor.
- Doğrulama: 270 test ve build başarılı. Yeni regresyon testinde 100 sunucu kaydı üzerine 40 yerel uyarı eklendi; tüm sunucu kayıtları ve okunma değerleri korundu, son 30 yerel uyarı tekil kimlikle kaldı, sunucudan yeni okunma durumu geldiğinde yerel kayıtlar korundu. Bu sayı testi istemcinin aldığı listeyi kapsar; sunucu bildirim geçmişinin bütününü/paginasyonunu kanıtlamaz.
- Bu değişiklik için gerçek tarayıcıdaki yoğun bildirim görsel kabulü henüz yapılmadı. Gerçek bildirim gönderilmedi, canlıya yayın yapılmadı. Genel plan açık.
+3 -3
View File
@@ -1,7 +1,7 @@
import {validateReminderPreferences,quietNow} from '../services/reminderPreferences';
import {validateTeam} from '../services/teamData';
import {savedFilterCriteria} from '../services/savedFilterCriteria';
import {mergeServerNotifications,acknowledgeNotifications} from '../services/notifications';
import {mergeServerNotifications,acknowledgeNotifications,addLocalNotification} from '../services/notifications';
import { searchTender } from '../services/searchTender';
import { normalizeTenderSearch, matchingItemResults } from '../services/tenderSearch';
import React, {createContext, useContext, useState, useEffect, useMemo, useRef } from 'react';
@@ -579,7 +579,7 @@ export const AppProvider = ({ children }) => {
// 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));
setNotifications(prev=>addLocalNotification(prev,title,text));
pushNotification(title,text);
};
const pushNotification=(title,text)=>{
@@ -605,7 +605,7 @@ export const AppProvider = ({ children }) => {
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));
setNotifications(current=>mergeServerNotifications(current,rows));
}catch(error){if(getToken()===session&&version===notificationVersion.current)setNotificationError(error.message);}
};
const markNotificationsRead=async()=>{
+8 -1
View File
@@ -1,8 +1,15 @@
const keepRecentLocal = rows => {
let localCount=0;
return rows.filter(row=>row.serverId || ++localCount<=30);
};
export function addLocalNotification(current,title,text) {
return keepRecentLocal([{id:'n-'+crypto.randomUUID(),title,text,time:'az önce',read:false},...current]);
}
export function mergeServerNotifications(current,rows) {
if(!Array.isArray(rows)||rows.some(row=>typeof row?.id!=='string'||typeof row.baslik!=='string'||typeof row.metin!=='string'))throw new Error('Bildirim listesi geçersiz.');
const remote=rows.map(row=>({id:'server:'+row.id,serverId:row.id,title:row.baslik,text:row.metin,
time:new Date(row.zaman).toLocaleString('tr-TR'),read:!!row.okundu,ikn:row.ikn}));
return [...remote,...current.filter(row=>!row.serverId)].slice(0,80);
return keepRecentLocal([...remote,...current.filter(row=>!row.serverId)]);
}
export async function acknowledgeNotifications(snapshot,request) {
const unread=snapshot.filter(row=>!row.read),ids=unread.filter(row=>row.serverId).map(row=>row.serverId);
+18 -1
View File
@@ -1,6 +1,6 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {mergeServerNotifications,acknowledgeNotifications} from '../src/services/notifications.js';
import {mergeServerNotifications,acknowledgeNotifications,addLocalNotification} from '../src/services/notifications.js';
const rows=[{id:'one',baslik:'Tarih değişti',metin:'Yeni tarih',okundu:0,zaman:'2026-09-06T10:00:00Z'}];
test('refresh keeps stable notification identities and server unread state without acknowledging delivery',()=>{
const once=mergeServerNotifications([{id:'local',title:'Yerel',read:false}],rows);
@@ -17,3 +17,20 @@ test('read acknowledgement requires server success and only covers the captured
const acknowledged=await acknowledgeNotifications(current,async ids=>{assert.deepEqual(ids,['one']);return {success:true,okundu:1};});
assert.deepEqual([...acknowledged],['server:one','local']);assert.equal(acknowledged.has('arrived-later'),false);
});
test('local notification bursts preserve every loaded server record and its read state',()=>{
const remote=Array.from({length:100},(_,i)=>({id:String(i),baslik:'İhale '+i,metin:'Kaynak bildirimi',okundu:i%2,zaman:'2026-09-06T10:00:00Z'}));
let list=mergeServerNotifications([],remote);
const before=list.map(row=>({...row}));
for(let i=0;i<40;i++)list=addLocalNotification(list,'Yerel '+i,'İşlem sonucu');
assert.deepEqual(list.filter(row=>row.serverId),before);
const local=list.filter(row=>!row.serverId);
assert.equal(local.length,30);
assert.equal(local[0].title,'Yerel 39');assert.equal(local.at(-1).title,'Yerel 10');
assert.equal(new Set(local.map(row=>row.id)).size,30);
const refreshed=mergeServerNotifications(list,remote.map(row=>({...row,okundu:1})));
assert.equal(refreshed.filter(row=>row.serverId).length,100);
assert.ok(refreshed.filter(row=>row.serverId).every(row=>row.read));
assert.deepEqual(refreshed.filter(row=>!row.serverId),local);
});