// Version: 2.11.57 (Local Template Requisites) var { useState, useRef, useEffect, useMemo } = React; const { CheckCircle, AlertTriangle, ArrowLeft, Save, Move, Maximize2, Trash2, Plus, Layout, ChevronUp, ChevronDown, Copy, Loader, Download, ExternalLink, Cloud, UploadCloud, FileText, Send, Search } = window; // Fallbacks для безопасности const SafeArrowLeft = ArrowLeft || (({size, className}) => ); const SafeMove = Move || (({size, className}) => ); const SafeMaximize2 = Maximize2 || (({size, className}) => ); const SafeCloud = Cloud || (({size, className}) => ); const SafeUploadCloud = UploadCloud || (({size, className}) => ☁↑); const SendIcon = Send || Download || (({size, className}) => ); const GRID_SIZE = 20; window.FurnitureEditor = ({ user, initialProject, onBack }) => { const getAutoNumber = () => { const today = new Date(); const dd = String(today.getDate()).padStart(2, '0'); const mm = String(today.getMonth() + 1).padStart(2, '0'); return `МЕБ-${dd}/${mm}-`; }; const getSavedSeller = () => { try { const saved = localStorage.getItem('savedSellerRequisites'); if (saved) return JSON.parse(saved); } catch(e) {} return { name: '', inn: '', kpp: '', ogrn: '', account: '', bank: '', bik: '', corr: '', phone: '', site: '', email: '', directorTitle: '', directorName: '', managerName: '', managerPhone: '' }; }; const [project, setProject] = useState(initialProject || { name: 'Новое Мебельное КП', data: {} }); const [clientName, setClientName] = useState(initialProject?.data?.clientName || getAutoNumber()); const [clientDate, setClientDate] = useState(new Date().toISOString().split('T')[0]); const [currentProjectId, setCurrentProjectId] = useState(initialProject?.id || null); // Обратная совместимость const [showBaseRequisites, setShowBaseRequisites] = useState(() => { if (initialProject?.data?.showBaseRequisites !== undefined) return initialProject.data.showBaseRequisites; const hasData = initialProject?.data?.requisites && (initialProject.data.requisites.seller.inn || initialProject.data.requisites.buyer.company); return initialProject?.data?.showRequisites && !hasData ? true : false; }); const [showFullRequisites, setShowFullRequisites] = useState(() => { if (initialProject?.data?.showFullRequisites !== undefined) return initialProject.data.showFullRequisites; const hasData = initialProject?.data?.requisites && (initialProject.data.requisites.seller.inn || initialProject.data.requisites.buyer.company); return initialProject?.data?.showRequisites && hasData ? true : false; }); const [requisites, setRequisites] = useState(() => { if (initialProject?.data?.requisites) return initialProject.data.requisites; return { seller: getSavedSeller(), buyer: { targetTitle: '', company: '', targetName: '' } }; }); // --- TABS & EXPORT STATE --- const [activeTab, setActiveTab] = useState('canvas'); const [isPdf, setIsPdf] = useState(false); // --- EXPORT TOGGLES --- const [exportCanvas, setExportCanvas] = useState(true); const [exportTable, setExportTable] = useState(true); // --- TOOLBAR STATE --- const [saving, setSaving] = useState(false); const [sendingTg, setSendingTg] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); // --- CANVAS SETTINGS --- const [showGrid, setShowGrid] = useState(true); const [snapToGrid, setSnapToGrid] = useState(true); const [orientation, setOrientation] = useState('portrait'); const canvasRef = useRef(null); const canvasWidth = orientation === 'portrait' ? 794 : 1123; const canvasMinHeight = orientation === 'portrait' ? 1110 : 780; const [toast, setToast] = useState({ show: false, message: '', type: 'success' }); const showToast = (message, type = 'success') => { setToast({ show: true, message, type }); setTimeout(() => setToast({ show: false, message: '', type: 'success' }), 4000); }; useEffect(() => { if (window.loadPdfLibrary) { window.loadPdfLibrary().catch(e => console.error("Ошибка загрузки PDF", e)); } }, []); // --- CANVAS DATA --- const [sketches, setSketches] = useState([]); const [draggingImageId, setDraggingImageId] = useState(null); const [dragStart, setDragStart] = useState({ mouseX: 0, mouseY: 0, imageX: 0, imageY: 0 }); const [resizingImageId, setResizingImageId] = useState(null); const [resizeStart, setResizeStart] = useState({ scale: 1, x: 0, y: 0, baseWidth: 100, startX: 0 }); // --- TABLE (СМЕТА) DATA --- const [items, setItems] = useState([{ id: Date.now(), type: 'item', title: '', price: 0, quantity: 1, finalPrice: 0, link: '' }]); const isManagerOrHigher = user?.is_vip || user?.tier === 'speed' || user?.tier === 'manager'; const isSpeedOrVip = user?.is_vip || user?.tier === 'speed'; const canUseLinks = isManagerOrHigher; const isExporting = isPdf; const isInitialMount = useRef(true); useEffect(() => { if (isInitialMount.current) isInitialMount.current = false; else setHasUnsavedChanges(true); }, [items, clientName, clientDate, sketches, orientation, showBaseRequisites, showFullRequisites, requisites]); useEffect(() => { if (initialProject && initialProject.data) { setCurrentProjectId(initialProject.id); if (initialProject.data.clientName !== undefined) setClientName(initialProject.data.clientName); if (initialProject.data.clientDate) setClientDate(initialProject.data.clientDate); if (initialProject.data.sketches) setSketches(initialProject.data.sketches); if (initialProject.data.orientation) setOrientation(initialProject.data.orientation); if (initialProject.data.items) setItems(initialProject.data.items); if (initialProject.data.showBaseRequisites !== undefined) setShowBaseRequisites(initialProject.data.showBaseRequisites); if (initialProject.data.showFullRequisites !== undefined) setShowFullRequisites(initialProject.data.showFullRequisites); if (initialProject.data.showBaseRequisites === undefined && initialProject.data.showFullRequisites === undefined && initialProject.data.showRequisites !== undefined) { const hasData = initialProject.data.requisites && (initialProject.data.requisites.seller.inn || initialProject.data.requisites.buyer.company); if (hasData) setShowFullRequisites(initialProject.data.showRequisites); else setShowBaseRequisites(initialProject.data.showRequisites); } if (initialProject.data.requisites) setRequisites(initialProject.data.requisites); setTimeout(() => setHasUnsavedChanges(false), 100); } }, [initialProject]); useEffect(() => { document.querySelectorAll('.sketch-textarea').forEach(el => { el.style.height = 'auto'; el.style.height = el.scrollHeight + 'px'; }); }, [sketches, isExporting]); const handleBackClick = () => { if (hasUnsavedChanges) { if (!confirm("У вас есть несохраненные изменения. Выйти без сохранения?")) return; } onBack(); }; const getFormattedDateForPrint = () => { if (!clientDate) return ''; const parts = clientDate.split('-'); if (parts.length === 3) return `${parts[2]}.${parts[1]}.${parts[0]}`; return clientDate; }; const setReqField = (section, field, value) => { setRequisites(prev => ({ ...prev, [section]: { ...prev[section], [field]: value } })); }; const saveSellerToLocal = () => { try { localStorage.setItem('savedSellerRequisites', JSON.stringify(requisites.seller)); showToast('Реквизиты продавца успешно сохранены как шаблон!'); } catch (e) { showToast('Ошибка сохранения шаблона', 'error'); } }; const handleAddImages = (files, startX = 40, startY = 220) => { Array.from(files).forEach((file, index) => { if (file && file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = async (event) => { let res = event.target.result; if (window.compressImage) { res = await window.compressImage(res, 1500, 0.85); } let safeX = Math.max(0, Math.min(startX + (index * 40), canvasWidth - 100)); let safeY = Math.max(0, Math.min(startY + (index * 40), canvasMinHeight - 100)); setSketches(prev => [...prev, { id: Date.now() + index + Math.random(), type: 'image', url: res, scale: 1, x: safeX, y: safeY }]); }; reader.readAsDataURL(file); } }); }; const handleAddText = () => { let safeX = Math.max(0, Math.min(40, canvasWidth - 100)); let safeY = Math.max(0, Math.min(220, canvasMinHeight - 100)); setSketches(prev => [...prev, { id: Date.now() + Math.random(), type: 'text', text: 'Введите текст...', scale: 1, width: 250, fontSize: 16, x: safeX, y: safeY }]); }; const handleFontSize = (e, id, delta) => { e.preventDefault(); e.stopPropagation(); setSketches(prev => prev.map(s => { if (s.id === id && s.type === 'text') { const currentSize = s.fontSize || 16; const newSize = Math.max(8, Math.min(120, currentSize + delta * 2)); return { ...s, fontSize: newSize }; } return s; })); }; const handleFileInput = (e) => { handleAddImages(e.target.files, 40, 220); e.target.value = ''; }; const handleCanvasDrop = (e) => { if (isExporting) return; e.preventDefault(); e.stopPropagation(); if (!canvasRef.current) return; const rect = canvasRef.current.getBoundingClientRect(); let dropX = e.clientX - rect.left; let dropY = e.clientY - rect.top; if (snapToGrid) { dropX = Math.round(dropX / GRID_SIZE) * GRID_SIZE; dropY = Math.round(dropY / GRID_SIZE) * GRID_SIZE; } dropX = Math.max(0, Math.min(dropX, canvasWidth - 100)); dropY = Math.max(0, Math.min(dropY, canvasMinHeight - 100)); handleAddImages(e.dataTransfer.files, dropX, dropY); }; const handleDragOver = (e) => { if (!isExporting) { e.preventDefault(); e.stopPropagation(); } }; const handleRemoveSketch = (id) => { setSketches(prev => prev.filter(s => s.id !== id)); }; const handleMouseDownPan = (e, sketch) => { if (isExporting) return; e.preventDefault(); e.stopPropagation(); setDraggingImageId(sketch.id); setDragStart({ mouseX: e.clientX, mouseY: e.clientY, imageX: sketch.x, imageY: sketch.y }); }; const handleMouseDownResize = (e, sketch) => { if (isExporting) return; e.preventDefault(); e.stopPropagation(); const targetElement = e.currentTarget.parentElement.querySelector('.resize-target'); const rect = targetElement ? targetElement.getBoundingClientRect() : { width: 100 }; const currentScale = sketch.type === 'text' ? 1 : sketch.scale; const baseWidth = rect.width / currentScale; setResizingImageId(sketch.id); setResizeStart({ x: e.clientX, y: e.clientY, scale: currentScale, baseWidth: baseWidth > 0 ? baseWidth : 100, startX: sketch.x }); }; useEffect(() => { const handleGlobalMouseMove = (e) => { if (draggingImageId) { const deltaX = e.clientX - dragStart.mouseX; const deltaY = e.clientY - dragStart.mouseY; let newX = dragStart.imageX + deltaX; let newY = dragStart.imageY + deltaY; if (snapToGrid) { newX = Math.round(newX / GRID_SIZE) * GRID_SIZE; newY = Math.round(newY / GRID_SIZE) * GRID_SIZE; } newX = Math.max(0, Math.min(newX, canvasWidth - 100)); newY = Math.max(0, Math.min(newY, canvasMinHeight - 100)); setSketches(prev => prev.map(s => s.id === draggingImageId ? { ...s, x: newX, y: newY } : s)); } else if (resizingImageId) { const deltaX = e.clientX - resizeStart.x; setSketches(prev => prev.map(s => { if (s.id === resizingImageId) { if (s.type === 'text') { let newWidth = resizeStart.baseWidth + deltaX; if (snapToGrid) { const expectedRightEdge = resizeStart.startX + newWidth; const snappedRightEdge = Math.round(expectedRightEdge / GRID_SIZE) * GRID_SIZE; newWidth = snappedRightEdge - resizeStart.startX; } newWidth = Math.max(100, newWidth); return { ...s, width: newWidth }; } else { let expectedWidth = resizeStart.baseWidth * resizeStart.scale + deltaX; let expectedRightEdge = resizeStart.startX + expectedWidth; let newScale; if (snapToGrid) { let snappedRightEdge = Math.round(expectedRightEdge / GRID_SIZE) * GRID_SIZE; newScale = (snappedRightEdge - resizeStart.startX) / resizeStart.baseWidth; } else { newScale = expectedWidth / resizeStart.baseWidth; } newScale = Math.max(0.1, Math.min(newScale, 5)); return { ...s, scale: newScale }; } } return s; })); } }; const handleGlobalMouseUp = () => { setDraggingImageId(null); setResizingImageId(null); }; if (draggingImageId || resizingImageId) { window.addEventListener('mousemove', handleGlobalMouseMove); window.addEventListener('mouseup', handleGlobalMouseUp); } return () => { window.removeEventListener('mousemove', handleGlobalMouseMove); window.removeEventListener('mouseup', handleGlobalMouseUp); }; }, [draggingImageId, resizingImageId, dragStart, resizeStart, snapToGrid, canvasWidth, canvasMinHeight]); useEffect(() => { const handleGlobalPaste = (e) => { if (isExporting || activeTab !== 'canvas') return; const items = e.clipboardData?.items; if (!items) return; const filesToPaste = []; for (let i = 0; i < items.length; i++) { if (items[i].type.startsWith('image/')) { const file = items[i].getAsFile(); if (file) filesToPaste.push(file); } } if (filesToPaste.length > 0) { if (document.activeElement && document.activeElement.tagName !== 'TEXTAREA') { e.preventDefault(); } handleAddImages(filesToPaste, 40, 220); } }; window.addEventListener('paste', handleGlobalPaste); return () => window.removeEventListener('paste', handleGlobalPaste); }, [isExporting, activeTab, canvasWidth, canvasMinHeight]); const addSection = () => setItems([...items, { id: Date.now(), type: 'section', title: 'НОВЫЙ РАЗДЕЛ' }]); const addItem = () => setItems([...items, { id: Date.now(), type: 'item', title: '', price: 0, quantity: 1, finalPrice: 0, link: '' }]); const deleteItem = (id) => setItems(items.filter(i => i.id !== id)); const duplicateItem = (id) => { const idx = items.findIndex(i => i.id === id); if (idx === -1) return; const cp = { ...items[idx], id: Date.now() }; const ni = [...items]; ni.splice(idx + 1, 0, cp); setItems(ni); }; const moveItem = (idx, dir) => { const ni = [...items]; if (dir === -1 && idx > 0) { [ni[idx], ni[idx - 1]] = [ni[idx - 1], ni[idx]]; } else if (dir === 1 && idx < ni.length - 1) { [ni[idx], ni[idx + 1]] = [ni[idx + 1], ni[idx]]; } setItems(ni); }; const handleItemChange = (id, field, value) => { setItems(prev => prev.map(i => { if (i.id === id) { const updated = { ...i, [field]: value }; if (i.type === 'item') { const p = parseFloat(updated.price) || 0; const q = parseFloat(updated.quantity) || 0; updated.finalPrice = window.calcFinal ? window.calcFinal(p, q, false, 'fixed', 0) : (p * q); } return updated; } return i; })); }; const formatPriceSafe = (p) => new Intl.NumberFormat('ru-RU').format(Math.round(p || 0)); const totals = useMemo(() => { const baseTotal = items.reduce((acc, i) => i.type === 'item' ? acc + ((parseFloat(i.price)||0) * (parseFloat(i.quantity)||0)) : acc, 0); return { t: baseTotal }; }, [items]); const handleLocalSave = () => { const payloadData = { templateType: 'furniture', clientName, clientDate, sketches, orientation, items, showBaseRequisites, showFullRequisites, requisites }; const data = JSON.stringify(payloadData, null, 2); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `Мебель_${clientName || 'Новый'}.json`; a.click(); URL.revokeObjectURL(url); setHasUnsavedChanges(false); }; const handleLocalLoad = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (ev) => { try { const data = JSON.parse(ev.target.result); if (data.clientName !== undefined) setClientName(data.clientName); if (data.clientDate !== undefined) setClientDate(data.clientDate); if (data.sketches !== undefined) setSketches(data.sketches); if (data.orientation !== undefined) setOrientation(data.orientation); if (data.items !== undefined) setItems(data.items); if (data.showBaseRequisites !== undefined) setShowBaseRequisites(data.showBaseRequisites); if (data.showFullRequisites !== undefined) setShowFullRequisites(data.showFullRequisites); if (data.showBaseRequisites === undefined && data.showFullRequisites === undefined && data.showRequisites !== undefined) { const hasData = data.requisites && (data.requisites.seller.inn || data.requisites.buyer.company); if (hasData) setShowFullRequisites(data.showRequisites); else setShowBaseRequisites(data.showRequisites); } if (data.requisites !== undefined) setRequisites(data.requisites); showToast("Проект загружен!"); setHasUnsavedChanges(false); } catch (err) { showToast("Ошибка загрузки", 'error'); } }; reader.readAsText(file); e.target.value = null; }; const handleCloudSave = async () => { if (!user || !user.id) { showToast("Войдите в систему", 'error'); return; } setSaving(true); const payloadData = { templateType: 'furniture', clientName, clientDate, sketches, orientation, items, showBaseRequisites, showFullRequisites, requisites }; const pName = clientName ? `Мебель: ${clientName}` : 'Новое Мебельное КП'; let projectIdToSave = currentProjectId; try { const checkRes = await fetch(`/api/projects/${user.id}?t=${Date.now()}`); const checkData = await checkRes.json(); if (Array.isArray(checkData)) { const existingProject = checkData.find(p => p.name === pName); if (existingProject) { if (!projectIdToSave || projectIdToSave !== existingProject.id) { const confirmOverwrite = confirm(`Файл "${pName}" уже существует. Перезаписать его?`); if (confirmOverwrite) { projectIdToSave = existingProject.id; } else { setSaving(false); return; } } } } } catch (e) { } let sketchesToSave = sketches; let hasCompressed = false; let projectData = { user_id: user.id, id: projectIdToSave || 0, name: pName, data: JSON.stringify({...payloadData, sketches: sketchesToSave}) }; let payloadSize = new Blob([JSON.stringify(projectData)]).size; if (payloadSize > 900 * 1024) { sketchesToSave = await Promise.all(sketchesToSave.map(async (sk) => { if (sk.type === 'image' && sk.url && sk.url.startsWith('data:image/')) { try { const compressed = await window.compressImage(sk.url, 800, 0.7); if (compressed !== sk.url) hasCompressed = true; return { ...sk, url: compressed }; } catch (e) { return sk; } } return sk; })); projectData.data = JSON.stringify({...payloadData, sketches: sketchesToSave}); payloadSize = new Blob([JSON.stringify(projectData)]).size; } if (hasCompressed) setSketches(sketchesToSave); if (payloadSize > 1024 * 1024) { const mbSize = (payloadSize / (1024 * 1024)).toFixed(2); showToast(`Файл слишком большой (${mbSize} МБ). Сервер принимает до 1 МБ. Уменьшите количество или размер фото.`, 'error'); setSaving(false); return; } try { const res = await fetch('/api/projects', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(projectData) }); const data = await res.json(); if (data.status === 'created' || data.status === 'updated') { if (data.id) setCurrentProjectId(data.id); showToast("✅ Проект успешно сохранен в облако!"); setHasUnsavedChanges(false); } else { showToast("Ошибка сохранения: " + data.message, 'error'); } } catch (e) { showToast("Ошибка соединения", 'error'); } finally { setSaving(false); } }; const handleSendToTelegram = async () => { if (!user) return; if (!exportCanvas && !exportTable) { showToast("Выберите хотя бы один раздел для экспорта!", "error"); return; } if (window.loadPdfLibrary && !window.html2pdf) { try { await window.loadPdfLibrary(); } catch (e) {} } if (!window.html2pdf) { showToast("Ошибка: Библиотека PDF заблокирована браузером. Отключите блокировщик рекламы или обновите страницу.", "error"); return; } showToast("Генерация и отправка в Telegram (~30 сек). Можно продолжать работу.", "success"); setSendingTg(true); setIsPdf(true); setTimeout(async () => { const el = document.getElementById('print-area'); try { const pdfOptions = { margin: 0, image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, useCORS: true, scrollY: 0, backgroundColor: '#ffffff' }, jsPDF: { format: 'a4', orientation: orientation, unit: 'mm' } }; const blob = await window.html2pdf().set(pdfOptions).from(el).output('blob'); if (blob.size === 0) throw new Error("Generated PDF is empty"); setIsPdf(false); const formData = new FormData(); formData.append('file', blob, `${clientName ? 'Мебель_'+clientName : 'Мебельное_КП'}.pdf`); formData.append('user_id', user.id); const res = await fetch('/api/send-pdf', { method: 'POST', body: formData }); let errData = null; if (!res.ok) { try { errData = await res.json(); } catch (parseError) { throw new Error("Ошибка сервера (возможно файл слишком большой)"); } } if (res.ok) { showToast("Файл успешно доставлен в Telegram!", "success"); } else { if (res.status === 400 && errData?.detail && errData.detail.includes("Telegram ID missing")) { showToast("Бот не знает ваш ID. Нажмите /start в боте.", 'error'); } else { showToast("Ошибка отправки: " + (errData?.detail || `Код ${res.status}`), 'error'); } } } catch (e) { console.error("CRITICAL SEND ERROR:", e); showToast(e.message || "Ошибка", 'error'); setIsPdf(false); } finally { setSendingTg(false); } }, 200); }; const handlePdf = async () => { if (!exportCanvas && !exportTable) { showToast("Выберите хотя бы один раздел для экспорта (Активно)", "error"); return; } if (window.loadPdfLibrary && !window.html2pdf) { try { await window.loadPdfLibrary(); } catch (e) {} } if (!window.html2pdf) { showToast("Ошибка: Библиотека PDF заблокирована браузером. Отключите блокировщик рекламы или обновите страницу.", "error"); return; } setIsPdf(true); await new Promise(r => setTimeout(r, 200)); const el = document.getElementById('print-area'); try { await window.html2pdf().set({ margin: 0, filename: `${clientName ? 'Мебель_'+clientName : 'Мебельное_КП'}.pdf`, image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, useCORS: true, scrollY: 0, backgroundColor: '#ffffff' }, jsPDF: { format: 'a4', orientation: orientation, unit: 'mm' }, pagebreak: { mode: ['css', 'legacy'] } }).from(el).save(); showToast("✅ PDF успешно скачан!"); } catch (e) { console.error(e); showToast("Ошибка при генерации PDF", "error"); } finally { setIsPdf(false); } }; const canvasGridStyle = (showGrid && !isExporting) ? { backgroundImage: `linear-gradient(to right, #e5e7eb 1px, transparent 1px), linear-gradient(to bottom, #e5e7eb 1px, transparent 1px)`, backgroundSize: `${GRID_SIZE}px ${GRID_SIZE}px` } : {}; return (
{toast.show && !isExporting && (
{toast.type === 'success' && } {toast.type === 'error' && } {toast.message}
)} {!isExporting && (

Мебельное КП

{isManagerOrHigher && ( <> )} {isSpeedOrVip && ( )}
)}
{!isExporting && (
setClientName(e.target.value)} className="w-full border border-gray-300 rounded-lg shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 bg-gray-50 p-2 outline-none font-bold text-gray-800" placeholder="МЕБ-02/03-..." />
setClientDate(e.target.value)} className="w-full border border-gray-300 rounded-lg shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 bg-gray-50 p-2 outline-none font-bold text-gray-800" />
)} {/* --- ВЫНЕСЕННЫЕ КНОПКИ ПОКАЗА РЕКВИЗИТОВ --- */} {!isPdf && (
)} {/* --- ФОРМА ВВОДА ПОЛНЫХ РЕКВИЗИТОВ --- */} {showFullRequisites && !isPdf && (

Информация о продавце

setReqField('seller', 'name', e.target.value)} placeholder="Название компании или ИП" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" />
setReqField('seller', 'inn', e.target.value)} placeholder="ИНН" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" /> setReqField('seller', 'kpp', e.target.value)} placeholder="КПП" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" /> setReqField('seller', 'ogrn', e.target.value)} placeholder="ОГРН" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" /> setReqField('seller', 'account', e.target.value)} placeholder="№ расчетного счета" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors md:col-span-1" /> setReqField('seller', 'bank', e.target.value)} placeholder="Наименование банка" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors md:col-span-2" /> setReqField('seller', 'bik', e.target.value)} placeholder="БИК Банка" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors md:col-span-1" /> setReqField('seller', 'corr', e.target.value)} placeholder="№ корреспондентского счета" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors md:col-span-2" /> setReqField('seller', 'phone', e.target.value)} placeholder="Телефон" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" /> setReqField('seller', 'site', e.target.value)} placeholder="Сайт" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" /> setReqField('seller', 'email', e.target.value)} placeholder="Email" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" /> setReqField('seller', 'directorTitle', e.target.value)} placeholder="Должность руководителя" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors md:col-span-1" /> setReqField('seller', 'directorName', e.target.value)} placeholder="ФИО руководителя" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors md:col-span-2" /> setReqField('seller', 'managerName', e.target.value)} placeholder="ФИО исполнителя" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors md:col-span-1" /> setReqField('seller', 'managerPhone', e.target.value)} placeholder="Телефон исполнителя" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors md:col-span-2" />

Информация о покупателе

setReqField('buyer', 'targetTitle', e.target.value)} placeholder="Должность сотрудника для кого адресовано КП" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" />
setReqField('buyer', 'company', e.target.value)} placeholder="Название компании" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" /> setReqField('buyer', 'targetName', e.target.value)} placeholder="ФИО сотрудника" className="w-full border border-gray-300 focus:border-blue-500 p-2.5 rounded-md outline-none transition-colors" />
)} {!isExporting && (
)}
{!isExporting && (
Формат:
{FileText && ( )}
)}
{/* --- ФИРМЕННЫЙ СТИЛЬ И РЕКВИЗИТЫ В PDF --- */} {(showBaseRequisites || showFullRequisites) && (
{/* Продавец */}
{user?.company_logo && Логотип} {(showFullRequisites && (requisites.seller.inn || requisites.seller.account || requisites.seller.phone || requisites.seller.name)) ? ( <>
{requisites.seller.name || (user?.company_requisites ? 'Продавец' : '')}
{requisites.seller.inn &&
ИНН: {requisites.seller.inn}{requisites.seller.kpp ? ` / КПП: ${requisites.seller.kpp}` : ''}
} {requisites.seller.ogrn &&
ОГРН: {requisites.seller.ogrn}
} {requisites.seller.bank &&
Банк: {requisites.seller.bank}
} {requisites.seller.account &&
Р/С: {requisites.seller.account}
} {requisites.seller.corr &&
К/С: {requisites.seller.corr}{requisites.seller.bik ? ` / БИК: ${requisites.seller.bik}` : ''}
} {(requisites.seller.phone || requisites.seller.email || requisites.seller.site) && (
{requisites.seller.phone && Тел: {requisites.seller.phone}} {requisites.seller.email && Email: {requisites.seller.email}} {requisites.seller.site && Web: {requisites.seller.site}}
)} {(requisites.seller.directorTitle || requisites.seller.directorName || requisites.seller.managerName) && (
{requisites.seller.directorName &&
{requisites.seller.directorTitle || 'Руководитель'}: {requisites.seller.directorName}
} {requisites.seller.managerName &&
Менеджер: {requisites.seller.managerName} {requisites.seller.managerPhone}
}
)}
) : ( <> {showFullRequisites && requisites.seller.name &&
{requisites.seller.name}
}
{user?.company_requisites}
)}
{/* Покупатель */} {showFullRequisites && (
Информация о покупателе
{requisites.buyer.company || clientName || 'Организация не указана'}
{requisites.buyer.targetTitle &&
{requisites.buyer.targetTitle}
} {requisites.buyer.targetName &&
{requisites.buyer.targetName}
}
)}
)}

Коммерческое Предложение

{clientName || ''}
{clientDate ? `Дата: ${getFormattedDateForPrint()}` : ''}
{sketches.length === 0 && !isExporting && (
{Plus && }

Холст пуст

Перетащите сюда картинки или нажмите кнопку выше (или Ctrl+V)

)} {sketches.map((sketch) => { const isText = sketch.type === 'text'; const currentScale = isText ? 1 : sketch.scale; const currentWidth = isText ? (sketch.width || 250) : undefined; const currentFontSize = isText ? (sketch.fontSize || 16) : undefined; return (
{isText ? (
{isExporting ? (
{sketch.text}
) : (