- analytics.js: firmStats/itemStats/provinceDensity (gerçek hesaplama) - Rakip Radar: canlı veriden kazanan firma analizi (mock 3 firma kaldırıldı) - Fiyat Analizi: gerçek kalem min/medyan/ort/maks + CSV export - TurkeyHeatMap: 81 il gerçek interaktif yoğunluk haritası (tıkla→filtrele) - Favori kurumlar: kart yıldızı + hızlı filtre (localStorage) - Ekip yönetimi: gerçek CRUD (RightSidebar) + Kanban ataması ekipten - Bildirimler: gerçek Notification API + izin + test + sessiz saatler - PDF: jsPDF ile gerçek birim fiyat teklif cetveli (kaşe/imza alanı)
176 lines
8.6 KiB
React
176 lines
8.6 KiB
React
import React, { useState } from 'react';
|
||
import { useApp } from '../../context/AppContext';
|
||
import { Smartphone, Wifi, WifiOff, Download, Bell, CheckCircle2, ShieldCheck, Zap, Sparkles, Moon } from 'lucide-react';
|
||
import confetti from 'canvas-confetti';
|
||
|
||
export const PwaSettings = () => {
|
||
const { userProfile, reminderSettings, setReminderSettings, requestPushPermission, notify, isQuietNow } = useApp();
|
||
const [offlineMode, setOfflineMode] = useState(true);
|
||
const [installed, setInstalled] = useState(false);
|
||
const [permission, setPermission] = useState(typeof Notification !== 'undefined' ? Notification.permission : 'unsupported');
|
||
|
||
const handleEnablePush = async () => {
|
||
const p = await requestPushPermission();
|
||
setPermission(p);
|
||
if (p === 'granted') {
|
||
setReminderSettings((s) => ({ ...s, pushEnabled: true }));
|
||
notify('Bildirimler açıldı', 'Yeni ihale ve son tarih uyarıları buradan gelecek.');
|
||
confetti({ particleCount: 50 });
|
||
}
|
||
};
|
||
const setQuiet = (patch) => setReminderSettings((s) => ({ ...s, ...patch }));
|
||
|
||
const handleInstallPwa = () => {
|
||
setInstalled(true);
|
||
confetti({ particleCount: 70 });
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6 animate-fade-in">
|
||
|
||
{/* Header Banner */}
|
||
<div className="bg-linear-to-r from-slate-900 via-blue-950 to-cyan-900 text-white p-6 rounded-2xl shadow-lg border border-cyan-800/40 flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-12 h-12 rounded-2xl bg-cyan-600 text-white flex items-center justify-center font-black shadow-md">
|
||
<Smartphone className="w-6 h-6" />
|
||
</div>
|
||
<div>
|
||
<h2 className="font-extrabold text-xl">Mobil PWA & Saha Ekibi Offline Modu</h2>
|
||
<p className="text-xs text-cyan-200 mt-0.5">Saha satış temsilcileriniz için internet bağlantısı olmadığında dahi çevrimdışı çalışma ve anlık push bildirimi.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Main Controls Grid */}
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
|
||
{/* Offline Cache Status */}
|
||
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-xs space-y-4">
|
||
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
|
||
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
|
||
{offlineMode ? <Wifi className="w-4 h-4 text-emerald-600" /> : <WifiOff className="w-4 h-4 text-slate-400" />} Çevrimdışı (Offline) Önbellek
|
||
</h3>
|
||
<span className="bg-emerald-100 text-emerald-800 font-extrabold text-[10px] px-2 py-0.5 rounded-full">
|
||
Senkronize
|
||
</span>
|
||
</div>
|
||
|
||
<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>
|
||
</p>
|
||
<p className="flex justify-between">
|
||
<span>Şartname PDF'leri:</span>
|
||
<strong className="text-slate-900">18 Belge Çevrimdışı</strong>
|
||
</p>
|
||
<p className="flex justify-between">
|
||
<span>Son Senkronizasyon:</span>
|
||
<strong className="text-emerald-700">Bugün 14:45</strong>
|
||
</p>
|
||
</div>
|
||
|
||
<button
|
||
onClick={() => setOfflineMode(!offlineMode)}
|
||
className="w-full bg-slate-100 hover:bg-slate-200 text-slate-800 font-bold text-xs py-2.5 rounded-xl border border-slate-200 transition-colors"
|
||
>
|
||
{offlineMode ? 'Çevrimdışı Önbelleği Yenile' : 'Çevrimdışı Modu Aktif Et'}
|
||
</button>
|
||
</div>
|
||
|
||
{/* Gerçek Tarayıcı Bildirimleri + Sessiz Saatler */}
|
||
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-xs space-y-4">
|
||
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
|
||
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
|
||
<Bell className="w-4 h-4 text-amber-500" /> Anlık Bildirimler
|
||
</h3>
|
||
<span className={`font-extrabold text-[10px] px-2 py-0.5 rounded-full ${
|
||
permission === 'granted' ? 'bg-emerald-100 text-emerald-800' : permission === 'denied' ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-600'
|
||
}`}>
|
||
{permission === 'granted' ? 'İzin Verildi' : permission === 'denied' ? 'Reddedildi' : permission === 'unsupported' ? 'Desteklenmiyor' : 'İzin Bekliyor'}
|
||
</span>
|
||
</div>
|
||
|
||
<p className="text-xs text-slate-600 leading-relaxed">
|
||
Tarayıcı bildirimlerine izin verin; yeni ihale ve son tarih uyarıları gerçek bildirim olarak gelir.
|
||
</p>
|
||
|
||
{permission !== 'granted' ? (
|
||
<button onClick={handleEnablePush} disabled={permission === 'unsupported'}
|
||
className="w-full bg-amber-500 hover:bg-amber-400 disabled:opacity-40 text-white font-bold text-xs py-2.5 rounded-xl">
|
||
Bildirimlere İzin Ver
|
||
</button>
|
||
) : (
|
||
<button onClick={() => notify('Test bildirimi', isQuietNow() ? 'Sessiz saatte — sadece listeye eklendi.' : 'Bildirimler çalışıyor ✓')}
|
||
className="w-full bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs py-2.5 rounded-xl">
|
||
Test Bildirimi Gönder
|
||
</button>
|
||
)}
|
||
|
||
{/* Sessiz saatler — gerçek */}
|
||
<div className="pt-2 border-t border-slate-100 space-y-2">
|
||
<label className="flex items-center justify-between text-xs font-bold text-slate-800 cursor-pointer">
|
||
<span className="flex items-center gap-1.5"><Moon className="w-3.5 h-3.5 text-indigo-500" /> Sessiz Saatler</span>
|
||
<input type="checkbox" checked={reminderSettings.quietEnabled}
|
||
onChange={(e) => setQuiet({ quietEnabled: e.target.checked })}
|
||
className="w-4 h-4 accent-indigo-600 rounded" />
|
||
</label>
|
||
{reminderSettings.quietEnabled && (
|
||
<div className="flex items-center gap-2 text-xs">
|
||
<input type="time" value={reminderSettings.quietStart}
|
||
onChange={(e) => setQuiet({ quietStart: e.target.value })}
|
||
className="bg-slate-50 border border-slate-300 rounded-lg px-2 py-1.5 flex-1" />
|
||
<span className="text-slate-400">→</span>
|
||
<input type="time" value={reminderSettings.quietEnd}
|
||
onChange={(e) => setQuiet({ quietEnd: e.target.value })}
|
||
className="bg-slate-50 border border-slate-300 rounded-lg px-2 py-1.5 flex-1" />
|
||
</div>
|
||
)}
|
||
{reminderSettings.quietEnabled && (
|
||
<p className="text-[10px] text-slate-400">
|
||
{isQuietNow() ? '🔕 Şu an sessiz saatte — tarayıcı bildirimi gösterilmez, uyarılar listeye eklenir.' : '🔔 Şu an aktif — bildirimler gösterilir.'}
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Install PWA Application */}
|
||
<div className="bg-linear-to-br from-slate-900 to-cyan-950 text-white p-6 rounded-2xl shadow-xs space-y-4 border border-cyan-800/40">
|
||
<div className="flex items-center justify-between">
|
||
<h3 className="font-black text-sm text-white flex items-center gap-2">
|
||
<Download className="w-4 h-4 text-cyan-400" /> İhaleist Mobil / Masaüstü
|
||
</h3>
|
||
<span className="bg-cyan-500 text-slate-950 font-black text-[10px] px-2 py-0.5 rounded-full">
|
||
PWA 2.0
|
||
</span>
|
||
</div>
|
||
|
||
<p className="text-xs text-slate-300 leading-relaxed">
|
||
İhaleist'i iOS, Android, Windows veya Mac cihazınıza bir tıklama ile uygulama olarak kurun.
|
||
</p>
|
||
|
||
<button
|
||
onClick={handleInstallPwa}
|
||
disabled={installed}
|
||
className="w-full bg-cyan-500 hover:bg-cyan-400 disabled:bg-slate-700 text-slate-950 font-black text-xs py-3 rounded-xl flex items-center justify-center gap-2 shadow-md transition-colors cursor-pointer"
|
||
>
|
||
{installed ? (
|
||
<>
|
||
<CheckCircle2 className="w-4 h-4 text-emerald-950" />
|
||
<span>Cihaza Yüklendi!</span>
|
||
</>
|
||
) : (
|
||
<>
|
||
<Download className="w-4 h-4 text-slate-950" />
|
||
<span>İhaleist Uygulamasını Cihaza Yükle</span>
|
||
</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
);
|
||
};
|