add prefilled calendar provider links
This commit is contained in:
@@ -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 (
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto bg-slate-900/70 backdrop-blur-xs flex justify-center p-2 sm:p-4 animate-fade-in">
|
||||
@@ -420,7 +439,19 @@ export const TenderDetailModal = () => {
|
||||
>
|
||||
<Calendar className="w-4 h-4 text-blue-600" /> Takvim Dosyasını İndir (.ics)
|
||||
</button>
|
||||
<p className="text-xs text-slate-600 mt-2">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.</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<a href={calendarUrls.google || '#'} target="_blank" rel="noopener noreferrer"
|
||||
onClick={(event) => handleCalendarOpen('google', event)}
|
||||
className="text-center bg-white hover:bg-blue-50 text-blue-700 font-bold py-2 px-3 rounded-lg text-xs border border-blue-200">
|
||||
Google Takvim taslağı
|
||||
</a>
|
||||
<a href={calendarUrls.outlook || '#'} target="_blank" rel="noopener noreferrer"
|
||||
onClick={(event) => handleCalendarOpen('outlook', event)}
|
||||
className="text-center bg-white hover:bg-blue-50 text-blue-700 font-bold py-2 px-3 rounded-lg text-xs border border-blue-200">
|
||||
Outlook taslağı
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-xs text-slate-600 mt-2">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.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -40,6 +40,43 @@ export function createTenderICS(t, now = new Date()) {
|
||||
'END:VEVENT','END:VCALENDAR',''].map(fold).join('\r\n');
|
||||
}
|
||||
|
||||
const calendarInstant = date => date.toISOString().replace(/\.000Z$/, 'Z');
|
||||
const calendarText = t => [
|
||||
`İKN: ${t.ikn || t.id || ''}`,
|
||||
t.kurum || '',
|
||||
'Kaynak sürümünü ve ihale tarihini resmi kaynaktan doğrulayın.',
|
||||
].filter(Boolean).join('\n');
|
||||
|
||||
/** Google Calendar'a kaynak tarihiyle ön-doldurulmuş etkinlik taslağı açar. */
|
||||
export function googleCalendarUrl(t) {
|
||||
const start = tenderDeadline(t);
|
||||
const instant = calendarInstant(start).replace(/[-:]/g, '');
|
||||
const params = new URLSearchParams({
|
||||
action: 'TEMPLATE',
|
||||
text: `İhale son teklif: ${t.baslik || t.ikn || 'İhale'}`,
|
||||
dates: `${instant}/${instant}`,
|
||||
details: calendarText(t),
|
||||
location: t.ihaleYeri || t.teslimYeri || '',
|
||||
});
|
||||
return `https://calendar.google.com/calendar/render?${params}`;
|
||||
}
|
||||
|
||||
/** Outlook web takviminde kaynak tarihiyle ön-doldurulmuş taslak açar. */
|
||||
export function outlookCalendarUrl(t) {
|
||||
const start = tenderDeadline(t);
|
||||
const instant = calendarInstant(start);
|
||||
const params = new URLSearchParams({
|
||||
path: '/calendar/action/compose',
|
||||
rru: 'addevent',
|
||||
startdt: instant,
|
||||
enddt: instant,
|
||||
subject: `İhale son teklif: ${t.baslik || t.ikn || 'İhale'}`,
|
||||
body: calendarText(t),
|
||||
location: t.ihaleYeri || t.teslimYeri || '',
|
||||
});
|
||||
return `https://outlook.live.com/calendar/0/deeplink/compose?${params}`;
|
||||
}
|
||||
|
||||
function parseICSDate(value, params = '') {
|
||||
const raw = String(value || '').trim();
|
||||
const match = /^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2})Z?)?$/.exec(raw);
|
||||
|
||||
Reference in New Issue
Block a user