fix: preserve bid drafts and align tender source data

This commit is contained in:
2026-09-06 08:40:10 +03:00
parent c91631d99f
commit 456728b4bd
26 changed files with 510 additions and 81 deletions
+26 -21
View File
@@ -1,4 +1,5 @@
import React, { useState, useMemo } from 'react';
import { bidRowTotal, validateBidRows, roundMoney } from '../services/bidValidation';
import { useApp } from '../context/AppContext';
import {
FolderCheck, ChevronDown, ChevronUp, FileSignature, Table2, ListChecks,
@@ -34,35 +35,38 @@ const FIRMA_ANAHTAR = 'ihaleist_firma_bilgi';
const bosFirma = () => Object.fromEntries(ALANLAR.map((a) => [a.k, '']));
export const BidPackageBuilder = ({ results, tender }) => {
const { userProfile } = useApp();
const { userProfile, mutateTender, savingCount, authUser } = useApp();
const storedDraft = tender?.bidDraft || {};
const [dirty, setDirty] = useState(false);
const [acik, setAcik] = useState(true);
const [hata, setHata] = useState(null);
const [firma, setFirma] = useState(() => {
let kayitli = {};
try { kayitli = JSON.parse(localStorage.getItem(FIRMA_ANAHTAR) || '{}'); } catch { /* yoksay */ }
try { kayitli = JSON.parse(localStorage.getItem(FIRMA_ANAHTAR + ':' + authUser?.email) || '{}'); } catch { /* yoksay */ }
return {
...bosFirma(),
firmaUnvan: userProfile?.company || '',
...kayitli,
...(storedDraft.firma || {}),
};
});
const [teklifTuru, setTeklifTuru] = useState('birim');
const [teklifTuru, setTeklifTuru] = useState(storedDraft.teklifTuru || 'birim');
const kaynakGecerlilik = /(?:geçerlilik[^.]{0,100}?|ihale tarihinden itibaren\s*)(\d+)\s*(?:takvim\s*)?gün/i.exec(tender?.aciklama || '');
const [gecerlilikGun, setGecerlilikGun] = useState(kaynakGecerlilik?.[1] || '');
const [kisim, setKisim] = useState('');
const [goturuBedel, setGoturuBedel] = useState('');
const [gecerlilikGun, setGecerlilikGun] = useState(storedDraft.gecerlilikGun || kaynakGecerlilik?.[1] || '');
const [kisim, setKisim] = useState(storedDraft.kisim || '');
const [goturuBedel, setGoturuBedel] = useState(storedDraft.goturuBedel || '');
const kalemler = tender?.items?.length
? tender.items.map(k => ({name:k.ad,quantity:`${k.miktar} ${k.birim || ''}`,keySpecs:[]}))
: (Array.isArray(results?.itemsAndSpecs) ? results.itemsAndSpecs : []);
const [fiyatlar, setFiyatlar] = useState({});
const [fiyatlar, setFiyatlar] = useState(storedDraft.fiyatlar || {});
const alanYaz = (k, v) => {
const yeni = { ...firma, [k]: v };
setFirma(yeni);
try { localStorage.setItem(FIRMA_ANAHTAR, JSON.stringify(yeni)); } catch { /* kota */ }
try { localStorage.setItem(FIRMA_ANAHTAR + ':' + authUser?.email, JSON.stringify(yeni)); } catch { /* kota */ }
};
const satirlar = useMemo(
@@ -70,21 +74,17 @@ export const BidPackageBuilder = ({ results, tender }) => {
[kalemler, fiyatlar],
);
const kalemToplami = useMemo(
() => satirlar.reduce((t, r) => {
const { miktar } = miktarAyristir(r.quantity);
return t + (miktar ?? 0) * (Number(r.birimFiyat) || 0);
}, 0),
[satirlar],
);
const kalemToplami = useMemo(() => roundMoney(satirlar.reduce((t,r) => t + (bidRowTotal(r) ?? 0),0)), [satirlar]);
const saveDraft = async () => {
const saved = await mutateTender(tender.id, {bidDraft:{firma,teklifTuru,gecerlilikGun,kisim,goturuBedel,fiyatlar}});
if (saved) { setDirty(false); setHata(null); }
else setHata('Taslak kaydedilemedi. Tekrar deneyin.');
};
const toplam = teklifTuru === 'goturu' ? (Number(goturuBedel) || 0) : kalemToplami;
const eksikler = ALANLAR.filter((a) => a.zorunlu && !String(firma[a.k] || '').trim());
const kalemEksik = teklifTuru === 'birim' && (!satirlar.length || satirlar.some(r => {
const {miktar,birim} = miktarAyristir(r.quantity);
return !r.name || !(miktar > 0) || !birim || !(Number(r.birimFiyat) > 0);
}));
const kalemEksik = teklifTuru === 'birim' && validateBidRows(satirlar).length > 0;
const turCeliskisi = teklifTuru === 'goturu' && /birim fiyat/i.test(tender?.aciklama || '');
const sureEksik = !(Number(gecerlilikGun) > 0) || (kaynakGecerlilik && Number(gecerlilikGun) < Number(kaynakGecerlilik[1]));
const hazir = eksikler.length === 0 && toplam > 0 && !kalemEksik && !turCeliskisi && !sureEksik;
@@ -135,7 +135,12 @@ export const BidPackageBuilder = ({ results, tender }) => {
</button>
{acik && (
<div className="border-t border-emerald-200 p-5 space-y-5">
<div className="border-t border-emerald-200 p-5 space-y-5" onChange={() => setDirty(true)}>
<div className="flex items-center gap-3">
<button type="button" onClick={saveDraft} disabled={savingCount > 0}
className="px-4 py-2 rounded-lg bg-blue-700 text-white text-xs disabled:opacity-50">Hazırlık taslağını kaydet</button>
<span role="status" className="text-xs">{dirty ? 'Kaydedilmemiş değişiklikler var' : storedDraft.firma ? 'Sunucuda kayıtlı taslak' : 'Henüz kaydedilmedi'}</span>
</div>
{/* Sizden beklenen bilgiler */}
{eksikler.length > 0 && (
@@ -238,7 +243,7 @@ export const BidPackageBuilder = ({ results, tender }) => {
<tbody>
{satirlar.map((r, i) => {
const { miktar, birim } = miktarAyristir(r.quantity);
const tutar = (miktar ?? 0) * (Number(r.birimFiyat) || 0);
const tutar = bidRowTotal(r);
return (
<tr key={i} className="border-b border-slate-100">
<td className="py-1.5 pr-2 text-slate-800">{r.name || '—'}</td>