fix: share persisted preparation checks across analysis screens

This commit is contained in:
2026-09-06 08:44:10 +03:00
parent e4b02f5dd3
commit 05c7e7ecdc
4 changed files with 52 additions and 43 deletions
+39 -39
View File
@@ -1,8 +1,11 @@
import React, { useState } from 'react';
import React, { useRef } from 'react';
import { useApp } from '../context/AppContext';
import { bidSourceSnapshot } from '../services/bidSource';
import { documentKey } from '../services/preparationChecks';
import { YeterlilikRozeti } from './YeterlilikRozeti';
import {
AlertOctagon, AlertTriangle, Eye, CalendarClock, ClipboardList, Route,
HelpCircle, Coins, Target, CheckSquare, Square, Package,
HelpCircle, Coins, Target, Package,
} from 'lucide-react';
import { BidPackageBuilder } from './BidPackageBuilder';
@@ -45,8 +48,16 @@ const Baslik = ({ icon: Icon, renk, children, not }) => (
</h4>
);
export const DeepAnalysisReport = ({ results, tender }) => {
const [isaretli, setIsaretli] = useState({});
export const DeepAnalysisReport = ({ results, tender: selectedTender }) => {
const {tenders, mutateTender, savingCount, saveError} = useApp();
const preparationRef = useRef(null);
const tender = {...selectedTender, ...tenders.find(t => t.id === selectedTender?.id)};
const sourceSnapshot = bidSourceSnapshot(tender, results);
const savedSteps = Array.isArray(tender.checklist) ? tender.checklist : [];
const stepDone = key => savedSteps.some(s => s.key === key && s.sourceSnapshot === sourceSnapshot && s.done);
const toggleStep = key => mutateTender(tender.id, {checklist:[
...savedSteps.filter(s => s.key !== key), {key, sourceSnapshot, done:!stepDone(key)},
]});
if (!results) return null;
const {
@@ -59,7 +70,6 @@ export const DeepAnalysisReport = ({ results, tender }) => {
dolu(roadmap) || dolu(questionsForAdministration) || dolu(itemsAndSpecs);
if (!varMi) return null;
const degistir = (k) => setIsaretli((o) => ({ ...o, [k]: !o[k] }));
return (
<div className="space-y-6">
@@ -181,41 +191,26 @@ export const DeepAnalysisReport = ({ results, tender }) => {
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Belge kontrol listesi */}
{/* Source requirements summarize the single saved preparation checklist. */}
{dolu(documentChecklist) && (
<div className="bg-white p-5 rounded-2xl border border-slate-200 shadow-xs space-y-3">
<Baslik icon={ClipboardList} renk="text-blue-600" not="işaretleyerek ilerleyin">
Belge Kontrol Listesi
</Baslik>
<Baslik icon={ClipboardList} renk="text-blue-600">Belge Gereksinimleri</Baslik>
<p className="text-xs text-slate-500">Kontrolleri aşağıdaki hazırlık formunda kaynaklarıyla kaydedin.</p>
<ul className="space-y-1.5">
{documentChecklist.map((b, i) => {
const k = `b${i}`;
const secili = !!isaretli[k];
return (
<li key={i}>
<button
type="button"
onClick={() => degistir(k)}
className="w-full text-left flex items-start gap-2 bg-slate-50 hover:bg-slate-100 border border-slate-200 rounded-lg p-2.5 transition-colors"
>
{secili
? <CheckSquare className="w-4 h-4 text-emerald-600 shrink-0 mt-0.5" />
: <Square className="w-4 h-4 text-slate-300 shrink-0 mt-0.5" />}
<span className="flex-1 min-w-0">
<span className={`text-xs font-semibold block ${secili ? 'line-through text-slate-400' : 'text-slate-800'}`}>
{b.document || '—'}
</span>
{b.note && <span className="text-[11px] text-slate-500 leading-snug block mt-0.5">{b.note}</span>}
</span>
<YeterlilikRozeti belge={b.document} />
{b.mandatory === false
? <span className="text-[10px] font-bold text-slate-500 bg-slate-200 px-1.5 py-0.5 rounded shrink-0">İsteğe bağlı</span>
: <span className="text-[10px] font-bold text-red-700 bg-red-100 px-1.5 py-0.5 rounded shrink-0">Zorunlu</span>}
</button>
</li>
);
const check = tender.bidDraft?.checks?.documents?.[documentKey(b)];
return <li key={i} className="bg-slate-50 border border-slate-200 rounded-lg p-2.5 space-y-1">
<span className="text-xs font-semibold">{b.document || '—'}</span>
<p className="text-[11px] text-slate-500">{b.note}</p>
<YeterlilikRozeti belge={b.document} />
<p className="text-[11px] text-slate-600">{check?.status === 'verified' && check?.evidence?.trim()
? 'Kayıtlı kullanıcı kontrolü var; güncel kaynak kontrolünü hazırlık formundan doğrulayın.'
: 'Belge kontrolü bekliyor.'}</p>
</li>;
})}
</ul>
<button type="button" onClick={() => {preparationRef.current?.scrollIntoView({behavior:'smooth',block:'start'});preparationRef.current?.focus({preventScroll:true});}}
className="text-xs font-bold text-blue-700">Belge ve ürün kontrollerine git</button>
</div>
)}
@@ -262,15 +257,17 @@ export const DeepAnalysisReport = ({ results, tender }) => {
</Baslik>
<ol className="space-y-2">
{roadmap.map((a, i) => {
const k = `y${i}`;
const secili = !!isaretli[k];
const k = JSON.stringify(a);
const secili = stepDone(k);
return (
<li key={i} className="flex gap-3">
<div className="flex flex-col items-center shrink-0">
<button
type="button"
onClick={() => degistir(k)}
aria-label={`${i + 1}. adımı tamamlandı işaretle`}
disabled={savingCount > 0}
onClick={() => toggleStep(k)}
aria-label={`${i + 1}. adımı ${secili ? 'yeniden aç' : 'tamamlandı işaretle'}`}
aria-pressed={secili}
className={`w-7 h-7 rounded-full text-white text-[11px] font-black flex items-center justify-center transition-colors ${
secili ? 'bg-emerald-600' : (FAZ_RENK[a.phase] || 'bg-slate-700')
}`}
@@ -323,7 +320,10 @@ export const DeepAnalysisReport = ({ results, tender }) => {
)}
{/* Analiz ve yol haritası okunduktan sonraki adım: teklif zarfını hazırla */}
<BidPackageBuilder key={tender?.ikn || tender?.id} results={results} tender={tender} />
{saveError && <p role="alert" className="text-xs text-red-700">{saveError}</p>}
<div ref={preparationRef} tabIndex={-1} aria-label="Kalıcı ihale hazırlığı">
<BidPackageBuilder key={tender?.ikn || tender?.id} results={results} tender={tender} />
</div>
</div>
);