diff --git a/docs/quality/uygulama-durumu.md b/docs/quality/uygulama-durumu.md index 738eca8..e3bd1a6 100644 --- a/docs/quality/uygulama-durumu.md +++ b/docs/quality/uygulama-durumu.md @@ -2320,3 +2320,9 @@ Doğrulama: yerel container probe `pass:true`; kanıt [`qa-container-runtime.jso Nginx CSP, HSTS ve `X-Frame-Options` başlıklarını da yayınlıyor; Node upstream'inin üç ortak başlığı nginx'te gizlenerek tekrar gönderim önleniyor. Drift probe readiness yanıtında gerçek `{ok:true}` sözleşmesini denetliyor, HTTPS hedeflerinde HSTS'i zorunlu tutuyor ve HTTP yerel container kontrollerinde HSTS'i gözlemlemeye devam ediyor. Doğrulama: `nginx -t`, Docker üretim build'i ve yerel container probe `pass:true`; CSP, HSTS, clickjacking ve temel güvenlik başlıklarının tamamı görüldü. Production probe'u deployment yapılana kadar başarısız kalmaya devam ediyor. + +### 2026-09-06 · Google ve Outlook takvim taslak bağlantıları + +İhale detayındaki takvim bölümüne, doğrulanmış İstanbul son teklif tarihini kullanan Google Calendar ve Outlook web taslak bağlantıları eklendi. Bağlantılar başlık, İKN, kurum, yer ve resmî kaynağı yeniden kontrol etme notunu önceden dolduruyor; kaynakta saat yoksa bağlantı üretilmiyor ve hata yalnız kullanıcı tıkladığında gösteriliyor. Otomatik OAuth senkronizasyonu, kayıt güncellemesi veya dış sağlayıcıya veri gönderimi yapılmıyor; kullanıcı taslağı kendi hesabında kaydediyor. + +Doğrulama: `npm test` 301/301, `npm run build` başarılı ve `npm run qa:browser` farklı cihaz saat dilimi (`America/New_York`) ile başarılı. Gerçek Google/Outlook hesaplarında OAuth, kayıt ve değişiklik sonrası yeniden senkron kabulü hâlâ açık. diff --git a/src/components/TenderDetailModal.jsx b/src/components/TenderDetailModal.jsx index a910775..af46669 100644 --- a/src/components/TenderDetailModal.jsx +++ b/src/components/TenderDetailModal.jsx @@ -1,5 +1,5 @@ import { TenderSourceHistory } from './TenderSourceHistory'; -import React, { useState, useEffect, useId } from 'react'; +import React, { useState, useEffect, useId, useMemo } from 'react'; import {useModalFocus} from '../hooks/useModalFocus'; import { useApp } from '../context/AppContext'; import { BidPackageBuilder } from './BidPackageBuilder'; @@ -12,7 +12,7 @@ import { Send, Stamp, ArrowLeft, ExternalLink, Printer, PenLine } from 'lucide-react'; import { acknowledgeNoteDraft, editNoteDraft } from '../services/noteDrafts'; import { tenderStateKey } from '../services/tenderState'; -import { createTenderICS } from '../services/tenderCalendar'; +import { createTenderICS, googleCalendarUrl, outlookCalendarUrl } from '../services/tenderCalendar'; import { validateExchangeAmount, MAX_EXCHANGE_AMOUNT } from '../services/exchangeAmount'; export const TenderDetailModal = () => { @@ -52,9 +52,20 @@ export const TenderDetailModal = () => { const fxIsStale = Boolean(fx?.refreshError); const bidAmountValid = validateExchangeAmount(bidAmount); + // Keep provider links deterministic during render; invalid source dates are + // reported only when the user tries to open a link. + const tender = (activeTenderDetail && tenders.find((t) => t.id === activeTenderDetail.id)) || activeTenderDetail; + const calendarUrls = useMemo(() => { + if (!tender) return { google: null, outlook: null, error: null }; + try { + return { google: googleCalendarUrl(tender), outlook: outlookCalendarUrl(tender), error: null }; + } catch (e) { + return { google: null, outlook: null, error: e.message }; + } + }, [tender]); + if (!activeTenderDetail) return null; // Havuzdaki güncel kayıt: takip/görev değişince düğme etiketleri de değişsin. - const tender = (activeTenderDetail && tenders.find((t) => t.id === activeTenderDetail.id)) || activeTenderDetail; const savedNotes = tender.notes || []; const handleAddNote = async (e) => { @@ -83,6 +94,14 @@ export const TenderDetailModal = () => { document.body.removeChild(link); setTimeout(() => URL.revokeObjectURL(link.href), 1000); }; + const handleCalendarOpen = (provider, event) => { + if (!calendarUrls[provider]) { + event.preventDefault(); + setActionError(calendarUrls.error || 'Takvim bağlantısı oluşturulamadı.'); + return; + } + setActionError(null); + }; return (
Bu dosya takvime otomatik senkron göndermez. Tarih veya saat değişirse yeni dosyayı indirip Google/Outlook kaydını güncelleyin; aynı etkinlik kimliği ve sürümü destekleniyorsa mevcut kayıt güncellenir.
+Google ve Outlook bağlantıları doğrulanmış tarih/saatle ön-doldurulmuş taslak açar; kaydetme ve değişiklik sonrası yeniden senkronizasyon kullanıcıya aittir. Otomatik OAuth senkronu yapılmaz.