import { PureComponent, createRef } from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; import ListItemButton from '@mui/material/ListItemButton'; import Typography from '@mui/material/Typography'; import Skeleton from '@mui/material/Skeleton'; import Tooltip from '@mui/material/Tooltip'; import ViewModuleIcon from '@mui/icons-material/ViewModule'; import Chip from '@mui/material/Chip'; import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; import FillSpace from 'dashgui/FillSpace'; import { PanelTile, PanelTileGrid } from 'dashgui/panels'; import BackendError from '../../components/BackendError'; import globalHistoryAutomat from '../../automats/globalHistoryAutomat'; import { router } from '../../appRouter'; import { backendErrorFrom } from '../../utils/backendError'; import { LINK } from '../../colors'; const CHUNK_SIZE = 50; function formatDateTime(val) { if (!val) return '–'; try { const d = new Date(val); if (isNaN(d.getTime())) return String(val); return d.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', }); } catch { return String(val); } } export function createDummyTestGlobalHistory() { return { id: 999999, date: '2030-10-30T12:00:00Z', type: 'Bestellung', reference: 'BEST-999999', text: 'Status geändert auf abgeschlossen durch Benutzer', user: 'Max Mustermann', }; } const rowGridSx = { display: 'grid', gridTemplateColumns: 'minmax(150px, max-content) minmax(110px, max-content) minmax(140px, max-content) minmax(240px, 1fr)', gridTemplateRows: '0px 1fr', rowGap: 0, columnGap: 2, alignItems: 'center', width: '100%', boxSizing: 'border-box', }; const ellipsisSx = { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }; const measuringCellSx = { visibility: 'hidden', height: 0, boxSizing: 'border-box', pointerEvents: 'none', textTransform: 'none', letterSpacing: 'normal', }; const headerCellSx = { color: 'text.secondary', typography: 'caption', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.04em', userSelect: 'none', }; function renderInvisibleDummyRow(dummy) { const d = dummy || createDummyTestGlobalHistory(); return ( <> {formatDateTime(d.date)} {d.type} {d.reference} {d.text} ); } class GlobalHistoryRow extends PureComponent { render() { const { item, disabled, headerItem } = this.props; const dummy = headerItem || createDummyTestGlobalHistory(); if (!item) { return ( {renderInvisibleDummyRow(dummy)} ); } const userName = item.user || item.employee || (item.companyuserId ? `User #${item.companyuserId}` : '—'); const typeStr = item.type || item.entityType || '–'; const refStr = item.reference || item.p1 || '–'; const isArticle = (typeStr === 'article' || typeStr === 'artikel') && refStr && refStr !== '–'; return ( { void router.navigate(`/artikel/${encodeURIComponent(refStr)}`); } : undefined } > {renderInvisibleDummyRow(dummy)} {/* 1. Datum */} {formatDateTime(item.date || item.createdAt)} {/* 2. Typ */} {/* 3. Referenz / Key */} {isArticle ? ( { e.preventDefault(); e.stopPropagation(); void router.navigate(`/artikel/${encodeURIComponent(refStr)}`); }} sx={{ color: LINK, fontWeight: 600, textDecoration: 'none', ...ellipsisSx, '&:hover': { textDecoration: 'underline' }, }} > {refStr} ) : ( {refStr} )} {/* 4. Meldung / Beschreibung */} {item.text || item.value || '–'} ); } } class GlobalHistorySearchField extends PureComponent { constructor(props) { super(props); const s = globalHistoryAutomat.state; const stats = globalHistoryAutomat.getChunkStats?.(); this.state = { input: s.filter ?? '', seekMonths: s.seekMonths ?? 0, seekYears: s.seekYears ?? 0, lruChunksCount: stats?.lruChunksCount || 0, maxLruChunks: stats?.maxLruChunks || 50, }; } componentDidMount() { this.unsub = globalHistoryAutomat.subscribe(this, (state) => ({ input: state.filter ?? '', seekMonths: state.seekMonths ?? 0, seekYears: state.seekYears ?? 0, })); if (import.meta.env.DEV) { this.unsubStats = globalHistoryAutomat.subscribeChunkStats?.((stats) => { this.setState({ lruChunksCount: stats.lruChunksCount, maxLruChunks: stats.maxLruChunks, }); }); } } componentWillUnmount() { this.unsub?.(); this.unsubStats?.(); } render() { const { inputRef, onReload, loading } = this.props; const { input, seekMonths, seekYears } = this.state; const monthActive = seekMonths > 0; const monthLabel = !monthActive ? '-1 Month' : `-${seekMonths} Month${seekMonths > 1 ? 's' : ''}`; const yearActive = seekYears > 0; const yearLabel = !yearActive ? '-1 Year' : `-${seekYears} Year${seekYears > 1 ? 's' : ''}`; return ( {/* Top row: search field */} globalHistoryAutomat.actions.filter(next)} /> {/* Filter Bar: Negativeseek (-1 Month, -1 Year, Reset) */} {import.meta.env.DEV && ( } label={ this.state.lruChunksCount > 0 ? `Chunk-Cache (${this.state.lruChunksCount}/${this.state.maxLruChunks || 50})` : 'Chunk-Cache' } onClick={() => window.dispatchEvent(new CustomEvent('dash:toggle-chunk-monitor'))} sx={{ cursor: 'pointer', height: 28, fontWeight: 600, fontSize: '0.75rem', bgcolor: 'background.paper', boxShadow: 1, '&:hover': { bgcolor: 'action.hover' }, }} /> )} ); } } export default class GlobalHistoryPage extends PureComponent { constructor(props) { super(props); this.headerItem = createDummyTestGlobalHistory(); this.state = { items: globalHistoryAutomat.getItems(), unfilteredTotal: globalHistoryAutomat.state.unfilteredTotal ?? 0, filterKey: globalHistoryAutomat.getFilterKey?.() ?? '', status: globalHistoryAutomat.state.status, error: globalHistoryAutomat.state.error, }; this.searchRef = createRef(); this.handleReload = this.handleReload.bind(this); this.handleRetry = this.handleRetry.bind(this); this.handlePageChange = this.handlePageChange.bind(this); } componentDidMount() { this.unsub = globalHistoryAutomat.subscribe(this, (state) => ({ items: state.items, unfilteredTotal: state.unfilteredTotal ?? 0, filterKey: globalHistoryAutomat.getFilterKey(state), status: state.status, error: state.error, })); } componentWillUnmount() { this.unsub?.(); } handlePageChange(page, meta) { const pageSize = meta?.pageSize ?? 10; const startIndex = (page - 1) * pageSize; const items = this.state.items; const maxIndex = items ? items.length - 1 : startIndex; const endIndex = Math.min(maxIndex, startIndex + pageSize - 1); const startChunk = Math.floor(startIndex / CHUNK_SIZE); const endChunk = Math.floor(endIndex / CHUNK_SIZE); globalHistoryAutomat.requireChunk(startChunk, endChunk); } handleReload() { void globalHistoryAutomat.actions.reload().catch(() => {}); } handleRetry() { void globalHistoryAutomat.actions.requireChunk(0, 0, { immediate: true }).catch(() => {}); } render() { const { items, unfilteredTotal, filterKey, status, error } = this.state; const loading = status === 'loading'; if (items === null && status === 'failed') { return ( <> ); } return ( <> {renderInvisibleDummyRow(this.headerItem)} Datum Typ Referenz / Key Meldung / Beschreibung } onPageChange={this.handlePageChange} /> {items === null && ( )} ); } }