Performans çalışmaları ve DevExpres License

This commit is contained in:
Sedat ÖZTÜRK 2025-08-18 17:55:51 +03:00
parent 0d04a30767
commit 7c6d4857df
8 changed files with 659 additions and 293 deletions

1
ui/.gitignore vendored
View file

@ -24,3 +24,4 @@ build
*.sln
*.sw?
.vite-cache/
src/devextreme-license.ts

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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(() => {
// Hoverda ı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 zoomu 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>
))}

View file

@ -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 />)

View file

@ -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 workbook = new Workbook()
const worksheet = workbook.addWorksheet(`${listFormCode}_sheet`)
exportDataExcel({
component: gridRef?.current?.instance,
worksheet,
autoFilterEnabled: true,
}).then(() => {
workbook.xlsx.writeBuffer().then((buffer) => {
const onExporting = async (e: DataGridTypes.ExportingEvent) => {
// DevExtremein 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`)
await exportDataExcel({
component: grid,
worksheet,
autoFilterEnabled: true,
})
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' },
)
}
}

View file

@ -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: {