2026-03-20 09:33:27 +08:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { ConfigProvider, App as AntApp } from 'antd';
|
|
|
|
|
import zhCN from 'antd/locale/zh_CN';
|
|
|
|
|
import { ConnectionStatus } from './components/ConnectionStatus';
|
|
|
|
|
import { ToolBar } from './components/ToolBar';
|
|
|
|
|
import { BomTable } from './components/BomTable';
|
|
|
|
|
import { AgGridBomTable } from './components/AgGridBomTable';
|
|
|
|
|
import { ProgressOverlay } from './components/ProgressOverlay';
|
|
|
|
|
import { ColumnConfigModal } from './components/ColumnConfigModal';
|
|
|
|
|
import { startHeartbeat } from './api/client';
|
|
|
|
|
import { useBomData } from './hooks/useBomData';
|
|
|
|
|
import { useDirtyTracking } from './hooks/useDirtyTracking';
|
|
|
|
|
import { useColumnConfig } from './hooks/useColumnConfig';
|
|
|
|
|
import type { SaveChangeDto } from './api/types';
|
|
|
|
|
|
|
|
|
|
const AppContent: React.FC = () => {
|
|
|
|
|
const [viewMode, setViewMode] = useState<'hierarchical' | 'flat'>('hierarchical');
|
|
|
|
|
const [configVisible, setConfigVisible] = useState(false);
|
|
|
|
|
|
2026-04-09 17:06:24 +08:00
|
|
|
const { items, treeData, settings, loading, reload, updateItemsLocally } = useBomData();
|
2026-03-20 09:33:27 +08:00
|
|
|
const { dirtyCells, setDirty, clearAll, isDirty, getDirtyChanges, dirtyCount } = useDirtyTracking();
|
|
|
|
|
const {
|
|
|
|
|
columns,
|
|
|
|
|
visibleColumns,
|
|
|
|
|
toggleVisible,
|
|
|
|
|
toggleExport,
|
|
|
|
|
addColumn,
|
|
|
|
|
removeColumn,
|
|
|
|
|
saveSettings,
|
|
|
|
|
} = useColumnConfig(settings);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const cleanup = startHeartbeat();
|
|
|
|
|
return cleanup;
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleSaveSettings = async () => {
|
|
|
|
|
await saveSettings();
|
|
|
|
|
setConfigVisible(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSaveSuccess = (changes: SaveChangeDto[]) => {
|
|
|
|
|
updateItemsLocally(changes);
|
|
|
|
|
clearAll();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div style={{ padding: 16, height: '100vh', display: 'flex', flexDirection: 'column', position: 'relative', overflow: 'hidden' }}>
|
|
|
|
|
<ConnectionStatus />
|
|
|
|
|
<ToolBar
|
|
|
|
|
dirtyCount={dirtyCount}
|
|
|
|
|
viewMode={viewMode}
|
|
|
|
|
onViewModeChange={setViewMode}
|
|
|
|
|
onRefresh={reload}
|
|
|
|
|
onOpenConfig={() => setConfigVisible(true)}
|
|
|
|
|
getDirtyChanges={getDirtyChanges}
|
|
|
|
|
onSaveSuccess={handleSaveSuccess}
|
2026-04-09 17:06:24 +08:00
|
|
|
items={items}
|
2026-03-20 09:33:27 +08:00
|
|
|
/>
|
|
|
|
|
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|
|
|
|
|
{viewMode === 'hierarchical' ? (
|
|
|
|
|
<BomTable
|
|
|
|
|
data={treeData}
|
|
|
|
|
loading={false}
|
|
|
|
|
visibleColumns={visibleColumns}
|
|
|
|
|
isDirty={isDirty}
|
|
|
|
|
setDirty={setDirty}
|
|
|
|
|
dirtyCells={dirtyCells}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<AgGridBomTable
|
|
|
|
|
data={treeData}
|
|
|
|
|
visibleColumns={visibleColumns}
|
|
|
|
|
isDirty={isDirty}
|
|
|
|
|
setDirty={setDirty}
|
|
|
|
|
dirtyCells={dirtyCells}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
<ProgressOverlay loading={loading} />
|
|
|
|
|
</div>
|
|
|
|
|
<ColumnConfigModal
|
|
|
|
|
visible={configVisible}
|
|
|
|
|
columns={columns}
|
|
|
|
|
onCancel={() => setConfigVisible(false)}
|
|
|
|
|
onSave={handleSaveSettings}
|
|
|
|
|
onToggleVisible={toggleVisible}
|
|
|
|
|
onToggleExport={toggleExport}
|
|
|
|
|
onAddColumn={addColumn}
|
|
|
|
|
onRemoveColumn={removeColumn}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const App: React.FC = () => {
|
|
|
|
|
return (
|
|
|
|
|
<ConfigProvider locale={zhCN}>
|
|
|
|
|
<AntApp>
|
|
|
|
|
<AppContent />
|
|
|
|
|
</AntApp>
|
|
|
|
|
</ConfigProvider>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default App;
|