sozsoft-platform/ui/src/views/report/DevexpressReportViewer.tsx
2026-07-06 12:44:26 +03:00

189 lines
6.4 KiB
TypeScript

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { FC } from 'react'
import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useParams, useLocation } from 'react-router-dom'
import ReportViewer, {
Callbacks,
RequestOptions,
} from 'devexpress-reporting-react/dx-report-viewer'
import { ajaxSetup } from '@devexpress/analytics-core/analytics-utils-native'
import { ZoomAutoBy } from 'devexpress-reporting/viewer/constants'
import { APP_NAME } from '@/constants/app.constant'
import { MODE_DARK } from '@/constants/theme.constant'
import { useStoreState } from '@/store'
import { createReportQueryString, createReportUrl } from './reportRouteParams'
const VIEWER_THEME_LINK_ID = 'devexpress-report-viewer-theme'
const DEFAULT_REPORT_ZOOM = 1
interface ReportPreviewModel {
autoFitBy?: ZoomAutoBy
originalZoom?: number
zoom?: number
}
interface ReportPreviewContainer {
reportPreview?: ReportPreviewModel
ReportPreview?: ReportPreviewModel
previewModel?: ReportPreviewContainer
args?: ReportPreviewContainer
sender?: ReportPreviewContainer & {
GetReportPreview?: () => ReportPreviewModel
}
}
const getViewerClassName = (isDarkMode: boolean) =>
[
'dx-viewport',
'dx-device-desktop',
'dx-device-generic',
'dx-theme-generic',
'dx-theme-generic-typography',
isDarkMode ? 'dx-color-scheme-dark' : 'dx-color-scheme-light',
'report-viewer-shell',
isDarkMode ? 'report-viewer-shell-dark' : '',
]
.filter(Boolean)
.join(' ')
const loadViewerCss = (isDarkMode: boolean) => {
const styles = [
new URL('@devexpress/analytics-core/dist/css/dx-analytics.common.css', import.meta.url).href,
new URL('devexpress-reporting/dist/css/dx-webdocumentviewer.css', import.meta.url).href,
]
styles.forEach((href) => {
if (document.querySelector(`link[href="${href}"]`)) return
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = href
document.head.appendChild(link)
})
const themeHref = isDarkMode
? new URL('@devexpress/analytics-core/dist/css/dx-analytics.dark.css', import.meta.url).href
: new URL('@devexpress/analytics-core/dist/css/dx-analytics.light.css', import.meta.url).href
let themeLink = document.getElementById(VIEWER_THEME_LINK_ID) as HTMLLinkElement | null
if (!themeLink) {
themeLink = document.createElement('link')
themeLink.id = VIEWER_THEME_LINK_ID
themeLink.rel = 'stylesheet'
document.head.appendChild(themeLink)
}
if (themeLink.href !== themeHref) {
themeLink.href = themeHref
}
}
const DevexpressReportViewer: FC = () => {
const { translate } = useLocalization()
const { report, listFormCode, id } = useParams<{
report: string
listFormCode: string
id: string
}>()
const location = useLocation()
const token = useStoreState((state) => state.auth.session.token)
const themeMode = useStoreState((state) => state.theme.mode)
const cultureName = useStoreState((state) => state.locale.currentLang)
const configCultureName = useStoreState(
(state) => state.abpConfig.config?.localization.currentCulture.cultureName,
)
const [configuredRequestKey, setConfiguredRequestKey] = useState<string>()
const previewModelRef = useRef<ReportPreviewModel>()
const isDarkMode = themeMode === MODE_DARK
const reportCultureName = cultureName || configCultureName
const requestConfigKey = `${token ?? ''}\u0000${reportCultureName ?? ''}`
const getReportPreview = useCallback(
(eventOrModel?: ReportPreviewContainer | ReportPreviewModel) => {
const event = eventOrModel as ReportPreviewContainer | undefined
const eventArgs = event?.args
const sender = event?.sender
const previewModel = event?.previewModel
const candidates = [
eventArgs?.reportPreview,
eventArgs?.ReportPreview,
eventArgs?.previewModel?.reportPreview,
event?.reportPreview,
event?.ReportPreview,
previewModel?.reportPreview,
sender?.GetReportPreview?.(),
sender?.previewModel?.reportPreview,
eventOrModel as ReportPreviewModel | undefined,
previewModelRef.current,
]
return candidates.find(
(candidate) =>
candidate &&
('zoom' in candidate || 'originalZoom' in candidate || 'autoFitBy' in candidate),
)
},
[],
)
const setDefaultZoom = useCallback(
(eventOrModel?: ReportPreviewContainer | ReportPreviewModel) => {
const preview = getReportPreview(eventOrModel)
if (!preview) return
previewModelRef.current = preview
preview.autoFitBy = ZoomAutoBy.None
preview.originalZoom = DEFAULT_REPORT_ZOOM
preview.zoom = DEFAULT_REPORT_ZOOM
},
[getReportPreview],
)
useEffect(() => {
loadViewerCss(isDarkMode)
}, [isDarkMode])
useEffect(() => {
const nextHeaders = { ...(ajaxSetup.ajaxSettings.headers ?? {}) }
delete nextHeaders.Authorization
delete nextHeaders['Accept-Language']
ajaxSetup.ajaxSettings.headers = {
...nextHeaders,
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(reportCultureName ? { 'Accept-Language': reportCultureName } : {}),
}
setConfiguredRequestKey(requestConfigKey)
}, [token, reportCultureName, requestConfigKey])
const reportUrlWithParams = useMemo(() => {
const searchParams = new URLSearchParams(location.search)
if (reportCultureName) {
searchParams.set('cultureName', reportCultureName)
}
const search = createReportQueryString(searchParams)
return createReportUrl({ report, id, listFormCode }, search ? `?${search}` : '')
}, [report, id, listFormCode, location.search, reportCultureName])
if (!reportUrlWithParams) return null
return (
<Container className={getViewerClassName(isDarkMode)}>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Reports')}
defaultTitle={APP_NAME}
/>
{configuredRequestKey === requestConfigKey && (
<ReportViewer key={reportUrlWithParams} reportUrl={reportUrlWithParams}>
<Callbacks BeforeRender={setDefaultZoom} DocumentReady={setDefaultZoom} />
<RequestOptions host={import.meta.env.VITE_API_URL} invokeAction="/DXXRDV" />
</ReportViewer>
)}
</Container>
)
}
export default DevexpressReportViewer