Performans çalışmaları ve DevExpres License
This commit is contained in:
parent
0d04a30767
commit
7c6d4857df
8 changed files with 659 additions and 293 deletions
1
ui/.gitignore
vendored
1
ui/.gitignore
vendored
|
|
@ -24,3 +24,4 @@ build
|
|||
*.sln
|
||||
*.sw?
|
||||
.vite-cache/
|
||||
src/devextreme-license.ts
|
||||
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
|||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "index.html",
|
||||
"revision": "0.p87ro290qlo"
|
||||
"revision": "0.20gg38gpeso"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
|
|
|
|||
670
ui/package-lock.json
generated
670
ui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -65,7 +65,7 @@
|
|||
"@types/babel__standalone": "^7.1.9",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^18.15.5",
|
||||
"@types/node": "^20.19.11",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/react-helmet": "^6.1.9",
|
||||
|
|
@ -95,8 +95,8 @@
|
|||
"prettier": "^3.1.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^5.4.11",
|
||||
"vite-plugin-pwa": "^0.21.1"
|
||||
"vite": "^7.1.2",
|
||||
"vite-plugin-pwa": "^1.0.2"
|
||||
},
|
||||
"volta": {
|
||||
"node": "22.12.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { Button } from '../ui/Button'
|
||||
import {
|
||||
|
|
@ -9,8 +9,6 @@ import {
|
|||
FaSearchPlus,
|
||||
FaSearchMinus,
|
||||
} from 'react-icons/fa'
|
||||
import html2canvas from 'html2canvas'
|
||||
import jsPDF from 'jspdf'
|
||||
import { ReportGeneratedDto, ReportTemplateDto } from '@/proxy/reports/models'
|
||||
import { useReports } from '@/utils/hooks/useReports'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
|
|
@ -25,9 +23,62 @@ export const ReportViewer: React.FC = () => {
|
|||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const { getReportById, getTemplateById } = useReports()
|
||||
|
||||
// İç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])
|
||||
|
||||
// Asenkron veri yükleme
|
||||
useEffect(() => {
|
||||
const loadReportData = async () => {
|
||||
|
|
@ -77,49 +128,6 @@ export const ReportViewer: React.FC = () => {
|
|||
setZoomLevel((prev) => Math.max(prev - 25, 50)) // Minimum %50
|
||||
}
|
||||
|
||||
// İç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]
|
||||
}
|
||||
|
||||
// Loading durumu
|
||||
if (isLoading) {
|
||||
return (
|
||||
|
|
@ -205,22 +213,30 @@ export const ReportViewer: React.FC = () => {
|
|||
}, 100)
|
||||
}
|
||||
|
||||
// DEĞİŞTİR: handleDownloadPdf
|
||||
const handleDownloadPdf = async () => {
|
||||
const pages = splitContentIntoPages(report.generatedContent)
|
||||
// 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
|
||||
|
||||
try {
|
||||
const pdf = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'mm',
|
||||
format: 'a4',
|
||||
})
|
||||
const pdf = new jsPDFCtor({ orientation: 'portrait', unit: 'mm', format: 'a4' })
|
||||
|
||||
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
|
||||
|
||||
// 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'
|
||||
|
||||
const canvas = await html2canvas(element, {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
|
|
@ -228,19 +244,17 @@ export const ReportViewer: React.FC = () => {
|
|||
backgroundColor: '#ffffff',
|
||||
})
|
||||
|
||||
if (container) container.style.transform = prevTransform ?? ''
|
||||
|
||||
const imgData = canvas.toDataURL('image/png')
|
||||
const imgWidth = 210 // A4 width in mm
|
||||
const imgHeight = 297 // A4 height in mm
|
||||
|
||||
if (i > 0) {
|
||||
pdf.addPage()
|
||||
}
|
||||
|
||||
const imgWidth = 210
|
||||
const imgHeight = 297
|
||||
if (i > 0) pdf.addPage()
|
||||
pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight)
|
||||
}
|
||||
|
||||
pdf.save(
|
||||
`${report.templateName}_${new Date(report.generatedAt).toLocaleDateString('tr-TR')}.pdf`,
|
||||
`${report!.templateName}_${new Date(report!.generatedAt).toLocaleDateString('tr-TR')}.pdf`,
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('PDF oluşturma hatası:', error)
|
||||
|
|
@ -290,6 +304,7 @@ export const ReportViewer: React.FC = () => {
|
|||
</Button>
|
||||
<div className="w-px h-6 bg-gray-300 mx-2"></div>
|
||||
<Button
|
||||
onMouseEnter={preloadPdfLibs} // ← opsiyonel prefetch
|
||||
onClick={handleDownloadPdf}
|
||||
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"
|
||||
>
|
||||
|
|
@ -313,7 +328,7 @@ export const ReportViewer: React.FC = () => {
|
|||
transformOrigin: 'top center',
|
||||
}}
|
||||
>
|
||||
{splitContentIntoPages(report.generatedContent).map((pageContent, index) => (
|
||||
{memoizedPages.map((pageContent, index) => (
|
||||
<div
|
||||
key={index}
|
||||
id={index === 0 ? 'report-content' : `report-content-page-${index + 1}`}
|
||||
|
|
@ -483,8 +498,7 @@ export const ReportViewer: React.FC = () => {
|
|||
|
||||
{/* Sayfa Footer - Sayfa Numarası */}
|
||||
<div className="page-footer">
|
||||
{translate('::App.Reports.ReportViewer.Page')} {index + 1} /{' '}
|
||||
{splitContentIntoPages(report.generatedContent).length}
|
||||
{translate('::App.Reports.ReportViewer.Page')} {index + 1} / {memoizedPages.length}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ import './index.css'
|
|||
import { UiEvalService } from './services/UiEvalService'
|
||||
import 'devextreme-react/text-area'
|
||||
import 'devextreme-react/html-editor'
|
||||
import config from 'devextreme/core/config'
|
||||
import { licenseKey } from './devextreme-license'
|
||||
|
||||
// Lisansı uygulama başlamadan önce kaydediyoruz
|
||||
config({ licenseKey })
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
|
||||
|
||||
|
|
|
|||
|
|
@ -63,11 +63,6 @@ import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
|
|||
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
|
||||
import { useFilters } from './useFilters'
|
||||
import { useToolbar } from './useToolbar'
|
||||
import { Workbook } from 'exceljs'
|
||||
import saveAs from 'file-saver'
|
||||
import { jsPDF } from 'jspdf'
|
||||
import { exportDataGrid as exportDataPdf } from 'devextreme/pdf_exporter'
|
||||
import { exportDataGrid as exportDataExcel } from 'devextreme/excel_exporter'
|
||||
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
|
||||
|
||||
interface GridProps {
|
||||
|
|
@ -94,6 +89,14 @@ const Grid = (props: GridProps) => {
|
|||
const [formData, setFormData] = useState<any>()
|
||||
const [mode, setMode] = useState<RowMode>('view')
|
||||
|
||||
const preloadExportLibs = () => {
|
||||
import('exceljs')
|
||||
import('file-saver')
|
||||
import('devextreme/excel_exporter')
|
||||
import('jspdf')
|
||||
import('devextreme/pdf_exporter')
|
||||
}
|
||||
|
||||
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
|
||||
gridDto,
|
||||
listFormCode,
|
||||
|
|
@ -431,45 +434,71 @@ const Grid = (props: GridProps) => {
|
|||
gridRef.current.instance.option('stateStoring', stateStoring)
|
||||
}, [columnData])
|
||||
|
||||
const onExporting = (e: DataGridTypes.ExportingEvent) => {
|
||||
if (e.format == 'xlsx') {
|
||||
const onExporting = async (e: DataGridTypes.ExportingEvent) => {
|
||||
// DevExtreme’in varsayılan export davranışını iptal ediyoruz; kendi akışımızı çalıştıracağız
|
||||
e.cancel = true
|
||||
|
||||
const grid = gridRef?.current?.instance
|
||||
if (!grid) return
|
||||
|
||||
try {
|
||||
if (e.format === 'xlsx' || e.format === 'csv') {
|
||||
// exceljs + file-saver + devextreme excel exporter => ihtiyaç anında yükle
|
||||
const [{ Workbook }, { saveAs }, { exportDataGrid: exportDataExcel }] = await Promise.all([
|
||||
import('exceljs'),
|
||||
import('file-saver'),
|
||||
import('devextreme/excel_exporter'),
|
||||
])
|
||||
|
||||
const workbook = new Workbook()
|
||||
const worksheet = workbook.addWorksheet(`${listFormCode}_sheet`)
|
||||
exportDataExcel({
|
||||
component: gridRef?.current?.instance,
|
||||
|
||||
await exportDataExcel({
|
||||
component: grid,
|
||||
worksheet,
|
||||
autoFilterEnabled: true,
|
||||
}).then(() => {
|
||||
workbook.xlsx.writeBuffer().then((buffer) => {
|
||||
})
|
||||
|
||||
if (e.format === 'xlsx') {
|
||||
const buffer = await workbook.xlsx.writeBuffer()
|
||||
saveAs(
|
||||
new Blob([buffer], { type: 'application/octet-stream' }),
|
||||
`${listFormCode}_export.xlsx`,
|
||||
)
|
||||
})
|
||||
})
|
||||
} else if (e.format == 'pdf') {
|
||||
const doc = new jsPDF()
|
||||
exportDataPdf({
|
||||
jsPDFDocument: doc,
|
||||
component: gridRef?.current?.instance,
|
||||
indent: 5,
|
||||
}).then(() => {
|
||||
doc.save(`${listFormCode}_export.pdf`)
|
||||
})
|
||||
} else if (e.format == 'csv') {
|
||||
const workbook = new Workbook()
|
||||
const worksheet = workbook.addWorksheet(`${listFormCode}_sheet`)
|
||||
exportDataExcel({
|
||||
component: gridRef?.current?.instance,
|
||||
worksheet: worksheet,
|
||||
}).then(function () {
|
||||
workbook.csv.writeBuffer().then(function (buffer) {
|
||||
} else {
|
||||
const buffer = await workbook.csv.writeBuffer()
|
||||
saveAs(
|
||||
new Blob([buffer], { type: 'application/octet-stream' }),
|
||||
`${listFormCode}_export.csv`,
|
||||
)
|
||||
}
|
||||
} else if (e.format === 'pdf') {
|
||||
// jspdf + devextreme pdf exporter => ihtiyaç anında yükle
|
||||
const [jspdfMod, { exportDataGrid: exportDataPdf }] = await Promise.all([
|
||||
import('jspdf'),
|
||||
import('devextreme/pdf_exporter'),
|
||||
])
|
||||
|
||||
// jsPDF bazı paketlemelerde default, bazılarında named export olarak gelir
|
||||
const JsPDFCtor = (jspdfMod as any).default ?? (jspdfMod as any).jsPDF
|
||||
const doc = new JsPDFCtor({})
|
||||
|
||||
await exportDataPdf({
|
||||
jsPDFDocument: doc,
|
||||
component: grid,
|
||||
indent: 5,
|
||||
})
|
||||
})
|
||||
|
||||
doc.save(`${listFormCode}_export.pdf`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Export error:', err)
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2500}>
|
||||
{translate('::App.Common.ExportError') ?? 'Dışa aktarma sırasında hata oluştu.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,29 @@ export default defineConfig(async ({ mode }) => {
|
|||
build: {
|
||||
outDir: 'dist',
|
||||
sourcemap: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules')) {
|
||||
if (id.match(/node_modules[\\/]react/)) return 'vendor-react'
|
||||
if (id.match(/node_modules[\\/]react-dom/)) return 'vendor-reactdom'
|
||||
if (id.match(/node_modules[\\/]devextreme/)) return 'vendor-devextreme'
|
||||
if (id.match(/node_modules[\\/]@devexpress/)) return 'vendor-devexpress'
|
||||
if (id.match(/node_modules[\\/]devextreme-react/)) return 'vendor-devextreme-react'
|
||||
if (id.match(/node_modules[\\/]axios/)) return 'vendor-axios'
|
||||
if (id.match(/node_modules[\\/]formik/)) return 'vendor-formik'
|
||||
if (id.match(/node_modules[\\/]jspdf/)) return 'vendor-jspdf'
|
||||
if (id.match(/node_modules[\\/]exceljs/)) return 'vendor-exceljs'
|
||||
if (id.match(/node_modules[\\/]html2canvas/)) return 'vendor-html2canvas'
|
||||
if (id.match(/node_modules[\\/]@?react-router/)) return 'vendor-reactrouter'
|
||||
// Büyük modüller için özel chunk
|
||||
if (id.match(/src[\\/]codeParser/)) return 'chunk-codeParser'
|
||||
if (id.match(/src[\\/]views[\\/]list[\\/]Utils/)) return 'chunk-list-utils'
|
||||
return 'vendor'
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
preview: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue