Files
ihaleist/src/components/CompanyScannerModal.jsx
T

66 lines
6.1 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 {HealthPortfolioEditor} from './HealthPortfolioEditor';
import React,{useState,useEffect,useRef,useId} from 'react';
import {useModalFocus} from '../hooks/useModalFocus';
import {useApp} from '../context/AppContext';
import {getToken} from '../services/auth';
import {X,Loader2} from 'lucide-react';
const fields=[['extractedSectors','Sektörler'],['extractedProducts','Ürün ve hizmetler'],['extractedCertifications','Sertifika ve kayıt beyanları'],['ihaleKonulari','İhale konuları']];
export function CompanyScannerModal(){
const {isCompanyScannerOpen,authUser}=useApp();
return isCompanyScannerOpen?<ProfileEditor key={`${authUser?.email}:${getToken()}`}/>:null;
}
function ProfileEditor(){
const {setIsCompanyScannerOpen,userProfile,scanCompanyWebsite,updateCompanyCapabilities,uyumEsigi}=useApp();
const [url,setUrl]=useState(userProfile.website || ''),[caps,setCaps]=useState(userProfile.scrapedCapabilities || {});
const [threshold,setThreshold]=useState(uyumEsigi),[busy,setBusy]=useState(''),[error,setError]=useState(null),[message,setMessage]=useState('');
const [entries,setEntries]=useState({});
const alive=useRef(true),controller=useRef(null),locked=useRef(false),titleId=useId();
const modalFocus=useModalFocus(true,()=>setIsCompanyScannerOpen(false));
useEffect(()=>{
alive.current=true;
return()=>{alive.current=false;controller.current?.abort();};
},[]);
const run=async(kind,operation)=>{
if(locked.current)return;locked.current=true;setBusy(kind);setError(null);setMessage('');
try{await operation();}catch(e){if(alive.current)setError(e.message);}finally{locked.current=false;if(alive.current)setBusy('');}
};
const scan=e=>{e.preventDefault();void run('scan',async()=>{
controller.current=new AbortController();
const proposal=await scanCompanyWebsite(url,{signal:controller.current.signal});
if(alive.current){setCaps(previous=>({...proposal,healthPortfolio:previous.healthPortfolio || []}));setMessage('Tarama önerileri alındı. Alanları kontrol edip düzenleyin; henüz profilinize kaydedilmedi.');}
});};
const save=()=>run('save',async()=>{
await updateCompanyCapabilities(caps,{website:url,threshold});
if(alive.current)setMessage('Firma profili sunucuya kaydedildi. Eşleşmeler kayıtlı profilinizle güncellenir.');
});
const edit=(key,values)=>{setCaps(previous=>({...previous,[key]:values}));setMessage('Kaydedilmemiş değişiklikler var.');};
return <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/70">
<section {...modalFocus} role="dialog" aria-modal="true" aria-labelledby={titleId} tabIndex={-1} className="bg-white rounded-2xl w-full max-w-3xl max-h-[92vh] overflow-y-auto p-5 space-y-4">
<div className="flex justify-between gap-3"><h2 id={titleId} className="font-bold text-lg">Firma ürün ve faaliyet profili</h2><button type="button" aria-label="Firma profilini kapat" onClick={()=>setIsCompanyScannerOpen(false)}><X/></button></div>
<p className="text-sm text-slate-600">Ürünlerinizi elle ekleyebilir veya web sitenizden öneri alabilirsiniz. Tarama, ürün uygunluğunu ya da ÜTS/sertifika geçerliliğini doğrulamaz. Kaydetmeden kapatırsanız bu ekrandaki değişiklikler uygulanmaz.</p>
<form onSubmit={scan} className="flex gap-2 items-end flex-wrap">
<label className="text-sm flex-1 min-w-0">Firma web sitesi<input aria-label="Firma web sitesi" type="url" required disabled={!!busy} value={url} onChange={e=>{setUrl(e.target.value);setMessage('Kaydedilmemiş değişiklikler var.');}} placeholder="https://www.firmaniz.com.tr" className="block border rounded p-2 w-full"/></label>
<button type="submit" disabled={!!busy} className="bg-blue-700 text-white rounded p-2 disabled:opacity-50">Web sitesinden öneri al</button>
</form>
{busy&&<p role="status" className="flex gap-2 text-sm"><Loader2 className="w-4 h-4 animate-spin"/>{busy==='scan'?'Site taranıyor; sonuç bekleniyor…':'Profil kaydediliyor…'}</p>}
{error&&<p role="alert" className="text-red-700 text-sm">{error}</p>}
{message&&<p role="status" className="text-sm text-blue-800">{message}</p>}
{caps.ozet&&<p className="text-sm">Tarama özeti: {caps.ozet}</p>}
<fieldset disabled={!!busy} className="grid gap-4 sm:grid-cols-2">
{fields.map(([key,label])=><section key={key} className="border rounded p-3 space-y-2">
<h3 className="font-bold text-sm">{label}</h3>
<form onSubmit={e=>{e.preventDefault();const value=(entries[key] || '').trim();if(!value)return;edit(key,[...new Set([...(caps[key] || []),value])]);setEntries(previous=>({...previous,[key]:''}));}} className="flex gap-2">
<input aria-label={`${label} yeni kayıt`} value={entries[key] || ''} onChange={e=>setEntries(previous=>({...previous,[key]:e.target.value}))} maxLength={300} className="min-w-0 w-full border rounded p-2 text-sm"/>
<button type="submit" className="border rounded p-2 text-sm" disabled={!(entries[key] || '').trim()} aria-label={`${label} ekle`}>Ekle</button>
</form>
<ul className="space-y-2">{(caps[key] || []).map((value,index)=><li key={`${value}:${index}`} className="flex justify-between gap-2 text-sm break-words"><span>{value}</span><button type="button" aria-label={`${label}: ${value} kaldır`} onClick={()=>edit(key,caps[key].filter((_,i)=>i!==index))} className="text-red-700 shrink-0">Kaldır</button></li>)}</ul>
</section>)}
</fieldset>
<HealthPortfolioEditor items={caps.healthPortfolio || []} disabled={!!busy} onChange={items=>edit('healthPortfolio',items)}/>
<label className="block text-sm">Uyum eşiği: %{threshold}<input aria-label="Uyum eşiği" type="range" min={30} max={95} step={5} disabled={!!busy} value={threshold} onChange={e=>{setThreshold(Number(e.target.value));setMessage('Kaydedilmemiş değişiklikler var.');}} className="block w-full"/></label>
<p className="text-xs text-slate-500">Uyum skoru terim eşleşmesi ve varsa anlamsal yakınlık girdisidir; başvuru yeterliliği değildir.</p>
<button type="button" onClick={save} disabled={!!busy} className="bg-blue-700 text-white px-4 py-2 rounded disabled:opacity-50">Profili kaydet</button>
</section>
</div>;
}