persist imported calendar events across reloads
This commit is contained in:
@@ -2350,3 +2350,9 @@ Kanıt [`qa-live-runtime.json`](evidence/qa-live-runtime.json) içinde; ödeme,
|
|||||||
İhale detayındaki özel not taslakları hesap değişiminde artık render sırasında `setState` ile temizlenmiyor; değişim bir effect ile uygulanıyor. Böylece React render uyarısı ve hesap geçişi sırasında eski özel notun kısa süreli görünmesi riski azaltıldı. Mevcut owner/acknowledgement kontrolleri korunuyor.
|
İhale detayındaki özel not taslakları hesap değişiminde artık render sırasında `setState` ile temizlenmiyor; değişim bir effect ile uygulanıyor. Böylece React render uyarısı ve hesap geçişi sırasında eski özel notun kısa süreli görünmesi riski azaltıldı. Mevcut owner/acknowledgement kontrolleri korunuyor.
|
||||||
|
|
||||||
Doğrulama: `npm test` 301/301, `npm run build` ve `npm run qa:browser` başarılı; browser QA iki hesaplı analitik/ICS izolasyonunu da geçti.
|
Doğrulama: `npm test` 301/301, `npm run build` ve `npm run qa:browser` başarılı; browser QA iki hesaplı analitik/ICS izolasyonunu da geçti.
|
||||||
|
|
||||||
|
### 2026-09-06 · Harici ICS’nin yenileme sonrası korunması
|
||||||
|
|
||||||
|
İçe aktarılan harici ICS etkinlikleri artık yalnız sekme oturumunda değil, normalize edilmiş hesap e-postasına bağlı `localStorage` anahtarında saklanıyor. Böylece sayfa yenilemesi sonrası etkinlikler geri geliyor; ikinci hesap ilk hesabın takvim kayıtlarını göremiyor. Metin, bunun kaynak ihale kaydını değiştirmeyen tarayıcı içi bir kopya olduğunu açıkça belirtiyor.
|
||||||
|
|
||||||
|
Doğrulama: browser QA `icsPersistsAfterReload`, `accountScopedIcs` ve `primaryIcsRestored` kontrollerini geçti; `npm test` 301/301 ve üretim derlemesi başarılı. Gerçek cihazlar arası senkronizasyon ve OAuth takvim güncellemesi hâlâ açık.
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ export const TenderCalendar = () => {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!calendarStorageKey) { setImported([]); setLoadedCalendarKey(null); return; }
|
if (!calendarStorageKey) { setImported([]); setLoadedCalendarKey(null); return; }
|
||||||
let saved = null;
|
let saved = null;
|
||||||
try { saved = JSON.parse(sessionStorage.getItem(calendarStorageKey) || '[]'); } catch { saved = []; }
|
try { saved = JSON.parse(localStorage.getItem(calendarStorageKey) || '[]'); } catch { saved = []; }
|
||||||
setImported(Array.isArray(saved) && saved.length <= 100 ? saved : []);
|
setImported(Array.isArray(saved) && saved.length <= 100 ? saved : []);
|
||||||
setLoadedCalendarKey(calendarStorageKey);
|
setLoadedCalendarKey(calendarStorageKey);
|
||||||
}, [calendarStorageKey]);
|
}, [calendarStorageKey]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!loadedCalendarKey || loadedCalendarKey !== calendarStorageKey) return;
|
if (!loadedCalendarKey || loadedCalendarKey !== calendarStorageKey) return;
|
||||||
try { sessionStorage.setItem(loadedCalendarKey, JSON.stringify(imported)); } catch { /* private browsing can deny storage */ }
|
try { localStorage.setItem(loadedCalendarKey, JSON.stringify(imported)); } catch { /* private browsing can deny storage */ }
|
||||||
}, [imported, loadedCalendarKey, calendarStorageKey]);
|
}, [imported, loadedCalendarKey, calendarStorageKey]);
|
||||||
const allTenders = useMemo(() => [...tenders, ...imported], [tenders, imported]);
|
const allTenders = useMemo(() => [...tenders, ...imported], [tenders, imported]);
|
||||||
const agenda=useMemo(()=>calendarAgenda(allTenders,now),[allTenders,now]);
|
const agenda=useMemo(()=>calendarAgenda(allTenders,now),[allTenders,now]);
|
||||||
@@ -65,7 +65,7 @@ export const TenderCalendar = () => {
|
|||||||
</label>
|
</label>
|
||||||
<section className="border border-slate-200 rounded-xl bg-white p-4 space-y-2" aria-label="Harici takvim içe aktarma">
|
<section className="border border-slate-200 rounded-xl bg-white p-4 space-y-2" aria-label="Harici takvim içe aktarma">
|
||||||
<h2 className="text-sm font-bold">Harici takvimden içe aktar</h2>
|
<h2 className="text-sm font-bold">Harici takvimden içe aktar</h2>
|
||||||
<p className="text-xs text-slate-600">Google veya Outlook’tan indirdiğiniz .ics dosyasını yalnız bu tarayıcı oturumunda görüntüler. Kaynak ihale kaydını değiştirmez.</p>
|
<p className="text-xs text-slate-600">Google veya Outlook’tan indirdiğiniz .ics dosyası bu tarayıcıda hesabınıza özel saklanır; yenileme sonrası yeniden yüklenir. Kaynak ihale kaydını değiştirmez.</p>
|
||||||
<input type="file" accept=".ics,text/calendar" aria-label="ICS takvim dosyası seç" onChange={async event=>{
|
<input type="file" accept=".ics,text/calendar" aria-label="ICS takvim dosyası seç" onChange={async event=>{
|
||||||
const file=event.target.files?.[0]; if(!file) return;
|
const file=event.target.files?.[0]; if(!file) return;
|
||||||
try { const parsed=parseTenderICS(await file.text()); setImported(parsed); setImportError(''); }
|
try { const parsed=parseTenderICS(await file.text()); setImported(parsed); setImportError(''); }
|
||||||
|
|||||||
@@ -102,12 +102,18 @@ try {
|
|||||||
await page.locator('input[aria-label="ICS takvim dosyası seç"]').setInputFiles(icsPath);
|
await page.locator('input[aria-label="ICS takvim dosyası seç"]').setInputFiles(icsPath);
|
||||||
await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).waitFor();
|
await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).waitFor();
|
||||||
evidence.checks.icsImported = true;
|
evidence.checks.icsImported = true;
|
||||||
evidence.checks.icsStorageScoped = await page.evaluate(() => Object.keys(sessionStorage).some(key => key.startsWith('ihaleist_imported_calendar:')));
|
evidence.checks.icsStorageScoped = await page.evaluate(() => Object.keys(localStorage).some(key => key.startsWith('ihaleist_imported_calendar:')));
|
||||||
if (!evidence.checks.icsStorageScoped) throw new Error('ICS kaydı hesap kapsamlı sessionStorage anahtarı kullanmıyor.');
|
if (!evidence.checks.icsStorageScoped) throw new Error('ICS kaydı hesap kapsamlı localStorage anahtarı kullanmıyor.');
|
||||||
await page.getByRole('button', { name: 'Keşfet' }).click();
|
await page.getByRole('button', { name: 'Keşfet' }).click();
|
||||||
await page.getByRole('button', { name: 'İhale Takvimi' }).click();
|
await page.getByRole('button', { name: 'İhale Takvimi' }).click();
|
||||||
evidence.checks.icsPersistsAfterTabChange = await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).count() === 1;
|
evidence.checks.icsPersistsAfterTabChange = await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).count() === 1;
|
||||||
if (!evidence.checks.icsPersistsAfterTabChange) throw new Error('ICS kaydı sekme değişiminden sonra korunmadı.');
|
if (!evidence.checks.icsPersistsAfterTabChange) throw new Error('ICS kaydı sekme değişiminden sonra korunmadı.');
|
||||||
|
await page.reload({ waitUntil: 'networkidle' });
|
||||||
|
await page.getByRole('button', { name: 'Keşfet', exact: true }).click();
|
||||||
|
await page.getByRole('button', { name: 'İhale Takvimi' }).click();
|
||||||
|
await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).waitFor({ state: 'attached', timeout: 10_000 });
|
||||||
|
evidence.checks.icsPersistsAfterReload = await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).count() === 1;
|
||||||
|
if (!evidence.checks.icsPersistsAfterReload) throw new Error('ICS kaydı sayfa yenilemesinden sonra korunmadı.');
|
||||||
await page.goto(`${url.replace(/\/qa-signin$/, '')}/qa-switch-account?account=other`, { waitUntil: 'networkidle' });
|
await page.goto(`${url.replace(/\/qa-signin$/, '')}/qa-switch-account?account=other`, { waitUntil: 'networkidle' });
|
||||||
await page.getByRole('button', { name: 'Keşfet' }).click();
|
await page.getByRole('button', { name: 'Keşfet' }).click();
|
||||||
await page.getByRole('button', { name: 'İhale Takvimi' }).click();
|
await page.getByRole('button', { name: 'İhale Takvimi' }).click();
|
||||||
@@ -119,7 +125,7 @@ try {
|
|||||||
await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).waitFor({ state: 'attached', timeout: 10_000 });
|
await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).waitFor({ state: 'attached', timeout: 10_000 });
|
||||||
evidence.checks.primaryIcsRestored = await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).count() === 1;
|
evidence.checks.primaryIcsRestored = await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).count() === 1;
|
||||||
if (!evidence.checks.primaryIcsRestored) {
|
if (!evidence.checks.primaryIcsRestored) {
|
||||||
const storage = await page.evaluate(() => Object.fromEntries(Object.keys(sessionStorage).map(key => [key, sessionStorage.getItem(key)])));
|
const storage = await page.evaluate(() => Object.fromEntries(Object.keys(localStorage).filter(key => key.startsWith('ihaleist_imported_calendar:')).map(key => [key, localStorage.getItem(key)])));
|
||||||
throw new Error(`İlk hesabın ICS etkinliği hesap değişiminden sonra geri yüklenmedi: ${JSON.stringify(storage)}`);
|
throw new Error(`İlk hesabın ICS etkinliği hesap değişiminden sonra geri yüklenmedi: ${JSON.stringify(storage)}`);
|
||||||
}
|
}
|
||||||
} finally { await rm(icsDir, { recursive: true, force: true }); }
|
} finally { await rm(icsDir, { recursive: true, force: true }); }
|
||||||
|
|||||||
Reference in New Issue
Block a user