66 lines
6.0 KiB
React
66 lines
6.0 KiB
React
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 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 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 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>;
|
||
}
|