2025-08-18 14:55:51 +00:00
|
|
|
|
import React, { useState, useEffect, useMemo, useCallback } from 'react'
|
2025-08-15 08:07:51 +00:00
|
|
|
|
import { useParams, useNavigate } from 'react-router-dom'
|
|
|
|
|
|
import { Button } from '../ui/Button'
|
2025-08-16 19:47:24 +00:00
|
|
|
|
import {
|
|
|
|
|
|
FaArrowLeft,
|
|
|
|
|
|
FaCalendarAlt,
|
|
|
|
|
|
FaFileAlt,
|
|
|
|
|
|
FaDownload,
|
|
|
|
|
|
FaSearchPlus,
|
|
|
|
|
|
FaSearchMinus,
|
|
|
|
|
|
} from 'react-icons/fa'
|
2025-08-15 11:52:30 +00:00
|
|
|
|
import { ReportGeneratedDto, ReportTemplateDto } from '@/proxy/reports/models'
|
2025-08-15 08:07:51 +00:00
|
|
|
|
import { useReports } from '@/utils/hooks/useReports'
|
2025-08-15 20:27:19 +00:00
|
|
|
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
2025-08-17 12:51:31 +00:00
|
|
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
2025-08-15 08:07:51 +00:00
|
|
|
|
|
|
|
|
|
|
export const ReportViewer: React.FC = () => {
|
2025-08-15 09:19:20 +00:00
|
|
|
|
const { id } = useParams<{ id: string }>()
|
2025-08-15 08:07:51 +00:00
|
|
|
|
const navigate = useNavigate()
|
|
|
|
|
|
const [zoomLevel, setZoomLevel] = useState(100)
|
2025-08-15 11:52:30 +00:00
|
|
|
|
const [report, setReport] = useState<ReportGeneratedDto | null>(null)
|
2025-08-15 08:07:51 +00:00
|
|
|
|
const [template, setTemplate] = useState<ReportTemplateDto | null>(null)
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null)
|
2025-08-17 12:51:31 +00:00
|
|
|
|
const { translate } = useLocalization()
|
2025-08-15 08:07:51 +00:00
|
|
|
|
const { getReportById, getTemplateById } = useReports()
|
|
|
|
|
|
|
2025-08-18 14:55:51 +00:00
|
|
|
|
// İçeriği sayfalara bölen fonksiyon
|
|
|
|
|
|
const splitContentIntoPages = (content: string) => {
|
|
|
|
|
|
// Basit olarak içeriği paragraf ve tablo bazında bölelim
|
|
|
|
|
|
const tempDiv = document.createElement('div')
|
|
|
|
|
|
tempDiv.innerHTML = content
|
|
|
|
|
|
|
|
|
|
|
|
const elements = Array.from(tempDiv.children)
|
|
|
|
|
|
const pages: string[] = []
|
|
|
|
|
|
let currentPage = ''
|
|
|
|
|
|
let currentPageHeight = 0
|
|
|
|
|
|
const maxPageHeight = 257 // 297mm - 40mm padding (top+bottom)
|
|
|
|
|
|
|
|
|
|
|
|
elements.forEach((element) => {
|
|
|
|
|
|
const elementHtml = element.outerHTML
|
|
|
|
|
|
// Basit yükseklik tahmini (gerçek uygulamada daha karmaşık olabilir)
|
|
|
|
|
|
let estimatedHeight = 20 // Default height
|
|
|
|
|
|
|
|
|
|
|
|
if (element.tagName === 'TABLE') {
|
|
|
|
|
|
const rows = element.querySelectorAll('tr')
|
|
|
|
|
|
estimatedHeight = rows.length * 25 // Her satır için 25mm
|
|
|
|
|
|
} else if (element.tagName.startsWith('H')) {
|
|
|
|
|
|
estimatedHeight = 15
|
|
|
|
|
|
} else if (element.tagName === 'P') {
|
|
|
|
|
|
estimatedHeight = 10
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (currentPageHeight + estimatedHeight > maxPageHeight && currentPage) {
|
|
|
|
|
|
pages.push(currentPage)
|
|
|
|
|
|
currentPage = elementHtml
|
|
|
|
|
|
currentPageHeight = estimatedHeight
|
|
|
|
|
|
} else {
|
|
|
|
|
|
currentPage += elementHtml
|
|
|
|
|
|
currentPageHeight += estimatedHeight
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (currentPage) {
|
|
|
|
|
|
pages.push(currentPage)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return pages.length > 0 ? pages : [content]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const preloadPdfLibs = useCallback(() => {
|
|
|
|
|
|
// Hover’da ısıtma için (opsiyonel)
|
|
|
|
|
|
import('jspdf')
|
|
|
|
|
|
import('html2canvas')
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
// YENİ: memoize edilmiş sayfalar
|
|
|
|
|
|
const memoizedPages = useMemo(() => {
|
|
|
|
|
|
return report ? splitContentIntoPages(report.generatedContent) : []
|
|
|
|
|
|
}, [report])
|
|
|
|
|
|
|
2025-08-15 08:07:51 +00:00
|
|
|
|
// Asenkron veri yükleme
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const loadReportData = async () => {
|
2025-08-15 09:19:20 +00:00
|
|
|
|
if (!id) {
|
2025-08-15 08:07:51 +00:00
|
|
|
|
setError("Rapor ID'si bulunamadı")
|
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
setIsLoading(true)
|
|
|
|
|
|
setError(null)
|
|
|
|
|
|
|
|
|
|
|
|
// Raporu yükle
|
2025-08-15 09:19:20 +00:00
|
|
|
|
const reportData = await getReportById(id)
|
2025-08-15 08:07:51 +00:00
|
|
|
|
|
|
|
|
|
|
if (!reportData) {
|
|
|
|
|
|
setError('Rapor bulunamadı')
|
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setReport(reportData)
|
|
|
|
|
|
|
|
|
|
|
|
// Şablonu yükle
|
|
|
|
|
|
if (reportData.templateId) {
|
|
|
|
|
|
const templateData = await getTemplateById(reportData.templateId)
|
|
|
|
|
|
setTemplate(templateData || null)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Error loading report data:', err)
|
|
|
|
|
|
setError('Rapor yüklenirken bir hata oluştu')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadReportData()
|
2025-08-15 09:19:20 +00:00
|
|
|
|
}, [id, getReportById, getTemplateById])
|
2025-08-15 08:07:51 +00:00
|
|
|
|
|
|
|
|
|
|
// Zoom fonksiyonları
|
|
|
|
|
|
const handleZoomIn = () => {
|
|
|
|
|
|
setZoomLevel((prev) => Math.min(prev + 25, 200)) // Maksimum %200
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleZoomOut = () => {
|
|
|
|
|
|
setZoomLevel((prev) => Math.max(prev - 25, 50)) // Minimum %50
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Loading durumu
|
|
|
|
|
|
if (isLoading) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
2025-08-17 12:51:31 +00:00
|
|
|
|
<h1 className="text-xl font-semibold text-gray-900 mb-2">
|
|
|
|
|
|
{translate('::App.Reports.ReportViewer.LoadingTitle')}
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
<p className="text-gray-600">{translate('::App.Reports.ReportViewer.LoadingSubtitle')}</p>
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Error durumu
|
2025-08-15 09:19:20 +00:00
|
|
|
|
if (error || !id) {
|
2025-08-15 08:07:51 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
|
|
|
|
<div className="text-center">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
<h1 className="text-2xl font-bold text-gray-900 mb-4">
|
|
|
|
|
|
{error || translate('::App.Reports.ReportViewer.ErrorNotFound')}
|
|
|
|
|
|
</h1>
|
2025-08-15 20:27:19 +00:00
|
|
|
|
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaArrowLeft className="h-4 w-4 mr-2" />
|
2025-08-15 08:07:51 +00:00
|
|
|
|
Ana Sayfaya Dön
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-15 09:19:20 +00:00
|
|
|
|
if (!id) {
|
2025-08-15 08:07:51 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
|
|
|
|
<div className="text-center">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
<h1 className="text-2xl font-bold text-gray-900 mb-4">
|
|
|
|
|
|
{translate('::App.Reports.ReportViewer.ErrorNotFound')}
|
|
|
|
|
|
</h1>
|
2025-08-15 20:27:19 +00:00
|
|
|
|
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaArrowLeft className="h-4 w-4 mr-2" />
|
2025-08-15 08:07:51 +00:00
|
|
|
|
Ana Sayfaya Dön
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Report yüklenmemiş ise
|
|
|
|
|
|
if (!report) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
|
|
|
|
<div className="text-center">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
<h1 className="text-2xl font-bold text-gray-900 mb-4">
|
|
|
|
|
|
{translate('::App.Reports.ReportViewer.ErrorNotFound')}
|
|
|
|
|
|
</h1>
|
2025-08-15 08:07:51 +00:00
|
|
|
|
<p className="text-gray-600 mb-6">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.ReportViewer.ErrorNotFoundDescription')}
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</p>
|
2025-08-15 20:27:19 +00:00
|
|
|
|
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaArrowLeft className="h-4 w-4 mr-2" />
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.ReportViewer.BackToDashboard')}
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handlePrint = () => {
|
|
|
|
|
|
// Yazdırma sırasında zoom seviyesini geçici olarak %100'e ayarla
|
|
|
|
|
|
const currentZoom = zoomLevel
|
|
|
|
|
|
setZoomLevel(100)
|
|
|
|
|
|
|
|
|
|
|
|
// DOM'un güncellenmesi için kısa bir gecikme
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
window.print()
|
|
|
|
|
|
|
|
|
|
|
|
// Yazdırma işlemi tamamlandıktan sonra orijinal zoom seviyesine geri dön
|
|
|
|
|
|
// Print dialog kapandıktan sonra zoom'u eski haline getir
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
setZoomLevel(currentZoom)
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-18 14:55:51 +00:00
|
|
|
|
// DEĞİŞTİR: handleDownloadPdf
|
2025-08-15 08:07:51 +00:00
|
|
|
|
const handleDownloadPdf = async () => {
|
2025-08-18 14:55:51 +00:00
|
|
|
|
// Ağır kütüphaneleri ihtiyaç anında indir
|
|
|
|
|
|
const [jspdfMod, h2cMod] = await Promise.all([import('jspdf'), import('html2canvas')])
|
|
|
|
|
|
|
|
|
|
|
|
// jsPDF bazı dağıtımlarda default, bazılarında { jsPDF } olarak gelir
|
|
|
|
|
|
const jsPDFCtor = (jspdfMod as any).default ?? (jspdfMod as any).jsPDF
|
|
|
|
|
|
const html2canvas = (h2cMod as any).default ?? (h2cMod as any)
|
|
|
|
|
|
|
|
|
|
|
|
const pages = memoizedPages // aşağıdaki 2. adımda tanımlayacağız
|
2025-08-15 08:07:51 +00:00
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-18 14:55:51 +00:00
|
|
|
|
const pdf = new jsPDFCtor({ orientation: 'portrait', unit: 'mm', format: 'a4' })
|
2025-08-15 08:07:51 +00:00
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < pages.length; i++) {
|
|
|
|
|
|
const elementId = i === 0 ? 'report-content' : `report-content-page-${i + 1}`
|
|
|
|
|
|
const element = document.getElementById(elementId)
|
|
|
|
|
|
if (!element) continue
|
|
|
|
|
|
|
2025-08-18 14:55:51 +00:00
|
|
|
|
// Yakalama öncesi zoom’u etkisizleştir (transform varsa kalite düşmesin)
|
|
|
|
|
|
const container = element.parentElement as HTMLElement | null
|
|
|
|
|
|
const prevTransform = container?.style.transform
|
|
|
|
|
|
if (container) container.style.transform = 'none'
|
|
|
|
|
|
|
2025-08-15 08:07:51 +00:00
|
|
|
|
const canvas = await html2canvas(element, {
|
|
|
|
|
|
scale: 2,
|
|
|
|
|
|
useCORS: true,
|
|
|
|
|
|
allowTaint: true,
|
|
|
|
|
|
backgroundColor: '#ffffff',
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-08-18 14:55:51 +00:00
|
|
|
|
if (container) container.style.transform = prevTransform ?? ''
|
2025-08-15 08:07:51 +00:00
|
|
|
|
|
2025-08-18 14:55:51 +00:00
|
|
|
|
const imgData = canvas.toDataURL('image/png')
|
|
|
|
|
|
const imgWidth = 210
|
|
|
|
|
|
const imgHeight = 297
|
|
|
|
|
|
if (i > 0) pdf.addPage()
|
2025-08-15 08:07:51 +00:00
|
|
|
|
pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pdf.save(
|
2025-08-18 14:55:51 +00:00
|
|
|
|
`${report!.templateName}_${new Date(report!.generatedAt).toLocaleDateString('tr-TR')}.pdf`,
|
2025-08-15 08:07:51 +00:00
|
|
|
|
)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('PDF oluşturma hatası:', error)
|
|
|
|
|
|
alert('PDF oluşturulurken bir hata oluştu.')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gray-50">
|
|
|
|
|
|
{/* Header - Print edilmeyecek */}
|
|
|
|
|
|
<div className="print:hidden bg-white shadow-sm border-b border-gray-200 sticky top-0 z-10">
|
2025-08-15 20:27:19 +00:00
|
|
|
|
<div className="w-full px-4">
|
2025-08-15 08:07:51 +00:00
|
|
|
|
<div className="flex items-center justify-between h-16">
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<div className="flex items-center space-x-4 flex-1">
|
|
|
|
|
|
<div className="flex-1">
|
2025-08-15 08:07:51 +00:00
|
|
|
|
<h1 className="text-lg font-semibold text-gray-900">{report.templateName}</h1>
|
|
|
|
|
|
<div className="flex items-center space-x-4 text-sm text-gray-500">
|
|
|
|
|
|
<span className="flex items-center">
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaCalendarAlt className="h-4 w-4 mr-1" />
|
2025-08-15 08:07:51 +00:00
|
|
|
|
{new Date(report.generatedAt).toLocaleDateString('tr-TR', {
|
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
|
month: 'long',
|
|
|
|
|
|
day: 'numeric',
|
|
|
|
|
|
hour: '2-digit',
|
|
|
|
|
|
minute: '2-digit',
|
|
|
|
|
|
})}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{template && (
|
|
|
|
|
|
<span className="flex items-center">
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaFileAlt className="h-4 w-4 mr-1" />
|
2025-08-15 19:39:11 +00:00
|
|
|
|
{template.categoryName}
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<div className="flex items-center space-x-2 flex-shrink-0">
|
2025-08-15 08:07:51 +00:00
|
|
|
|
<Button variant="solid" onClick={handleZoomOut} disabled={zoomLevel <= 50} size="sm">
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaSearchMinus className="h-4 w-4" />
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</Button>
|
|
|
|
|
|
<span className="text-sm text-gray-600 px-2 min-w-[4rem] text-center">
|
|
|
|
|
|
{zoomLevel}%
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<Button variant="solid" onClick={handleZoomIn} disabled={zoomLevel >= 200} size="sm">
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaSearchPlus className="h-4 w-4" />
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</Button>
|
|
|
|
|
|
<div className="w-px h-6 bg-gray-300 mx-2"></div>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<Button
|
2025-08-18 14:55:51 +00:00
|
|
|
|
onMouseEnter={preloadPdfLibs} // ← opsiyonel prefetch
|
2025-08-15 19:39:11 +00:00
|
|
|
|
onClick={handleDownloadPdf}
|
2025-08-16 12:22:36 +00:00
|
|
|
|
className="bg-white-600 hover:bg-white-700 font-medium px-2 sm:px-3 py-1.5 rounded text-xs flex items-center gap-1"
|
2025-08-15 19:39:11 +00:00
|
|
|
|
>
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaDownload className="h-4 w-4 mr-2" />
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.ReportViewer.DownloadPDF')}
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</Button>
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<Button variant="solid" onClick={handlePrint}>
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.ReportViewer.Print')}
|
2025-08-16 19:47:24 +00:00
|
|
|
|
</Button>
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Rapor İçeriği */}
|
|
|
|
|
|
<div className="flex justify-center bg-gray-200 py-8 print:bg-white print:py-0">
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="space-y-8 print:space-y-0 transition-transform duration-300 ease-in-out"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
transform: `scale(${zoomLevel / 100})`,
|
|
|
|
|
|
transformOrigin: 'top center',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2025-08-18 14:55:51 +00:00
|
|
|
|
{memoizedPages.map((pageContent, index) => (
|
2025-08-15 08:07:51 +00:00
|
|
|
|
<div
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
id={index === 0 ? 'report-content' : `report-content-page-${index + 1}`}
|
|
|
|
|
|
className="bg-white shadow-lg print:shadow-none page-container"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: '210mm',
|
|
|
|
|
|
minHeight: '297mm',
|
|
|
|
|
|
padding: '0',
|
|
|
|
|
|
boxSizing: 'border-box',
|
|
|
|
|
|
position: 'relative',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{index === 0 && (
|
|
|
|
|
|
<style>
|
|
|
|
|
|
{`
|
|
|
|
|
|
/* Sayfa kırılması için CSS */
|
|
|
|
|
|
.page-container {
|
|
|
|
|
|
page-break-after: always;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-container:last-child {
|
|
|
|
|
|
page-break-after: auto;
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Header ve Footer stilleri */
|
|
|
|
|
|
.page-header {
|
|
|
|
|
|
height: 15mm;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: right;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
padding: 0 20mm;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-footer {
|
|
|
|
|
|
height: 15mm;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
padding: 0 20mm;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
margin-top: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-content-area {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 0 20mm;
|
|
|
|
|
|
min-height: 267mm; /* 297mm - 15mm header - 15mm footer */
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* İçerik için sayfa kırılması kuralları */
|
|
|
|
|
|
.page-container h1, .page-container h2, .page-container h3 {
|
|
|
|
|
|
page-break-after: avoid;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-container table {
|
|
|
|
|
|
page-break-inside: avoid;
|
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-container table td,
|
|
|
|
|
|
.page-container table th {
|
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
vertical-align: top;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Print specific styles */
|
|
|
|
|
|
@media print {
|
|
|
|
|
|
body {
|
|
|
|
|
|
margin: 0 !important;
|
|
|
|
|
|
padding: 0 !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-container {
|
|
|
|
|
|
width: 210mm !important;
|
|
|
|
|
|
min-height: 297mm !important;
|
|
|
|
|
|
margin: 0 !important;
|
|
|
|
|
|
padding: 0 !important;
|
|
|
|
|
|
box-shadow: none !important;
|
|
|
|
|
|
page-break-after: always !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-container:last-child {
|
|
|
|
|
|
page-break-after: auto !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-header {
|
|
|
|
|
|
height: 15mm !important;
|
|
|
|
|
|
color: #666 !important;
|
|
|
|
|
|
-webkit-print-color-adjust: exact !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-footer {
|
|
|
|
|
|
height: 15mm !important;
|
|
|
|
|
|
color: #666 !important;
|
|
|
|
|
|
-webkit-print-color-adjust: exact !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-content-area {
|
|
|
|
|
|
padding: 0 20mm !important;
|
|
|
|
|
|
min-height: 267mm !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.print\\:hidden {
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Table print styles */
|
|
|
|
|
|
.page-container table {
|
|
|
|
|
|
border-collapse: collapse !important;
|
|
|
|
|
|
page-break-inside: avoid !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-container table th,
|
|
|
|
|
|
.page-container table td {
|
|
|
|
|
|
border: 1px solid #000 !important;
|
|
|
|
|
|
page-break-inside: avoid !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-container table th {
|
|
|
|
|
|
background-color: #f0f0f0 !important;
|
|
|
|
|
|
-webkit-print-color-adjust: exact !important;
|
|
|
|
|
|
color-adjust: exact !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-container table tr:nth-child(even) {
|
|
|
|
|
|
background-color: #f5f5f5 !important;
|
|
|
|
|
|
-webkit-print-color-adjust: exact !important;
|
|
|
|
|
|
color-adjust: exact !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Sayfa Header - Tarih ve Saat */}
|
|
|
|
|
|
<div className="page-header">
|
|
|
|
|
|
{new Date().toLocaleDateString('tr-TR', {
|
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
|
month: 'long',
|
|
|
|
|
|
day: 'numeric',
|
|
|
|
|
|
hour: '2-digit',
|
|
|
|
|
|
minute: '2-digit',
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Sayfa İçeriği */}
|
|
|
|
|
|
<div className="page-content-area">
|
|
|
|
|
|
<div dangerouslySetInnerHTML={{ __html: pageContent }} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Sayfa Footer - Sayfa Numarası */}
|
|
|
|
|
|
<div className="page-footer">
|
2025-08-18 14:55:51 +00:00
|
|
|
|
{translate('::App.Reports.ReportViewer.Page')} {index + 1} / {memoizedPages.length}
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|