fix: order calendar deadlines and surface undated tenders
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { useDeadlineClock } from '../../hooks/useDeadlineClock';
|
||||
import { calendarMonthDays, istanbulDateKey, tenderDateKey, deadlineInfo } from '../../services/tenderCalendar';
|
||||
import React, { useState } from 'react';
|
||||
import { calendarMonthDays, istanbulDateKey, calendarAgenda } from '../../services/tenderCalendar';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { useApp } from '../../context/AppContext';
|
||||
import { CalendarDays, Clock, MapPin, AlertCircle } from 'lucide-react';
|
||||
import { CalendarDays } from 'lucide-react';
|
||||
|
||||
export const TenderCalendar = () => {
|
||||
const now = useDeadlineClock();
|
||||
@@ -10,8 +10,22 @@ export const TenderCalendar = () => {
|
||||
const today = istanbulDateKey(now);
|
||||
const [month,setMonth] = useState(today.slice(0,7));
|
||||
const [selectedDay, setSelectedDay] = useState(today);
|
||||
const daysList = calendarMonthDays(month).map(day => ({...day,count:tenders.filter(t=>tenderDateKey(t)===day.key).length}));
|
||||
const dayTenders = tenders.filter(t => tenderDateKey(t) === selectedDay);
|
||||
const agenda=useMemo(()=>calendarAgenda(tenders,now),[tenders,now]);
|
||||
const daysList=calendarMonthDays(month).map(day=>({...day,count:agenda.days.get(day.key)?.length || 0}));
|
||||
const dayTenders=agenda.days.get(selectedDay) || [];
|
||||
const card=({tender:t,info})=><button key={t.id} type="button" onClick={()=>setActiveTenderDetail(t)}
|
||||
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">
|
||||
<span className="bg-purple-100 text-purple-900 rounded px-2 py-1">{info.deadline?.toLocaleTimeString('tr-TR',{hour:'2-digit',minute:'2-digit',timeZone:'Europe/Istanbul'}) || (info.dateKey ? 'Saat doğrulanmalı' : 'Tarih doğrulanmalı')}</span>
|
||||
<span>#{t.ikn}</span>
|
||||
<span>{t.durum || ''}{t.durum ? ' · ' : ''}{info.label}</span>
|
||||
</span>
|
||||
<span className="block font-bold text-sm break-words">{t.baslik}</span>
|
||||
<span className="block text-xs text-slate-500 break-words">{t.kurum} · {t.sehir}</span>
|
||||
</span>
|
||||
<span className="text-xs bg-amber-100 text-amber-900 rounded px-2 py-1">{t.alimTuru}</span>
|
||||
</button>;
|
||||
|
||||
return (
|
||||
<div className="space-y-4 my-4 animate-fade-in">
|
||||
@@ -38,6 +52,8 @@ export const TenderCalendar = () => {
|
||||
{daysList.map(d => (
|
||||
<button
|
||||
key={d.key}
|
||||
aria-pressed={selectedDay===d.key}
|
||||
aria-label={`${d.date} ${d.label}, ${d.count} ihale`}
|
||||
onClick={() => setSelectedDay(d.key)}
|
||||
className={`min-w-0 p-3 rounded-xl border text-center transition-all cursor-pointer ${
|
||||
selectedDay === d.key
|
||||
@@ -60,31 +76,15 @@ export const TenderCalendar = () => {
|
||||
<div className="space-y-3">
|
||||
<h2 className="text-sm font-bold">{selectedDay.split('-').reverse().join('.')} tarihli ihaleler</h2>
|
||||
{!dayTenders.length && <p className="text-sm text-slate-500">Bu gün için yüklenen havuzda ihale yok.</p>}
|
||||
{dayTenders.map(t => (
|
||||
<div
|
||||
key={t.id}
|
||||
role="button" tabIndex={0} onKeyDown={e=>{if(e.key==='Enter' || e.key===' '){e.preventDefault();setActiveTenderDetail(t);}}}
|
||||
onClick={() => setActiveTenderDetail(t)}
|
||||
className="bg-white rounded-xl border border-slate-200 p-4 shadow-xs hover:shadow-md cursor-pointer transition-all flex items-center justify-between gap-4"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="bg-purple-100 text-purple-900 text-[10px] font-extrabold px-2 py-0.5 rounded">
|
||||
⏰ {deadlineInfo(t, now).deadline?.toLocaleTimeString('tr-TR',{hour:'2-digit',minute:'2-digit',timeZone:'Europe/Istanbul'}) || 'Saat doğrulanmalı'}
|
||||
</span>
|
||||
<span className="text-xs text-slate-400 font-mono">#{t.ikn}</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-sm text-slate-900 hover:text-purple-600">{t.baslik}</h3>
|
||||
<p className="text-xs text-slate-500 font-medium">🏢 {t.kurum} • 📍 {t.sehir}</p>
|
||||
</div>
|
||||
|
||||
<span className="bg-amber-500 text-white font-extrabold text-xs px-3 py-1.5 rounded-lg shrink-0">
|
||||
{t.alimTuru}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<p className="text-xs text-slate-500">Saati bilinen ihaleler erken saatten geç saate sıralanır; saati doğrulanmamış kayıtlar sonda gösterilir.</p>
|
||||
{dayTenders.map(card)}
|
||||
</div>
|
||||
|
||||
{agenda.undated.length>0&&<section aria-label="Tarihi doğrulanacak ihaleler" className="space-y-3 border border-amber-300 rounded-xl p-3">
|
||||
<h2 className="text-sm font-bold">Tarihi doğrulanacak ihaleler ({agenda.undated.length})</h2>
|
||||
<p className="text-xs">Bu kayıtlar bir güne yerleştirilemedi. İlan detayından resmi tarih ve saati kontrol edin.</p>
|
||||
{agenda.undated.map(card)}
|
||||
</section>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -65,3 +65,24 @@ export function calendarMonthDays(month) {
|
||||
label:new Date(`${month}-${day}T12:00:00Z`).toLocaleDateString('tr-TR',{weekday:'short',timeZone:'Europe/Istanbul'})};
|
||||
});
|
||||
}
|
||||
|
||||
/** Group once and order each date by known deadline; undated records remain visible. */
|
||||
export function calendarAgenda(tenders, now=new Date()) {
|
||||
const days=new Map(),undated=[];
|
||||
for(const tender of tenders){
|
||||
const entry={tender,info:deadlineInfo(tender,now)};
|
||||
if(!entry.info.dateKey){undated.push(entry);continue;}
|
||||
if(!days.has(entry.info.dateKey))days.set(entry.info.dateKey,[]);
|
||||
days.get(entry.info.dateKey).push(entry);
|
||||
}
|
||||
const compare=(a,b)=>{
|
||||
const aTime=a.info.deadline?.getTime(),bTime=b.info.deadline?.getTime();
|
||||
if(aTime!==undefined&&bTime===undefined)return -1;
|
||||
if(aTime===undefined&&bTime!==undefined)return 1;
|
||||
if(aTime!==undefined&&aTime!==bTime)return aTime-bTime;
|
||||
return String(a.tender.ikn || a.tender.id || '').localeCompare(String(b.tender.ikn || b.tender.id || ''),'tr');
|
||||
};
|
||||
for(const entries of days.values())entries.sort(compare);
|
||||
undated.sort(compare);
|
||||
return {days,undated};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user