Files
ihaleist/src/components/CompanyScannerModal.jsx
T

314 lines
16 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from 'react';
import { useApp } from '../context/AppContext';
import { Globe, Bot, Sparkles, CheckCircle2, ShieldCheck, Tag, X, Loader2, Database, ArrowRight } from 'lucide-react';
import confetti from 'canvas-confetti';
export const CompanyScannerModal = () => {
const { isCompanyScannerOpen, setIsCompanyScannerOpen, userProfile, scanCompanyWebsite, updateCompanyCapabilities, uyumEsigi, setUyumEsigi, gommeDurum } = useApp();
const [urlInput, setUrlInput] = useState(userProfile.website || '');
const [isScanning, setIsScanning] = useState(false);
const [scanStep, setScanStep] = useState(0);
const [scanSuccess, setScanSuccess] = useState(false);
const [newProductInput, setNewProductInput] = useState('');
if (!isCompanyScannerOpen) return null;
const handleStartScan = async (e) => {
e.preventDefault();
if (!urlInput.trim()) return;
setIsScanning(true);
setScanSuccess(false);
setScanStep(1);
setTimeout(() => setScanStep(2), 600);
setTimeout(() => setScanStep(3), 1200);
const capabilities = await scanCompanyWebsite(urlInput);
setIsScanning(false);
setScanSuccess(true);
confetti({ particleCount: 70, spread: 60 });
};
const caps = userProfile.scrapedCapabilities;
const handleRemoveSector = (indexToRemove) => {
if (!caps?.extractedSectors) return;
const updated = caps.extractedSectors.filter((_, idx) => idx !== indexToRemove);
updateCompanyCapabilities({ extractedSectors: updated });
};
const handleRemoveProduct = (indexToRemove) => {
if (!caps?.extractedProducts) return;
const updated = caps.extractedProducts.filter((_, idx) => idx !== indexToRemove);
updateCompanyCapabilities({ extractedProducts: updated });
};
const handleRemoveCert = (indexToRemove) => {
if (!caps?.extractedCertifications) return;
const updated = caps.extractedCertifications.filter((_, idx) => idx !== indexToRemove);
updateCompanyCapabilities({ extractedCertifications: updated });
};
const handleAddCustomProduct = (e) => {
e.preventDefault();
if (!newProductInput.trim() || !caps) return;
const updated = [...(caps.extractedProducts || []), newProductInput.trim()];
updateCompanyCapabilities({ extractedProducts: updated });
setNewProductInput('');
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/70 backdrop-blur-xs animate-fade-in">
<div className="bg-white rounded-2xl border border-slate-200 shadow-2xl w-full max-w-3xl overflow-hidden my-auto flex flex-col max-h-[92vh]">
{/* Header */}
<div className="bg-linear-to-r from-blue-900 via-slate-900 to-indigo-900 text-white px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-blue-600 flex items-center justify-center text-white shadow-md">
<Globe className="w-6 h-6" />
</div>
<div>
<h2 className="font-extrabold text-base flex items-center gap-2">
Firma Web Sitesi Tarama & Detaylı Ürün Analiz Motoru
</h2>
<p className="text-xs text-blue-200">Ürün ve faaliyet alanı önerilerini sitenizden çıkarır; profilinize eklemeden önce doğrulayın.</p>
</div>
</div>
<button
onClick={() => setIsCompanyScannerOpen(false)}
className="text-slate-400 hover:text-white p-1 rounded-lg transition-colors"
>
<X className="w-6 h-6" />
</button>
</div>
{/* Modal Body */}
<div className="p-6 space-y-6 overflow-y-auto">
{/* Strict Sector Protection Banner */}
<div className="bg-blue-50 border border-blue-200 rounded-xl p-3 flex items-center justify-between text-xs text-blue-900">
<div className="flex items-center gap-2 font-bold">
<ShieldCheck className="w-4 h-4 text-blue-600" />
<span>Sektör Filtresi Aktif: Sadece web sitenizdeki kesinleşmiş faaliyet alanları ve detaylı ürün kataloğu işlenir.</span>
</div>
<span className="bg-blue-600 text-white text-[10px] font-extrabold px-2 py-0.5 rounded-full">
Açıklanabilir ürün ve sektör eşleşmesi
</span>
</div>
{/* URL Form */}
<form onSubmit={handleStartScan} className="bg-slate-50 p-4 rounded-xl border border-slate-200 space-y-3">
<label className="block text-xs font-bold text-slate-800 uppercase tracking-wider">
FİRMANIZA AİT WEB SİTESİ ADRESİ
</label>
<div className="flex gap-2">
<div className="relative flex-1">
<Globe className="w-4 h-4 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2" />
<input
type="url"
required
value={urlInput}
onChange={(e) => setUrlInput(e.target.value)}
placeholder="https://www.firmaniz.com.tr"
className="w-full bg-white border border-slate-300 rounded-lg pl-9 pr-4 py-2.5 text-xs text-slate-900 font-semibold focus:ring-2 focus:ring-blue-500"
/>
</div>
<button
type="submit"
disabled={isScanning}
className="bg-blue-600 hover:bg-blue-500 text-white font-extrabold px-5 py-2.5 rounded-lg text-xs flex items-center gap-2 transition-colors shadow-md disabled:opacity-50 shrink-0"
>
{isScanning ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4 text-amber-300" />}
<span>{isScanning ? 'Detaylı Ürünler Taranıyor...' : 'Web Sitemi Tara & Profili Güncelle'}</span>
</button>
</div>
</form>
{/* Scanning Live Indicator */}
{isScanning && (
<div className="bg-blue-50 border border-blue-200 p-5 rounded-xl space-y-3 animate-fade-in">
<div className="flex items-center justify-between text-xs font-extrabold text-blue-900">
<span className="flex items-center gap-2">
<Loader2 className="w-4 h-4 text-blue-600 animate-spin" />
Derin Sayfa Metin Analizi ve Ürün Çıkarımı Sürüyor...
</span>
<span>%{(scanStep * 33).toFixed(0)}</span>
</div>
<div className="w-full bg-blue-200 rounded-full h-2 overflow-hidden">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-500"
style={{ width: `${scanStep * 33}%` }}
/>
</div>
<div className="space-y-1 text-xs text-slate-600 font-medium">
<p className={scanStep >= 1 ? 'text-blue-900 font-bold' : 'text-slate-400'}>
1. 🌐 {urlInput} alan adına bağlanıldı, harici ve alakasız sektör tanımları elendi.
</p>
<p className={scanStep >= 2 ? 'text-blue-900 font-bold' : 'text-slate-400'}>
2. 📄 Web sitesindeki "Hakkımızda", "Ürünler" ve "Katalog" alt sayfalarındaki ürün isimleri ayıklandı.
</p>
<p className={scanStep >= 3 ? 'text-blue-900 font-bold' : 'text-slate-400'}>
3. 💾 Firmanın gerçek ürün yelpazesi ve ISO/ÜTS kalite belgeleri İhaleist veritabanına işlendi.
</p>
</div>
</div>
)}
{scanSuccess && (
<div className="bg-emerald-50 border border-emerald-200 text-emerald-900 p-4 rounded-xl text-xs font-bold flex items-center gap-2 animate-fade-in shadow-xs">
<CheckCircle2 className="w-5 h-5 text-emerald-600 shrink-0" />
<span>Web sitenizdeki ürünler ve doğrudan doğrulanmış sektörler veritabanına işlendi! Alakasız sektörler elendi.</span>
</div>
)}
{/* Current Extracted Capabilities Database View */}
{caps && (
<div className="space-y-4">
<div className="flex items-center justify-between border-b border-slate-200 pb-2">
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
<Database className="w-4 h-4 text-blue-600" /> Veritabanındaki Doğrulanmış Firma Ürün & Sektör Profili
</h3>
<span className="text-[10px] text-slate-400 font-semibold">Son Tarama: {caps.scrapedAt}</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Sectors */}
<div className="bg-slate-50 p-4 rounded-xl border border-slate-200 space-y-2">
<span className="font-bold text-xs text-slate-800 uppercase tracking-wider flex items-center gap-1.5">
<Tag className="w-3.5 h-3.5 text-blue-600" /> Doğrulanmış Sektörler ({caps.extractedSectors?.length || 0})
</span>
<div className="space-y-1.5">
{caps.extractedSectors?.map((s, i) => (
<div key={i} className="flex items-center justify-between text-xs font-bold text-blue-900 bg-blue-100/70 border border-blue-200 px-2.5 py-1.5 rounded-lg group">
<span>{s}</span>
<button
type="button"
onClick={() => handleRemoveSector(i)}
className="text-blue-400 hover:text-red-600 transition-colors p-0.5"
title="Sektörü Kaldır"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
))}
</div>
</div>
{/* Products */}
<div className="bg-slate-50 p-4 rounded-xl border border-slate-200 space-y-3 md:col-span-2">
<span className="font-bold text-xs text-slate-800 uppercase tracking-wider flex items-center gap-1.5 justify-between">
<span className="flex items-center gap-1.5">
<Bot className="w-3.5 h-3.5 text-emerald-600" /> Detaylı Ürün & Hizmet Kataloğu ({caps.extractedProducts?.length || 0})
</span>
<span className="text-[10px] text-emerald-700 font-semibold">Sayfadan Ayıklanan Detaylı Kalemler</span>
</span>
{/* Add Custom Item Form */}
<form onSubmit={handleAddCustomProduct} className="flex gap-2">
<input
type="text"
value={newProductInput}
onChange={(e) => setNewProductInput(e.target.value)}
placeholder="+ Yeni Ürün / İhale Kalemi Ekle..."
className="flex-1 bg-white border border-slate-300 rounded-lg px-3 py-1.5 text-xs text-slate-900 font-semibold focus:ring-2 focus:ring-emerald-500"
/>
<button
type="submit"
className="bg-emerald-600 hover:bg-emerald-500 text-white font-bold px-3 py-1.5 rounded-lg text-xs transition-colors shrink-0"
>
Ekle
</button>
</form>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-1.5">
{caps.extractedProducts?.map((p, i) => (
<div key={i} className="text-xs font-semibold text-slate-700 bg-white border border-slate-200 p-2 rounded-lg flex items-start justify-between gap-1.5 group">
<div className="flex items-start gap-1.5">
<span className="shrink-0 text-emerald-600 font-bold">📦</span>
<span className="line-clamp-2">{p}</span>
</div>
<button
type="button"
onClick={() => handleRemoveProduct(i)}
className="text-slate-300 hover:text-red-600 transition-colors p-0.5 shrink-0"
title="Ürünü Kaldır"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
))}
</div>
</div>
</div>
{/* Certifications */}
<div className="bg-slate-50 p-4 rounded-xl border border-slate-200 space-y-2">
<span className="font-bold text-xs text-slate-800 uppercase tracking-wider flex items-center gap-1.5">
<ShieldCheck className="w-3.5 h-3.5 text-purple-600" /> Web Sitesinde Tespit Edilen Sertifika ve Kayıtlar
</span>
<div className="flex flex-wrap gap-2">
{caps.extractedCertifications?.map((c, i) => (
<span key={i} className="text-xs font-semibold text-purple-900 bg-purple-100/70 border border-purple-200 px-3 py-1 rounded-lg flex items-center gap-1.5">
📜 {c}
<button
type="button"
onClick={() => handleRemoveCert(i)}
className="text-purple-400 hover:text-red-600 transition-colors"
>
<X className="w-3 h-3" />
</button>
</span>
))}
</div>
</div>
</div>
)}
</div>
{/* Firma eşiği: "uyumlu" filtresi ve bildirim bu skordan itibaren.
(Önceki "%98+ doğruluk" cümlesi ölçülmemiş bir iddiaydı, kaldırıldı.) */}
<div className="bg-white px-6 py-4 border-t border-slate-200 space-y-2">
<div className="flex items-center justify-between gap-3 flex-wrap">
<span className="text-xs font-bold text-slate-800">Uyum eşiği — "Uyumlu" filtresi bu skordan itibaren gösterir</span>
<span className="text-sm font-black text-blue-700">%{uyumEsigi}</span>
</div>
<input
type="range" min={30} max={95} step={5} value={uyumEsigi}
onChange={(e) => setUyumEsigi(e.target.value)}
className="w-full accent-blue-600 cursor-pointer"
/>
<p className="text-[11px] text-slate-500 leading-relaxed">
Skor = profil terimlerinin ihalede geçmesi (başlık, teklif cetveli, OKAS) + anlamsal yakınlık bonusu.
{gommeDurum === 'hazir' && <> Anlamsal bonus <strong className="text-emerald-700">etkin</strong>.</>}
{gommeDurum === 'bekliyor' && <> Anlamsal bonus hesaplanıyor…</>}
{(gommeDurum === 'kapali' || gommeDurum === 'hata') && <> Anlamsal bonus şu an kapalı; yalnızca terim eşleşmesi.</>}
</p>
</div>
{/* Footer */}
<div className="bg-slate-50 p-4 border-t border-slate-200 flex justify-between items-center">
<p className="text-[11px] text-slate-500 font-medium">
Terim ekledikçe skor keskinleşir; kalem havuzu doldukça teklif cetveli eşleşmeleri devreye girer.
</p>
<button
onClick={() => setIsCompanyScannerOpen(false)}
className="bg-slate-800 hover:bg-slate-700 text-white font-bold text-xs px-6 py-2.5 rounded-lg transition-colors shadow-xs shrink-0"
>
Tamam & Profili Kaydet
</button>
</div>
</div>
</div>
);
};