Add health product portfolio and explicit tender item selection
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import {HealthPortfolioEditor} from './HealthPortfolioEditor';
|
||||
import React,{useState,useEffect,useRef,useId} from 'react';
|
||||
import {useApp} from '../context/AppContext';
|
||||
import {getToken} from '../services/auth';
|
||||
@@ -24,7 +25,7 @@ function ProfileEditor(){
|
||||
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(proposal);setMessage('Tarama önerileri alındı. Alanları kontrol edip düzenleyin; henüz profilinize kaydedilmedi.');}
|
||||
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});
|
||||
@@ -61,6 +62,7 @@ function ProfileEditor(){
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import {portfolioFields} from '../services/healthPortfolio';
|
||||
export function HealthPortfolioEditor({items,onChange,disabled}){
|
||||
const patch=(id,change)=>onChange(items.map(item=>item.id===id?{...item,...change}:item));
|
||||
return <fieldset disabled={disabled} className="border rounded-xl p-3 space-y-3">
|
||||
<legend className="font-bold">Sağlık ürün ve hizmet portföyü</legend>
|
||||
<p className="text-xs text-slate-600">Firma beyanıdır; resmi kayıt veya teknik uygunluk doğrulaması değildir. İhale hazırlığında seçilen ürünün bu sürümü taslakta tutulur.</p>
|
||||
{items.map((item,index)=><section key={item.id} className="border rounded p-3 grid sm:grid-cols-2 gap-2">
|
||||
<h3 className="font-bold text-sm sm:col-span-2">Portföy kaydı {index+1}</h3>
|
||||
{portfolioFields.map(([key,label])=><label key={key} className="text-xs">{label}<input aria-label={`Portföy ${index+1} ${label}`} className="block w-full border rounded p-2" maxLength={1000} value={item[key] || ''} onChange={e=>patch(item.id,{[key]:e.target.value})}/></label>)}
|
||||
<label className="text-xs">Kapsam<select aria-label={`Portföy ${index+1} Kapsam`} className="block w-full border rounded p-2" value={item.kind || 'consumable'} onChange={e=>patch(item.id,{kind:e.target.value})}><option value="consumable">Sarf / test ürünü</option><option value="device">Cihaz ve servis</option><option value="staffedService">Personelli hizmet</option></select></label>
|
||||
<label className="text-xs">ÜTS beyanı<select aria-label={`Portföy ${index+1} ÜTS beyanı`} className="block w-full border rounded p-2" value={item.utsStatus || 'unknown'} onChange={e=>patch(item.id,{utsStatus:e.target.value})}><option value="unknown">Kontrol gerekli</option><option value="declared">Kayıt mevcut — firma beyanı</option><option value="notApplicable">Uygulanmıyor — açıklama gerekli</option></select></label>
|
||||
<button type="button" className="text-red-700 text-xs" onClick={()=>onChange(items.filter(row=>row.id!==item.id))}>Portföy kaydı {index+1} kaldır</button>
|
||||
</section>)}
|
||||
<button type="button" className="border rounded p-2 text-sm" onClick={()=>onChange([...items,{id:crypto.randomUUID(),kind:'consumable',utsStatus:'unknown'}])}>Portföy kaydı ekle</button>
|
||||
</fieldset>;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import {useApp} from '../context/AppContext';
|
||||
import {portfolioProductText,selectPortfolioProduct} from '../services/healthPortfolio';
|
||||
import React, {useState} from 'react';
|
||||
import {HEALTH_CHECKS, documentKey, documentRevision, documentScopeProblem} from '../services/preparationChecks';
|
||||
const fieldClass = 'w-full border border-slate-300 rounded-lg p-2 text-xs bg-white';
|
||||
@@ -6,6 +8,8 @@ const Status = ({value, onChange, optional = true}) => <select aria-label="Kontr
|
||||
{optional && <option value="notApplicable">Uygulanmıyor — gerekçe gerekli</option>}
|
||||
</select>;
|
||||
export function PreparationChecks({rows, documents, manualDocuments, setManualDocuments, checks, setChecks, vault, refreshVault, downloadFile}) {
|
||||
const {userProfile}=useApp();
|
||||
const portfolio=userProfile.scrapedCapabilities?.healthPortfolio || [];
|
||||
const eligibleFiles = vault.documents.filter(file => !documentScopeProblem(file,vault.tenderIkn));
|
||||
const [name,setName] = useState('');
|
||||
const [clause,setClause] = useState('');
|
||||
@@ -56,7 +60,16 @@ export function PreparationChecks({rows, documents, manualDocuments, setManualDo
|
||||
return <details key={row.priceKey} className="border rounded-xl p-3">
|
||||
<summary className="cursor-pointer text-xs font-bold">{row.name} — ürün/hizmet ve yükümlülük kontrolü</summary>
|
||||
<div className="space-y-2 mt-3">
|
||||
<label className="block text-xs">Teklif edilen marka/model veya hizmet kapsamı<input className={fieldClass} value={c.product || ''} onChange={e=>updateProduct(row.priceKey,{product:e.target.value})}/></label>
|
||||
<p className="text-xs text-slate-600">Ürün seçimini veya marka/model metnini değiştirirseniz teknik kontroller ve kaynak açıklamaları yeniden doldurulmalıdır.</p>
|
||||
<label className="block text-xs">Firma portföyünden seç
|
||||
<select className={fieldClass} value={c.portfolioId || ''} onChange={e=>updateProduct(row.priceKey,selectPortfolioProduct(portfolio.find(item=>item.id===e.target.value)))}>
|
||||
<option value="">Elle kapsam girin</option>
|
||||
{c.portfolioId&&!portfolio.some(item=>item.id===c.portfolioId)&&<option value={c.portfolioId}>Önceki portföy kaydı kaldırılmış</option>}
|
||||
{portfolio.filter(item=>portfolioProductText(item)).map(item=><option key={item.id} value={item.id}>{portfolioProductText(item)}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
{c.portfolioSnapshot&&<p className="text-xs text-slate-600">Seçim anındaki firma beyanı: {c.portfolioSnapshot.kind==='device'?'Cihaz ve servis':c.portfolioSnapshot.kind==='staffedService'?'Personelli hizmet':'Sarf / test ürünü'} · ÜTS: {{declared:'Kayıt mevcut — firma beyanı',notApplicable:'Uygulanmıyor — firma beyanı',unknown:'Kontrol gerekli'}[c.portfolioSnapshot.utsStatus] || 'Kontrol gerekli'} · {c.portfolioSnapshot.utsReference || 'Kayıt açıklaması yok'} · Servis: {c.portfolioSnapshot.serviceCapacity || 'Belirtilmedi'}. İhale koşullarını aşağıda ayrıca doğrulayın.</p>}
|
||||
<label className="block text-xs">Teklif edilen marka/model veya hizmet kapsamı<input className={fieldClass} value={c.product || ''} onChange={e=>updateProduct(row.priceKey,{...selectPortfolioProduct(null),product:e.target.value})}/></label>
|
||||
<label className="block text-xs">Teknik doküman/madde ve uygunluk dayanağı<input className={fieldClass} value={c.source || ''} onChange={e=>updateProduct(row.priceKey,{source:e.target.value})}/></label>
|
||||
<label className="block text-xs">Ürün/hizmet uygunluğu<select className={fieldClass} value={c.status || 'pending'} onChange={e=>updateProduct(row.priceKey,{status:e.target.value})}>
|
||||
<option value="pending">Kontrol gerekli</option><option value="suitable">Uygun</option><option value="unsuitable">Uygun değil</option>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import {HEALTH_CHECKS} from './preparationChecks.js';
|
||||
export const portfolioFields=[['group','Ürün grubu'],['brand','Marka'],['model','Model / hizmet adı'],['utsReference','ÜTS kayıt numarası veya açıklaması'],['serviceCapacity','Kurulum / bakım / servis kapasitesi']];
|
||||
export function portfolioProductText(item){return [item.group,item.brand,item.model].filter(Boolean).join(' · ');}
|
||||
export function selectPortfolioProduct(item){
|
||||
return {portfolioId:item?.id || null,portfolioSnapshot:item ? {...item} : null,product:item ? portfolioProductText(item) : '',source:'',status:'pending',
|
||||
...Object.fromEntries(HEALTH_CHECKS.map(([key])=>[key,{status:'pending',evidence:''}]))};
|
||||
}
|
||||
Reference in New Issue
Block a user