75 lines
7.1 KiB
React
75 lines
7.1 KiB
React
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';
|
||
const Status = ({value, onChange, optional = true}) => <select aria-label="Kontrol durumu" className={fieldClass} value={value || 'pending'} onChange={e => onChange(e.target.value)}>
|
||
<option value="pending">Kontrol gerekli</option><option value="verified">Doğrulandı</option>
|
||
{optional && <option value="notApplicable">Uygulanmıyor — gerekçe gerekli</option>}
|
||
</select>;
|
||
export function PreparationChecks({rows, documents, manualDocuments, setManualDocuments, checks, setChecks, vault, refreshVault, downloadFile}) {
|
||
const eligibleFiles = vault.documents.filter(file => !documentScopeProblem(file,vault.tenderIkn));
|
||
const [name,setName] = useState('');
|
||
const [clause,setClause] = useState('');
|
||
const updateDocument = (key, patch) => setChecks(old => ({...old,documents:{...old.documents,[key]:{...old.documents?.[key],...patch}}}));
|
||
const updateProduct = (key, patch) => setChecks(old => ({...old,products:{...old.products,[key]:{...old.products?.[key],...patch}}}));
|
||
return <section className="space-y-4" aria-label="Belge ve ürün uygunluğu">
|
||
<h5 className="font-extrabold text-sm">Belge ve ürün uygunluğu</h5>
|
||
<p className="text-xs text-slate-600">Her kontrol için belge adı/sürümü ve kaynak maddeyi yazın. ÜTS, numune ve servis koşulları her ihalede zorunlu değildir; uygulanmıyorsa gerekçesini belirtin. Bu kayıtlar kullanıcı kontrolüdür; resmi uygunluk onayı değildir.</p>
|
||
<div className="space-y-2">
|
||
<p className="text-xs">Firma belgelerini veya bu İKN için yüklenmiş ihale dokümanlarını ilgili gereksinime bağlayın. Başka ihale dokümanları seçilemez.</p>
|
||
<button type="button" onClick={refreshVault} disabled={vault.loading} className="text-xs font-bold text-blue-700">Belge deposunu yenile</button>
|
||
{vault.loading && <p role="status" className="text-xs">Belge deposu yükleniyor…</p>}
|
||
{vault.error && <p role="alert" className="text-xs text-red-700">Belge deposu yüklenemedi: {vault.error}</p>}
|
||
{documents.map(d => {
|
||
const key = documentKey(d), c = checks.documents?.[key] || {};
|
||
return <fieldset key={key} className="border rounded-xl p-3 space-y-2">
|
||
<legend className="text-xs font-bold">{d.document} {d.mandatory === false ? '(isteğe bağlı)' : '(zorunlu)'}</legend>
|
||
<p className="text-xs text-slate-600">{d.note}</p>
|
||
{manualDocuments.some(item => documentKey(item) === key) && <button type="button" className="text-xs text-red-700"
|
||
onClick={() => setManualDocuments(manualDocuments.filter(item => documentKey(item) !== key))}>Elle eklenen gereksinimi kaldır</button>}
|
||
<label className="block text-xs">Bağlı dosya
|
||
<select className={fieldClass} value={c.documentId || ''} disabled={vault.loading || Boolean(vault.error)} onChange={e=>{
|
||
const file=eligibleFiles.find(f=>f.id===e.target.value);
|
||
updateDocument(key,{documentId:file?.id || null,documentRevision:file ? documentRevision(file) : null,status:'pending'});
|
||
}}>
|
||
<option value="">Dosya seçin</option>
|
||
{c.documentId && !eligibleFiles.some(f=>f.id===c.documentId) && <option value={c.documentId}>Önceki dosya bulunamadı veya bu ihale için kullanılamıyor</option>}
|
||
{eligibleFiles.map(file=><option key={file.id} value={file.id}>{file.ad} · {file.son_kullanma || 'Süre bilgisi yok'}{file.erisilebilir === false ? ' · erişilemiyor' : ''}</option>)}
|
||
</select>
|
||
</label>
|
||
{vault.documents.some(f=>f.id===c.documentId) && <button type="button" className="text-xs text-blue-700" onClick={()=>downloadFile(vault.documents.find(f=>f.id===c.documentId))}>Bağlı dosyayı indir</button>}
|
||
<Status value={c.status} optional={d.mandatory === false} onChange={status => updateDocument(key,{status})}/>
|
||
<label className="block text-xs">Belge adı/sürümü, geçerlilik kontrolü ve kaynak madde
|
||
<input className={fieldClass} value={c.evidence || ''} onChange={e => updateDocument(key,{evidence:e.target.value})}/>
|
||
</label>
|
||
</fieldset>;
|
||
})}
|
||
<div className="border border-dashed rounded-xl p-3 space-y-2">
|
||
<p className="text-xs font-semibold">Resmi dokümandan eksik belge gereksinimi ekle</p>
|
||
<label className="block text-xs">Belge adı<input className={fieldClass} value={name} onChange={e=>setName(e.target.value)}/></label>
|
||
<label className="block text-xs">Kaynak doküman ve madde<input className={fieldClass} value={clause} onChange={e=>setClause(e.target.value)}/></label>
|
||
<button type="button" className="text-xs font-bold text-blue-700 disabled:opacity-40" disabled={!name.trim() || !clause.trim()}
|
||
onClick={()=>{const d={document:name.trim(),note:clause.trim(),mandatory:true};if(!documents.some(x=>documentKey(x)===documentKey(d)))setManualDocuments([...manualDocuments,d]);setName('');setClause('');}}>Belge gereksinimi ekle</button>
|
||
</div>
|
||
</div>
|
||
{rows.map(row => {
|
||
const c=checks.products?.[row.priceKey] || {};
|
||
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>
|
||
<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>
|
||
</select></label>
|
||
{HEALTH_CHECKS.map(([key,label])=><fieldset key={key} className="bg-slate-50 rounded-lg p-2 space-y-1">
|
||
<legend className="text-xs font-semibold">{label}</legend>
|
||
<Status value={c[key]?.status} onChange={status=>updateProduct(row.priceKey,{[key]:{...c[key],status}})}/>
|
||
<label className="block text-xs">Kaynak, karşılanan koşul veya uygulanmama gerekçesi<input className={fieldClass} value={c[key]?.evidence || ''} onChange={e=>updateProduct(row.priceKey,{[key]:{...c[key],evidence:e.target.value}})}/></label>
|
||
</fieldset>)}
|
||
<label className="block text-xs">Sorumlu<input className={fieldClass} value={c.owner || ''} onChange={e=>updateProduct(row.priceKey,{owner:e.target.value})}/></label>
|
||
</div>
|
||
</details>;
|
||
})}
|
||
</section>;
|
||
}
|