add bounded ICS calendar import

This commit is contained in:
2026-09-06 18:40:29 +03:00
parent 5e19f3fbaa
commit a8c20ba280
4 changed files with 88 additions and 4 deletions
+17 -3
View File
@@ -1,5 +1,5 @@
import { useDeadlineClock } from '../../hooks/useDeadlineClock';
import { calendarMonthDays, istanbulDateKey, calendarAgenda, shiftCalendarMonth } from '../../services/tenderCalendar';
import { calendarMonthDays, istanbulDateKey, calendarAgenda, shiftCalendarMonth, parseTenderICS } from '../../services/tenderCalendar';
import React, { useState, useMemo } from 'react';
import { useApp } from '../../context/AppContext';
import { CalendarDays } from 'lucide-react';
@@ -10,11 +10,13 @@ export const TenderCalendar = () => {
const today = istanbulDateKey(now);
const [month,setMonth] = useState(today.slice(0,7));
const [selectedDay, setSelectedDay] = useState(today);
const agenda=useMemo(()=>calendarAgenda(tenders,now),[tenders,now]);
const [imported,setImported] = useState([]), [importError,setImportError] = useState('');
const allTenders = useMemo(() => [...tenders, ...imported], [tenders, imported]);
const agenda=useMemo(()=>calendarAgenda(allTenders,now),[allTenders,now]);
const daysList=calendarMonthDays(month).map(day=>({...day,count:agenda.days.get(day.key)?.length || 0}));
const dayTenders=agenda.days.get(selectedDay) || [];
const chooseMonth=value=>{if(calendarMonthDays(value).length){setMonth(value);setSelectedDay(value+'-01');}};
const card=({tender:t,info})=><button key={t.id} type="button" onClick={()=>setActiveTenderDetail(t)}
const card=({tender:t,info})=><button key={t.id} type="button" onClick={()=>!t.externalCalendar&&setActiveTenderDetail(t)} aria-disabled={t.externalCalendar || undefined}
className="w-full text-left bg-white rounded-xl border border-slate-200 p-4 shadow-xs hover:shadow-md transition-all flex flex-wrap items-center justify-between gap-3">
<span className="min-w-0 space-y-1">
<span className="flex flex-wrap gap-2 text-xs">
@@ -48,6 +50,18 @@ export const TenderCalendar = () => {
<input type="month" aria-label="Takvim ayı seç" value={month} onInput={e=>chooseMonth(e.currentTarget.value)} onChange={e=>chooseMonth(e.target.value)}
className="ml-3 border border-slate-300 rounded-lg p-2"/>
</label>
<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>
<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>
<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;
try { const parsed=parseTenderICS(await file.text()); setImported(parsed); setImportError(''); }
catch(error) { setImported([]); setImportError(error.message); }
event.target.value='';
}} className="block text-xs" />
{imported.length>0&&<p role="status" className="text-xs text-emerald-700">{imported.length} harici etkinlik içe aktarıldı.</p>}
{importError&&<p role="alert" className="text-xs text-red-700">Takvim içe aktarılamadı: {importError}</p>}
</section>
<div role="group" className="flex flex-wrap gap-2" aria-label="Takvim ayı gezinme">
<button type="button" aria-label="Önceki takvim ayı" className="border rounded px-3 py-2 text-sm" disabled={!shiftCalendarMonth(month,-1)} onClick={()=>chooseMonth(shiftCalendarMonth(month,-1))}>Önceki ay</button>
<button type="button" aria-label="Bugünün takvim ayına dön" className="border rounded px-3 py-2 text-sm" onClick={()=>{setMonth(today.slice(0,7));setSelectedDay(today);}}>Bugüne dön</button>