fix: persist tender preparation and correct calendar export
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { BidPackageBuilder } from './BidPackageBuilder';
|
||||
import { TenderDocuments } from './TenderDocuments';
|
||||
import { TeklifAsistani } from './TeklifAsistani';
|
||||
import { TeklifCetveliKalemleri } from './TeklifCetveliKalemleri';
|
||||
@@ -7,13 +8,13 @@ import {
|
||||
X, Star, Share2, Calendar, MapPin, Building2, Tag, FileText, CheckSquare,
|
||||
DollarSign, Calculator, Download, Sparkles, AlertTriangle, ShieldCheck,
|
||||
Send, Stamp, ArrowLeft, ExternalLink, Printer, PenLine } from 'lucide-react';
|
||||
import confetti from 'canvas-confetti';
|
||||
import { createTenderICS } from '../services/tenderCalendar';
|
||||
|
||||
export const TenderDetailModal = () => {
|
||||
const { activeTenderDetail, setActiveTenderDetail, toggleTrackTender, userProfile, setIsAiSpecAnalyzerOpen, gorevAc, gorevKontrol, aiAnalysisResults, tenders } = useApp();
|
||||
const { activeTenderDetail, setActiveTenderDetail, toggleTrackTender, userProfile, setIsAiSpecAnalyzerOpen, gorevAc, gorevKontrol, aiAnalysisResults, tenders, mutateTender, savingCount, saveError } = useApp();
|
||||
const [activeTab, setActiveTab] = useState('details'); // 'details', 'pdf_stamp', 'ai_assistant', 'checklist', 'kurum_tenders'
|
||||
const [noteText, setNoteText] = useState('');
|
||||
const [savedNotes, setSavedNotes] = useState([]);
|
||||
const [actionError, setActionError] = useState(null);
|
||||
|
||||
// Kur Farkı Calculator State
|
||||
const [bidAmount, setBidAmount] = useState(15000);
|
||||
@@ -28,30 +29,23 @@ export const TenderDetailModal = () => {
|
||||
// Havuzdaki güncel kayıt: takip/görev değişince düğme etiketleri de değişsin.
|
||||
const tender = (activeTenderDetail && tenders.find((t) => t.id === activeTenderDetail.id)) || activeTenderDetail;
|
||||
|
||||
const handleAddNote = (e) => {
|
||||
const savedNotes = tender.notes || [];
|
||||
const handleAddNote = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!noteText.trim()) return;
|
||||
setSavedNotes([...savedNotes, {
|
||||
const saved = await mutateTender(tender.id, {notes: [...savedNotes, {
|
||||
id: Date.now(),
|
||||
author: userProfile.name,
|
||||
text: noteText.trim(),
|
||||
date: 'Az önce'
|
||||
}]);
|
||||
setNoteText('');
|
||||
date: new Date().toLocaleString('tr-TR')
|
||||
}]});
|
||||
if (saved) setNoteText('');
|
||||
};
|
||||
|
||||
const handleExportICS = () => {
|
||||
confetti({ particleCount: 50 });
|
||||
const icsData = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//İhaleist//İhale Takvimi//TR
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:İhale Son Teslim: ${tender.baslik}
|
||||
DESCRIPTION:İKN: ${tender.ikn} - ${tender.kurum}
|
||||
DTSTART:20260810T120000Z
|
||||
DTEND:20260810T130000Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR`;
|
||||
let icsData;
|
||||
try { icsData = createTenderICS(tender); setActionError(null); }
|
||||
catch (e) { setActionError(e.message); return; }
|
||||
|
||||
const blob = new Blob([icsData], { type: 'text/calendar;charset=utf-8' });
|
||||
const link = document.createElement('a');
|
||||
@@ -60,6 +54,7 @@ END:VCALENDAR`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 1000);
|
||||
};
|
||||
|
||||
const handleStampMouseDown = () => setIsDraggingStamp(true);
|
||||
@@ -77,6 +72,8 @@ END:VCALENDAR`;
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto bg-slate-900/70 backdrop-blur-xs flex justify-center p-2 sm:p-4 animate-fade-in">
|
||||
<div className="bg-white w-full max-w-6xl rounded-2xl shadow-2xl border border-slate-200 overflow-hidden my-auto flex flex-col max-h-[92vh]">
|
||||
|
||||
{(actionError || saveError) && <p role="alert" className="p-3 bg-red-50 text-red-900">{actionError || saveError}</p>}
|
||||
{savingCount > 0 && <p role="status" className="p-2 bg-blue-50">Kaydediliyor…</p>}
|
||||
{/* Top Header Controls */}
|
||||
<div className="bg-[#1f2937] text-white px-6 py-3.5 flex items-center justify-between border-b border-slate-700 shrink-0">
|
||||
<button
|
||||
@@ -383,86 +380,8 @@ END:VCALENDAR`;
|
||||
|
||||
{/* Tab 2: PDF Birim Fiyat Cetveli & Kaşe Editörü */}
|
||||
{activeTab === 'pdf_stamp' && (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-amber-50 border border-amber-200 text-amber-900 p-4 rounded-xl text-xs flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
<Stamp className="w-5 h-5 text-amber-600" />
|
||||
<span>Teklif cetvelinizi doldurun ve Dijital Kaşenizi sürükleyerek belgenin altına yerleştirin.</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
import('../services/pdf').then(({ generateBidSheetPDF }) => {
|
||||
generateBidSheetPDF(tender, { firma: userProfile.company, stamp: true });
|
||||
});
|
||||
}}
|
||||
className="bg-amber-600 hover:bg-amber-500 text-white font-bold px-4 py-2 rounded-lg flex items-center gap-1.5 shadow-2xs"
|
||||
>
|
||||
<Download className="w-4 h-4" /> Kaşeli PDF İndir
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Interactive PDF Sheet Canvas Simulator */}
|
||||
<div
|
||||
onMouseMove={handleStampMouseMove}
|
||||
onMouseUp={handleStampMouseUp}
|
||||
className="bg-slate-200 p-6 rounded-2xl border border-slate-300 relative select-none min-h-[500px] flex justify-center"
|
||||
>
|
||||
{/* Paper sheet */}
|
||||
<div className="bg-white w-full max-w-2xl p-8 rounded-lg shadow-xl border border-slate-300 relative space-y-6 text-slate-800">
|
||||
<div className="text-center border-b-2 border-slate-800 pb-4 space-y-1">
|
||||
<p className="font-extrabold text-sm uppercase">{tender.kurum}</p>
|
||||
<p className="font-bold text-xs text-blue-900">BİRİM FİYAT TEKLİF CETVELİ — İKN: {tender.ikn}</p>
|
||||
</div>
|
||||
|
||||
<table className="w-full text-left text-xs border border-slate-400">
|
||||
<thead className="bg-slate-100 font-bold border-b border-slate-400">
|
||||
<tr>
|
||||
<th className="p-2 border-r border-slate-400">Sıra</th>
|
||||
<th className="p-2 border-r border-slate-400">Malzeme Adı</th>
|
||||
<th className="p-2 border-r border-slate-400">Miktar</th>
|
||||
<th className="p-2 border-r border-slate-400">Birim Fiyat (TL)</th>
|
||||
<th className="p-2">Toplam (TL)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-300">
|
||||
{tender.items?.map((item, idx) => (
|
||||
<tr key={idx}>
|
||||
<td className="p-2 border-r border-slate-400 font-bold text-center">{idx + 1}</td>
|
||||
<td className="p-2 border-r border-slate-400 font-semibold">{item.ad}</td>
|
||||
<td className="p-2 border-r border-slate-400">{item.miktar} {item.birim}</td>
|
||||
<td className="p-2 border-r border-slate-400 bg-yellow-50 font-bold text-blue-900">
|
||||
{item.birimFiyat || 150} TL
|
||||
</td>
|
||||
<td className="p-2 font-mono font-bold text-emerald-800">
|
||||
{((item.birimFiyat || 150) * parseFloat(item.miktar)).toLocaleString('tr-TR')} TL
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="pt-12 text-right">
|
||||
<p className="font-bold text-xs text-slate-800">{userProfile.company}</p>
|
||||
<p className="text-[10px] text-slate-500">Yetkili İmza ve Kaşe Alanı</p>
|
||||
</div>
|
||||
|
||||
{/* Draggable Stamp Element */}
|
||||
<div
|
||||
onMouseDown={handleStampMouseDown}
|
||||
style={{ left: `${stampPos.x}px`, top: `${stampPos.y}px` }}
|
||||
className="absolute cursor-grab active:cursor-grabbing border-2 border-dashed border-red-500 bg-red-500/10 p-3 rounded-lg text-center transform -rotate-6 shadow-md transition-shadow hover:shadow-lg select-none"
|
||||
>
|
||||
<div className="w-32 h-16 border-2 border-red-700 rounded flex flex-col items-center justify-center p-1 text-red-700 font-black text-[9px] uppercase tracking-tighter">
|
||||
<span>{userProfile.company.slice(0, 25)}</span>
|
||||
<span className="text-[8px] text-red-600 font-bold mt-0.5">MÜDÜR İMZA & KAŞE</span>
|
||||
<span className="text-[7px] text-slate-500">2026-08-07 ONAYLI</span>
|
||||
</div>
|
||||
<span className="text-[9px] font-extrabold text-red-600 block mt-1">↕ Sürükleyip Bırakın</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BidPackageBuilder key={tender.ikn || tender.id} tender={tender}
|
||||
results={aiAnalysisResults?.tenderKey === (tender.ikn || tender.id) ? aiAnalysisResults : null} />
|
||||
)}
|
||||
|
||||
{/* Tab 3: AI Asistanı */}
|
||||
|
||||
Reference in New Issue
Block a user