Kullanıcı testi hataları: sabit tarihler, kişisel varsayılanlar, kılavuz düğmesi, mobil başlık, AI taslak çökmesi
- Günlük Bülten ve İhale Takvimi sabit "10.08.2026" yerine bugünden hesaplar; takvim 7 günü gerçek ihale tarihleriyle sayar - Ekip/üye/firma varsayılanları oturumdaki kullanıcıdan; sahte "Ekip Notları" ve sabit "%94 Uyum" rozeti kaldırıldı (gerçek uyum skoru varsa gösterilir) - Kılavuz düğmesi document.body'ye portal (ekran dışına düşüyordu) - Detay modalı havuzdaki güncel kaydı okur: Takip Et / Görev aç etiketleri güncellenir - Mobil: başlıkta ikincil düğmeler md altında gizli, arama formu küçülebilir - PWA sayfası: sahte önbellek sayıları yerine Cache API ve bellek sayımı - Sözleşme sorgula pazar payı bir ondalık (hep %0 görünüyordu) - Rakip Radar "SMS" vaadi düzeltildi; sektör=CPV etiketi tekrarı gizlendi; Stripe kurulum uyarısı yalnız yöneticiye - Açılışta 7 gereksiz PUT /api/me/data: ilk yükleme yazımı atlanır - server: teklif taslağı çökmesi (kalemOku nesne döndürür), denetim yanıtından model adı kaldırıldı, gereklilikler satır bazlı; derin analiz 12k token + kesik JSON onarımı Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -54,7 +54,7 @@ export const Header = () => {
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<form onSubmit={handleSearchSubmit} className="flex-1 max-w-2xl flex items-center gap-2">
|
||||
<form onSubmit={handleSearchSubmit} className="flex-1 min-w-0 max-w-2xl flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<input
|
||||
type="text"
|
||||
@@ -100,13 +100,13 @@ export const Header = () => {
|
||||
</form>
|
||||
|
||||
{/* Right Action Icons */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 sm:gap-3 shrink-0">
|
||||
|
||||
{/* Real Live EKAP Mode Badge */}
|
||||
<button
|
||||
onClick={refreshLiveEKAPData}
|
||||
disabled={isRefreshingLive}
|
||||
className="bg-emerald-600 hover:bg-emerald-500 text-white font-extrabold px-3 py-1.5 rounded-lg text-xs flex items-center gap-1.5 shadow-xs transition-colors shrink-0 disabled:opacity-50"
|
||||
className="bg-emerald-600 hover:bg-emerald-500 text-white font-extrabold px-3 py-1.5 rounded-lg text-xs hidden md:hidden md:flex items-center gap-1.5 shadow-xs transition-colors shrink-0 disabled:opacity-50"
|
||||
title="Canlı EKAP ve Doğrudan Temin Verilerini Anlık Güncelleyin"
|
||||
>
|
||||
<RefreshCw className={`w-3.5 h-3.5 text-white ${isRefreshingLive ? 'animate-spin' : ''}`} />
|
||||
@@ -126,7 +126,7 @@ export const Header = () => {
|
||||
{/* Company Web Scanner Button */}
|
||||
<button
|
||||
onClick={() => setIsCompanyScannerOpen(true)}
|
||||
className="bg-blue-600 hover:bg-blue-500 text-white font-bold px-3 py-1.5 rounded-lg text-xs flex items-center gap-1.5 shadow-xs transition-colors shrink-0"
|
||||
className="bg-blue-600 hover:bg-blue-500 text-white font-bold px-3 py-1.5 rounded-lg text-xs hidden md:flex items-center gap-1.5 shadow-xs transition-colors shrink-0"
|
||||
title="Web Sitenizi Tarayın & AI İhale Profiline İşleyin"
|
||||
>
|
||||
<Globe className="w-4 h-4 text-blue-200" />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { HelpCircle, X, BookOpen, Lightbulb, ListChecks } from 'lucide-react';
|
||||
@@ -97,7 +98,7 @@ export const KullanimKilavuzu = () => {
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
return createPortal(
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
@@ -142,6 +143,7 @@ export const KullanimKilavuzu = () => {
|
||||
</aside>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</>,
|
||||
document.body,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -137,7 +137,7 @@ export const TenderCard = ({ tender }) => {
|
||||
<span className="bg-slate-100 text-slate-700 font-medium text-[11px] px-2.5 py-1 rounded-md border border-slate-200">
|
||||
{tender.sektor}
|
||||
</span>
|
||||
{tender.cpvLabel && tender.cpvLabel !== 'Genel' && (
|
||||
{tender.cpvLabel && tender.cpvLabel !== 'Genel' && tender.cpvLabel !== tender.sektor && (
|
||||
<span className="bg-sky-50 text-sky-700 font-medium text-[11px] px-2.5 py-1 rounded-md border border-sky-200">
|
||||
{tender.cpvLabel}
|
||||
</span>
|
||||
|
||||
@@ -10,12 +10,10 @@ import {
|
||||
import confetti from 'canvas-confetti';
|
||||
|
||||
export const TenderDetailModal = () => {
|
||||
const { activeTenderDetail, setActiveTenderDetail, toggleTrackTender, userProfile, setIsAiSpecAnalyzerOpen, gorevAc, gorevKontrol, aiAnalysisResults } = useApp();
|
||||
const { activeTenderDetail, setActiveTenderDetail, toggleTrackTender, userProfile, setIsAiSpecAnalyzerOpen, gorevAc, gorevKontrol, aiAnalysisResults, tenders } = useApp();
|
||||
const [activeTab, setActiveTab] = useState('details'); // 'details', 'pdf_stamp', 'ai_assistant', 'checklist', 'kurum_tenders'
|
||||
const [noteText, setNoteText] = useState('');
|
||||
const [savedNotes, setSavedNotes] = useState([
|
||||
{ id: 1, author: 'Hikmet Tanrıverdi', text: 'Kayseri deposuna doğrudan sevkiyat yapılacak. Birim fiyat 145 TL üstü olmalı.', date: 'Bugün 10:15' }
|
||||
]);
|
||||
const [savedNotes, setSavedNotes] = useState([]);
|
||||
|
||||
// Kur Farkı Calculator State
|
||||
const [bidAmount, setBidAmount] = useState(15000);
|
||||
@@ -27,7 +25,8 @@ export const TenderDetailModal = () => {
|
||||
const [isDraggingStamp, setIsDraggingStamp] = useState(false);
|
||||
|
||||
if (!activeTenderDetail) return null;
|
||||
const tender = activeTenderDetail;
|
||||
// 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 handleAddNote = (e) => {
|
||||
e.preventDefault();
|
||||
@@ -176,7 +175,7 @@ END:VCALENDAR`;
|
||||
{[
|
||||
{ id: 'details', label: 'İhale Detayları', icon: FileText },
|
||||
{ id: 'pdf_stamp', label: 'Birim Fiyat Cetveli (PDF & Kaşe)', icon: Stamp, badge: 'İnteraktif' },
|
||||
{ id: 'ai_assistant', label: 'Yapay Zeka Asistanı', icon: Sparkles, badge: '%94 Uyum' },
|
||||
{ id: 'ai_assistant', label: 'Yapay Zeka Asistanı', icon: Sparkles, badge: tender?.aiMatchScore != null ? `%${tender.aiMatchScore} Uyum` : undefined },
|
||||
{ id: 'checklist', label: 'Hazırlık Checklist', icon: CheckSquare },
|
||||
{ id: 'teklif_asistani', label: 'Teklif Asistanı', icon: PenLine, badge: 'AI' },
|
||||
].map(tab => {
|
||||
|
||||
@@ -66,7 +66,7 @@ export const CompetitorRadar = () => {
|
||||
Proaktif Takip
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-indigo-200 mt-0.5">İzlemeye aldığınız rakip firmalar ihaleye teklif verdiğinde veya kazandığında anında SMS ve Push bildirimi alın.</p>
|
||||
<p className="text-xs text-indigo-200 mt-0.5">İzlemeye aldığınız rakip firmalar ihaleye teklif verdiğinde veya kazandığında uygulama içi, e-posta ve Telegram bildirimi alın.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -75,10 +75,10 @@ export const ContractSearch = () => {
|
||||
<div>
|
||||
<div className="flex justify-between text-xs font-semibold text-slate-700 mb-1">
|
||||
<span>Pazar Payı (hacim)</span>
|
||||
<span className="font-bold text-indigo-700">%{comp.share}</span>
|
||||
<span className="font-bold text-indigo-700">%{Number(comp.share).toLocaleString('tr-TR', { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</span>
|
||||
</div>
|
||||
<div className="w-full bg-slate-100 rounded-full h-2 overflow-hidden">
|
||||
<div className="bg-indigo-600 h-2 rounded-full" style={{ width: `${Math.max(comp.share, 2)}%` }}></div>
|
||||
<div className="bg-indigo-600 h-2 rounded-full" style={{ width: `${Math.min(100, Math.max(comp.share * 4, 2))}%` }}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,14 @@ import { Newspaper, Calendar, Building2, Package, ArrowRight } from 'lucide-reac
|
||||
export const DailyBulletin = () => {
|
||||
const { tenders, setActiveTenderDetail } = useApp();
|
||||
|
||||
const todayStr = '10.08.2026';
|
||||
const todayTenders = tenders.filter(t => t.tarih === todayStr || (t.eklenmeZamani || '').includes('az') || (t.eklenmeZamani || '').includes('önce'));
|
||||
// Bugün: yayımlanma (ilan) tarihi bugün olanlar; ilan tarihi yoksa havuza az önce eklenenler.
|
||||
const bugun = new Date();
|
||||
const todayStr = `${String(bugun.getDate()).padStart(2, '0')}.${String(bugun.getMonth() + 1).padStart(2, '0')}.${bugun.getFullYear()}`;
|
||||
const isoBugun = `${bugun.getFullYear()}-${String(bugun.getMonth() + 1).padStart(2, '0')}-${String(bugun.getDate()).padStart(2, '0')}`;
|
||||
const bugunMu = (v) => !!v && (String(v).startsWith(isoBugun) || String(v).slice(0, 10) === todayStr);
|
||||
const todayTenders = tenders.filter(t =>
|
||||
bugunMu(t.ilanTarihi) || (!t.ilanTarihi && ((t.eklenmeZamani || '').includes('az') || (t.eklenmeZamani || '').includes('saat')))
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 my-4 animate-fade-in">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Check, Sparkles, CreditCard, Loader2, Info, ShieldCheck, XCircle } from
|
||||
import { CancelSubscriptionModal } from '../CancelSubscriptionModal';
|
||||
|
||||
export const Pricing = () => {
|
||||
const { authUser, plan, planYenile } = useApp();
|
||||
const { authUser, plan, planYenile, isAdmin } = useApp();
|
||||
const [data, setData] = useState(null);
|
||||
const [busy, setBusy] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -75,7 +75,7 @@ export const Pricing = () => {
|
||||
<div className="bg-red-50 border border-red-200 rounded-xl p-3 text-xs text-red-800 text-center">{error}</div>
|
||||
)}
|
||||
|
||||
{data && !data.configured && (
|
||||
{data && !data.configured && isAdmin && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 flex gap-3">
|
||||
<Info className="w-4 h-4 text-amber-700 shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-amber-900 leading-relaxed">
|
||||
|
||||
@@ -7,6 +7,20 @@ import { NotifySubscriptions } from './NotifySubscriptions';
|
||||
|
||||
export const PwaSettings = () => {
|
||||
const { userProfile, authUser, tenders, reminderSettings, setReminderSettings, requestPushPermission, notify, isQuietNow } = useApp();
|
||||
// Gerçek Cache API sayımı — sahte "102 ihale önbellekte" yerine
|
||||
const [onbellekSayisi, setOnbellekSayisi] = useState(null);
|
||||
const [onbellekAdi, setOnbellekAdi] = useState('');
|
||||
useEffect(() => {
|
||||
if (!('caches' in window)) return;
|
||||
(async () => {
|
||||
try {
|
||||
const adlar = await caches.keys();
|
||||
let toplam = 0;
|
||||
for (const ad of adlar) toplam += (await (await caches.open(ad)).keys()).length;
|
||||
setOnbellekSayisi(toplam); setOnbellekAdi(adlar[adlar.length - 1] || '');
|
||||
} catch { setOnbellekSayisi(null); }
|
||||
})();
|
||||
}, []);
|
||||
const [offlineMode, setOfflineMode] = useState(true);
|
||||
const [installed, setInstalled] = useState(false);
|
||||
const [permission, setPermission] = useState(typeof Notification !== 'undefined' ? Notification.permission : 'unsupported');
|
||||
@@ -113,16 +127,16 @@ export const PwaSettings = () => {
|
||||
|
||||
<div className="space-y-2 text-xs text-slate-700">
|
||||
<p className="flex justify-between">
|
||||
<span>İhale Kayıtları:</span>
|
||||
<strong className="text-slate-900">102 İhale Önbellekte</strong>
|
||||
<span>İhale Kayıtları (bellekte):</span>
|
||||
<strong className="text-slate-900">{tenders.length.toLocaleString('tr-TR')} ihale</strong>
|
||||
</p>
|
||||
<p className="flex justify-between">
|
||||
<span>Şartname PDF'leri:</span>
|
||||
<strong className="text-slate-900">18 Belge Çevrimdışı</strong>
|
||||
<span>Önbelleğe alınmış istek:</span>
|
||||
<strong className="text-slate-900">{onbellekSayisi == null ? '—' : `${onbellekSayisi} kayıt`}</strong>
|
||||
</p>
|
||||
<p className="flex justify-between">
|
||||
<span>Son Senkronizasyon:</span>
|
||||
<strong className="text-emerald-700">Bugün 14:45</strong>
|
||||
<span>Uygulama sürümü önbelleği:</span>
|
||||
<strong className="text-emerald-700">{onbellekAdi || '—'}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,18 +4,17 @@ import { CalendarDays, Clock, MapPin, AlertCircle } from 'lucide-react';
|
||||
|
||||
export const TenderCalendar = () => {
|
||||
const { tenders, setActiveTenderDetail } = useApp();
|
||||
const [selectedDay, setSelectedDay] = useState('10.08.2026');
|
||||
const GUNLER = ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'];
|
||||
const ggaayyyy = (d) => `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${d.getFullYear()}`;
|
||||
// Bugünden başlayan 7 gün; sayılar havuzdaki gerçek ihale tarihlerinden.
|
||||
const daysList = Array.from({ length: 7 }, (_, i) => {
|
||||
const d = new Date(); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() + i);
|
||||
const date = ggaayyyy(d);
|
||||
return { date, label: i === 0 ? `Bugün (${GUNLER[d.getDay()]})` : i === 1 ? `Yarın (${GUNLER[d.getDay()]})` : GUNLER[d.getDay()], count: tenders.filter(t => t.tarih === date).length };
|
||||
});
|
||||
const [selectedDay, setSelectedDay] = useState(daysList[0].date);
|
||||
|
||||
const daysList = [
|
||||
{ date: '10.08.2026', label: 'Bugün (Pazartesi)', count: 3 },
|
||||
{ date: '11.08.2026', label: 'Salı', count: 4 },
|
||||
{ date: '12.08.2026', label: 'Çarşamba', count: 2 },
|
||||
{ date: '13.08.2026', label: 'Perşembe', count: 5 },
|
||||
{ date: '14.08.2026', label: 'Cuma', count: 3 },
|
||||
{ date: '15.08.2026', label: 'Cumartesi', count: 1 }
|
||||
];
|
||||
|
||||
const dayTenders = tenders.filter(t => t.tarih === selectedDay || (selectedDay === '10.08.2026' && (t.tarih || '').includes('10')));
|
||||
const dayTenders = tenders.filter(t => t.tarih === selectedDay);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 my-4 animate-fade-in">
|
||||
|
||||
Reference in New Issue
Block a user