u
This commit is contained in:
71
package-lock.json
generated
71
package-lock.json
generated
@@ -15,6 +15,8 @@
|
|||||||
"@mui/icons-material": "^9.4.0",
|
"@mui/icons-material": "^9.4.0",
|
||||||
"@mui/material": "^9.4.0",
|
"@mui/material": "^9.4.0",
|
||||||
"@shopify/draggable": "^1.2.1",
|
"@shopify/draggable": "^1.2.1",
|
||||||
|
"ag-grid-community": "^36.2.0",
|
||||||
|
"ag-grid-react": "^36.2.0",
|
||||||
"dashgui": "file:../dashgui",
|
"dashgui": "file:../dashgui",
|
||||||
"jsbarcode": "^3.12.3",
|
"jsbarcode": "^3.12.3",
|
||||||
"notistack": "^3.0.2",
|
"notistack": "^3.0.2",
|
||||||
@@ -24,6 +26,7 @@
|
|||||||
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
||||||
"react-dom": "^19.3.0",
|
"react-dom": "^19.3.0",
|
||||||
"react-router-dom": "^7.18.4",
|
"react-router-dom": "^7.18.4",
|
||||||
|
"react-virtual-aggridautomat": "file:../react-virtual-aggridautomat",
|
||||||
"sortablejs": "^1.15.7",
|
"sortablejs": "^1.15.7",
|
||||||
"thumbhash": "^0.1.1"
|
"thumbhash": "^0.1.1"
|
||||||
},
|
},
|
||||||
@@ -79,6 +82,34 @@
|
|||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"../react-virtual-aggridautomat": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"license": "0BSD",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/prismjs": "^1.26.6",
|
||||||
|
"ag-grid-community": "^36.2.0",
|
||||||
|
"ag-grid-react": "^36.2.0",
|
||||||
|
"marked": "^18.0.14",
|
||||||
|
"prismjs": "^1.30.0",
|
||||||
|
"react": "^19.2.8",
|
||||||
|
"react-automat": "github:sebseb7/react-automat",
|
||||||
|
"react-dom": "^19.2.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@types/node": "^24.13.3",
|
||||||
|
"@types/react": "^19.2.18",
|
||||||
|
"@types/react-dom": "^19.2.7",
|
||||||
|
"@vitejs/plugin-react": "^6.1.1",
|
||||||
|
"eslint": "^10.10.0",
|
||||||
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.5.6",
|
||||||
|
"globals": "^17.12.0",
|
||||||
|
"typescript": "~6.0.2",
|
||||||
|
"typescript-eslint": "^8.69.0",
|
||||||
|
"vite": "^8.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||||
@@ -3848,6 +3879,42 @@
|
|||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ag-charts-types": {
|
||||||
|
"version": "14.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-14.2.0.tgz",
|
||||||
|
"integrity": "sha512-4pDV6/4zbzPN7t607Gr1o6DO9RhmPafQGr+hIpTJBaerbaMoC+WOO3v/wkxFbvCNrHKOexEKSfNTH7PaAR4UzA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/ag-grid-community": {
|
||||||
|
"version": "36.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-36.2.0.tgz",
|
||||||
|
"integrity": "sha512-tK0Toj7t0fV/OMLPnDc0xlYKAqDbnCT3iJodYSn2TJ541caU8jPdYwWhMdW+AP2+pUNwFiJVgc04eJNfvBPsWw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ag-charts-types": "14.2.0",
|
||||||
|
"ag-stack": "36.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ag-grid-react": {
|
||||||
|
"version": "36.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-36.2.0.tgz",
|
||||||
|
"integrity": "sha512-7dsaR4Ga8/bJAOov7XJqxsDc1JAjUU5Bmh9+s4U9+/TKIdSsaVo53SuZb2yEFSB1elv3eh6aZP8a1fqGZehXwA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ag-grid-community": "36.2.0",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ag-stack": {
|
||||||
|
"version": "36.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ag-stack/-/ag-stack-36.2.0.tgz",
|
||||||
|
"integrity": "sha512-f3XT2htFs9dUPcHni2z9ak69QNta5ODEpooHuuNvbjN98JkokpEqRQ9YLQHQJFdCcgi9Ybs/j3zkkvmNRamIPg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.15.0",
|
"version": "6.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
|
||||||
@@ -7412,6 +7479,10 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-virtual-aggridautomat": {
|
||||||
|
"resolved": "../react-virtual-aggridautomat",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/readable-stream": {
|
"node_modules/readable-stream": {
|
||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"@mui/icons-material": "^9.4.0",
|
"@mui/icons-material": "^9.4.0",
|
||||||
"@mui/material": "^9.4.0",
|
"@mui/material": "^9.4.0",
|
||||||
"@shopify/draggable": "^1.2.1",
|
"@shopify/draggable": "^1.2.1",
|
||||||
|
"ag-grid-community": "^36.2.0",
|
||||||
|
"ag-grid-react": "^36.2.0",
|
||||||
"dashgui": "file:../dashgui",
|
"dashgui": "file:../dashgui",
|
||||||
"jsbarcode": "^3.12.3",
|
"jsbarcode": "^3.12.3",
|
||||||
"notistack": "^3.0.2",
|
"notistack": "^3.0.2",
|
||||||
@@ -31,6 +33,7 @@
|
|||||||
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
||||||
"react-dom": "^19.3.0",
|
"react-dom": "^19.3.0",
|
||||||
"react-router-dom": "^7.18.4",
|
"react-router-dom": "^7.18.4",
|
||||||
|
"react-virtual-aggridautomat": "file:../react-virtual-aggridautomat",
|
||||||
"sortablejs": "^1.15.7",
|
"sortablejs": "^1.15.7",
|
||||||
"thumbhash": "^0.1.1"
|
"thumbhash": "^0.1.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -215,6 +215,32 @@ export const article = {
|
|||||||
return http(`/api/article/${encodeURIComponent(sku)}`);
|
return http(`/api/article/${encodeURIComponent(sku)}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/article/history
|
||||||
|
* History of all articles with paging/sorting/filtering
|
||||||
|
*/
|
||||||
|
getHistory({ startRow, endRow, offset, limit, sortModel, filterModel } = {}) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (startRow != null) params.set('startRow', String(startRow));
|
||||||
|
if (endRow != null) params.set('endRow', String(endRow));
|
||||||
|
if (offset != null) params.set('offset', String(offset));
|
||||||
|
if (limit != null) params.set('limit', String(limit));
|
||||||
|
if (sortModel && sortModel.length) params.set('sort', JSON.stringify(sortModel));
|
||||||
|
if (filterModel && Object.keys(filterModel).length) params.set('filter', JSON.stringify(filterModel));
|
||||||
|
const qs = params.toString();
|
||||||
|
return http(`/api/article/history${qs ? `?${qs}` : ''}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/article/:sku/history
|
||||||
|
*/
|
||||||
|
getBySkuHistory(sku, { limit } = {}) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (limit != null) params.set('limit', String(limit));
|
||||||
|
const qs = params.toString();
|
||||||
|
return http(`/api/article/${encodeURIComponent(sku)}/history${qs ? `?${qs}` : ''}`);
|
||||||
|
},
|
||||||
|
|
||||||
/** POST /api/article → 201 { article } */
|
/** POST /api/article → 201 { article } */
|
||||||
create({ parentId, name, track_stock }) {
|
create({ parentId, name, track_stock }) {
|
||||||
return http('/api/article', {
|
return http('/api/article', {
|
||||||
@@ -1797,6 +1823,24 @@ export const labeltemplate = {
|
|||||||
// thumb is returned inline as a base64 string in list/get responses
|
// thumb is returned inline as a base64 string in list/get responses
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const globalHistory = {
|
||||||
|
/**
|
||||||
|
* GET /api/history
|
||||||
|
* Global system/entity history with paging/sorting/filtering
|
||||||
|
*/
|
||||||
|
list({ startRow, endRow, offset, limit, sortModel, filterModel } = {}) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (startRow != null) params.set('startRow', String(startRow));
|
||||||
|
if (endRow != null) params.set('endRow', String(endRow));
|
||||||
|
if (offset != null) params.set('offset', String(offset));
|
||||||
|
if (limit != null) params.set('limit', String(limit));
|
||||||
|
if (sortModel && sortModel.length) params.set('sort', JSON.stringify(sortModel));
|
||||||
|
if (filterModel && Object.keys(filterModel).length) params.set('filter', JSON.stringify(filterModel));
|
||||||
|
const qs = params.toString();
|
||||||
|
return http(`/api/history${qs ? `?${qs}` : ''}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
auth,
|
auth,
|
||||||
article,
|
article,
|
||||||
@@ -1822,4 +1866,5 @@ export default {
|
|||||||
unit,
|
unit,
|
||||||
warehouse,
|
warehouse,
|
||||||
stock,
|
stock,
|
||||||
|
globalHistory,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ import KundeListePage from './routes/kunde';
|
|||||||
import KundeNeuPage from './routes/kunde/neu';
|
import KundeNeuPage from './routes/kunde/neu';
|
||||||
import KundeGruppenPage from './routes/kunde/gruppen';
|
import KundeGruppenPage from './routes/kunde/gruppen';
|
||||||
import KundeEditPage from './routes/kunde/_entityId';
|
import KundeEditPage from './routes/kunde/_entityId';
|
||||||
import InfosPage from './routes/infos';
|
import {
|
||||||
|
ArticleHistoryPage,
|
||||||
|
GlobalHistoryPage,
|
||||||
|
StatisticsPage,
|
||||||
|
} from './routes/infos';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@@ -42,7 +46,16 @@ const routes = [
|
|||||||
{ path: ':deliveryId', element: <LieferungEditPage /> },
|
{ path: ':deliveryId', element: <LieferungEditPage /> },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ path: 'infos', element: <InfosPage /> },
|
{
|
||||||
|
path: 'infos',
|
||||||
|
element: <SectionLayout sectionPath="/infos" />,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <ArticleHistoryPage /> },
|
||||||
|
{ path: 'articlehistory', element: <ArticleHistoryPage /> },
|
||||||
|
{ path: 'globalhistory', element: <GlobalHistoryPage /> },
|
||||||
|
{ path: 'statistics', element: <StatisticsPage /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'kategorien/*',
|
path: 'kategorien/*',
|
||||||
element: <SectionLayout sectionPath="/artikel" />,
|
element: <SectionLayout sectionPath="/artikel" />,
|
||||||
|
|||||||
34
src/automats/articleHistoryAutomat.js
Normal file
34
src/automats/articleHistoryAutomat.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { createGridAutomat } from 'react-virtual-aggridautomat';
|
||||||
|
import { article } from '../api';
|
||||||
|
import authAutomat from './authAutomat';
|
||||||
|
|
||||||
|
export const articleHistoryGridAutomat = createGridAutomat({
|
||||||
|
name: 'articleHistory',
|
||||||
|
blockSize: 50,
|
||||||
|
fetchRows: async ({ startRow, endRow, sortModel, filterModel }) => {
|
||||||
|
try {
|
||||||
|
const res = await article.getHistory({
|
||||||
|
startRow,
|
||||||
|
endRow,
|
||||||
|
sortModel,
|
||||||
|
filterModel,
|
||||||
|
});
|
||||||
|
const rows = res?.rows ?? res?.history ?? res?.entries ?? (Array.isArray(res) ? res : []);
|
||||||
|
const total = res?.total ?? res?.totalRows ?? res?.totalCount;
|
||||||
|
const lastRow = total != null ? total : (rows.length < (endRow - startRow) ? startRow + rows.length : undefined);
|
||||||
|
return { rows, lastRow };
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to fetch article history (backend route pending):', err);
|
||||||
|
return { rows: [], lastRow: 0 };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reload on company switch or auth change
|
||||||
|
authAutomat.subscribe((auth) => {
|
||||||
|
if (auth.status === 'authed' && auth.user?.selectedCompanyId) {
|
||||||
|
articleHistoryGridAutomat.actions.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default articleHistoryGridAutomat;
|
||||||
34
src/automats/globalHistoryAutomat.js
Normal file
34
src/automats/globalHistoryAutomat.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { createGridAutomat } from 'react-virtual-aggridautomat';
|
||||||
|
import { globalHistory } from '../api';
|
||||||
|
import authAutomat from './authAutomat';
|
||||||
|
|
||||||
|
export const globalHistoryGridAutomat = createGridAutomat({
|
||||||
|
name: 'globalHistory',
|
||||||
|
blockSize: 50,
|
||||||
|
fetchRows: async ({ startRow, endRow, sortModel, filterModel }) => {
|
||||||
|
try {
|
||||||
|
const res = await globalHistory.list({
|
||||||
|
startRow,
|
||||||
|
endRow,
|
||||||
|
sortModel,
|
||||||
|
filterModel,
|
||||||
|
});
|
||||||
|
const rows = res?.rows ?? res?.history ?? res?.entries ?? (Array.isArray(res) ? res : []);
|
||||||
|
const total = res?.total ?? res?.totalRows ?? res?.totalCount;
|
||||||
|
const lastRow = total != null ? total : (rows.length < (endRow - startRow) ? startRow + rows.length : undefined);
|
||||||
|
return { rows, lastRow };
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to fetch global history (backend route pending):', err);
|
||||||
|
return { rows: [], lastRow: 0 };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reload on company switch or auth change
|
||||||
|
authAutomat.subscribe((auth) => {
|
||||||
|
if (auth.status === 'authed' && auth.user?.selectedCompanyId) {
|
||||||
|
globalHistoryGridAutomat.actions.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default globalHistoryGridAutomat;
|
||||||
@@ -70,3 +70,6 @@ export {
|
|||||||
invalidateAllCategoryViews,
|
invalidateAllCategoryViews,
|
||||||
} from './categoryAutomat';
|
} from './categoryAutomat';
|
||||||
|
|
||||||
|
export { default as articleHistoryGridAutomat } from './articleHistoryAutomat';
|
||||||
|
export { default as globalHistoryGridAutomat } from './globalHistoryAutomat';
|
||||||
|
|
||||||
|
|||||||
@@ -65,13 +65,13 @@ class ArticlePhoto extends Component {
|
|||||||
fill || objectFit === 'cover'
|
fill || objectFit === 'cover'
|
||||||
? { position: 'absolute', inset: 0 }
|
? { position: 'absolute', inset: 0 }
|
||||||
: {
|
: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '100%',
|
maxHeight: '100%',
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
width: photo.thumbW >= photo.thumbH ? '100%' : 'auto',
|
width: photo.thumbW >= photo.thumbH ? '100%' : 'auto',
|
||||||
height: photo.thumbH > photo.thumbW ? '100%' : 'auto',
|
height: photo.thumbH > photo.thumbW ? '100%' : 'auto',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ThumbHashImage thumbHash={photo.thumbHash} />
|
<ThumbHashImage thumbHash={photo.thumbHash} />
|
||||||
@@ -87,21 +87,21 @@ class ArticlePhoto extends Component {
|
|||||||
sx={
|
sx={
|
||||||
fill || objectFit === 'cover'
|
fill || objectFit === 'cover'
|
||||||
? {
|
? {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
objectFit: 'cover',
|
objectFit: 'cover',
|
||||||
opacity: thumbLoaded ? 1 : 0,
|
opacity: thumbLoaded ? 1 : 0,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '100%',
|
maxHeight: '100%',
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
opacity: thumbLoaded ? 1 : 0,
|
opacity: thumbLoaded ? 1 : 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -121,13 +121,13 @@ export default class ArticleSearchDialog extends PureComponent {
|
|||||||
const trimmed = query.trim().toLowerCase();
|
const trimmed = query.trim().toLowerCase();
|
||||||
const filtered = trimmed
|
const filtered = trimmed
|
||||||
? bookableArticles.filter((a) => {
|
? bookableArticles.filter((a) => {
|
||||||
const skuMatch = a.sku?.toLowerCase().includes(trimmed);
|
const skuMatch = a.sku?.toLowerCase().includes(trimmed);
|
||||||
const nameMatch = a.name?.toLowerCase().includes(trimmed);
|
const nameMatch = a.name?.toLowerCase().includes(trimmed);
|
||||||
const eanMatch = Array.isArray(a.eans)
|
const eanMatch = Array.isArray(a.eans)
|
||||||
? a.eans.some((e) => String(e).toLowerCase().includes(trimmed))
|
? a.eans.some((e) => String(e).toLowerCase().includes(trimmed))
|
||||||
: false;
|
: false;
|
||||||
return skuMatch || nameMatch || eanMatch;
|
return skuMatch || nameMatch || eanMatch;
|
||||||
})
|
})
|
||||||
: bookableArticles.slice(0, 50);
|
: bookableArticles.slice(0, 50);
|
||||||
|
|
||||||
const rawSku = line?.sku != null ? String(line.sku).trim() : '';
|
const rawSku = line?.sku != null ? String(line.sku).trim() : '';
|
||||||
@@ -270,8 +270,6 @@ export default class ArticleSearchDialog extends PureComponent {
|
|||||||
<ArticlePhoto
|
<ArticlePhoto
|
||||||
photo={{ fullHash, thumbHash, thumbW, thumbH }}
|
photo={{ fullHash, thumbHash, thumbW, thumbH }}
|
||||||
size={34}
|
size={34}
|
||||||
fill
|
|
||||||
objectFit="cover"
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Inventory2OutlinedIcon sx={{ fontSize: 18, color: 'text.secondary' }} />
|
<Inventory2OutlinedIcon sx={{ fontSize: 18, color: 'text.secondary' }} />
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class ManifestFormLine extends PureComponent {
|
|||||||
const current = getArticle(sku);
|
const current = getArticle(sku);
|
||||||
this.setState({ cachedArticle: current });
|
this.setState({ cachedArticle: current });
|
||||||
if (!current) {
|
if (!current) {
|
||||||
artikelAutomat.actions.loadArticle(sku).catch(() => {});
|
artikelAutomat.actions.loadArticle(sku).catch(() => { });
|
||||||
}
|
}
|
||||||
this.unsubArticle = artikelAutomat.subscribe(this, (state) => ({
|
this.unsubArticle = artikelAutomat.subscribe(this, (state) => ({
|
||||||
cachedArticle: createArticleSelector(sku)(state),
|
cachedArticle: createArticleSelector(sku)(state),
|
||||||
@@ -118,8 +118,8 @@ class ManifestFormLine extends PureComponent {
|
|||||||
isBooked
|
isBooked
|
||||||
? `Vollständig im Lager gebucht: ${ledgerLine.bookedAmount} Stück (Klicken für Details)`
|
? `Vollständig im Lager gebucht: ${ledgerLine.bookedAmount} Stück (Klicken für Details)`
|
||||||
: isPartial
|
: isPartial
|
||||||
? `Teilweise im Lager gebucht: ${ledgerLine.bookedAmount} von ${ledgerLine.targetAmount} Stück (Klicken zum Einbuchen dieser Position)`
|
? `Teilweise im Lager gebucht: ${ledgerLine.bookedAmount} von ${ledgerLine.targetAmount} Stück (Klicken zum Einbuchen dieser Position)`
|
||||||
: `Noch nicht im Lager gebucht (${ledgerLine.targetAmount} Stück – Klicken zum Einbuchen dieser Position)`
|
: `Noch nicht im Lager gebucht (${ledgerLine.targetAmount} Stück – Klicken zum Einbuchen dieser Position)`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Chip
|
<Chip
|
||||||
@@ -128,9 +128,9 @@ class ManifestFormLine extends PureComponent {
|
|||||||
onClick={
|
onClick={
|
||||||
isClickable
|
isClickable
|
||||||
? (e) => {
|
? (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onBookLine(ledgerLine);
|
onBookLine(ledgerLine);
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
@@ -144,8 +144,8 @@ class ManifestFormLine extends PureComponent {
|
|||||||
isBooked
|
isBooked
|
||||||
? `Lager: ${ledgerLine.bookedAmount} Stk. gebucht`
|
? `Lager: ${ledgerLine.bookedAmount} Stk. gebucht`
|
||||||
: isPartial
|
: isPartial
|
||||||
? `Lager: ${ledgerLine.bookedAmount}/${ledgerLine.targetAmount} Stk.`
|
? `Lager: ${ledgerLine.bookedAmount}/${ledgerLine.targetAmount} Stk.`
|
||||||
: 'Lager: offen'
|
: 'Lager: offen'
|
||||||
}
|
}
|
||||||
color={isBooked ? 'success' : isPartial ? 'warning' : 'default'}
|
color={isBooked ? 'success' : isPartial ? 'warning' : 'default'}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -157,12 +157,12 @@ class ManifestFormLine extends PureComponent {
|
|||||||
bgcolor: isBooked ? 'success.50' : undefined,
|
bgcolor: isBooked ? 'success.50' : undefined,
|
||||||
'&:hover': isClickable
|
'&:hover': isClickable
|
||||||
? {
|
? {
|
||||||
bgcolor: isBooked
|
bgcolor: isBooked
|
||||||
? 'success.100'
|
? 'success.100'
|
||||||
: isPartial
|
: isPartial
|
||||||
? 'warning.100'
|
? 'warning.100'
|
||||||
: 'action.hover',
|
: 'action.hover',
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -239,10 +239,10 @@ class ManifestFormLine extends PureComponent {
|
|||||||
MozAppearance: 'textfield',
|
MozAppearance: 'textfield',
|
||||||
},
|
},
|
||||||
'& input[type=number]::-webkit-outer-spin-button, & input[type=number]::-webkit-inner-spin-button':
|
'& input[type=number]::-webkit-outer-spin-button, & input[type=number]::-webkit-inner-spin-button':
|
||||||
{
|
{
|
||||||
WebkitAppearance: 'none',
|
WebkitAppearance: 'none',
|
||||||
margin: 0,
|
margin: 0,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -282,9 +282,9 @@ class ManifestFormLine extends PureComponent {
|
|||||||
const vkNetto = vkPrice?.net;
|
const vkNetto = vkPrice?.net;
|
||||||
const vkBrutto = vkPrice
|
const vkBrutto = vkPrice
|
||||||
? priceBrutto(vkPrice) ??
|
? priceBrutto(vkPrice) ??
|
||||||
(vkPrice.net != null && vkPrice.taxrate != null
|
(vkPrice.net != null && vkPrice.taxrate != null
|
||||||
? vkPrice.net * (1 + vkPrice.taxrate / 100)
|
? vkPrice.net * (1 + vkPrice.taxrate / 100)
|
||||||
: null)
|
: null)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -335,8 +335,6 @@ class ManifestFormLine extends PureComponent {
|
|||||||
<ArticlePhoto
|
<ArticlePhoto
|
||||||
photo={{ fullHash, thumbHash, thumbW, thumbH }}
|
photo={{ fullHash, thumbHash, thumbW, thumbH }}
|
||||||
size={28}
|
size={28}
|
||||||
fill
|
|
||||||
objectFit="cover"
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Inventory2OutlinedIcon sx={{ fontSize: 16, color: 'text.secondary' }} />
|
<Inventory2OutlinedIcon sx={{ fontSize: 16, color: 'text.secondary' }} />
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ function TopBand({ onSettingsClick, settingsTab, terminalCount, onPlacementClick
|
|||||||
color: isDeleteMode
|
color: isDeleteMode
|
||||||
? 'error.main'
|
? 'error.main'
|
||||||
: isTerminalMode || isPhotoMode
|
: isTerminalMode || isPhotoMode
|
||||||
? 'primary.main'
|
? 'primary.main'
|
||||||
: 'common.white',
|
: 'common.white',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isTerminalMode ? (
|
{isTerminalMode ? (
|
||||||
@@ -114,10 +114,10 @@ function MiddleBand({ settingsTab, nameInput, terminals, categoryPosterminalIds,
|
|||||||
px: 1,
|
px: 1,
|
||||||
...(isActionTab
|
...(isActionTab
|
||||||
? {
|
? {
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
bgcolor: settingsTab === -3 ? 'rgba(211, 47, 47, 0.12)' : 'grey.400',
|
bgcolor: settingsTab === -3 ? 'rgba(211, 47, 47, 0.12)' : 'grey.400',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
@import 'ag-grid-community/styles/ag-grid.css';
|
||||||
|
@import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
|
|||||||
31
src/nav.js
31
src/nav.js
@@ -17,6 +17,9 @@ import RequestQuoteIcon from '@mui/icons-material/RequestQuote';
|
|||||||
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
|
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
|
||||||
import LabelIcon from '@mui/icons-material/Label';
|
import LabelIcon from '@mui/icons-material/Label';
|
||||||
import TuneIcon from '@mui/icons-material/Tune';
|
import TuneIcon from '@mui/icons-material/Tune';
|
||||||
|
import HistoryIcon from '@mui/icons-material/History';
|
||||||
|
import PublicIcon from '@mui/icons-material/Public';
|
||||||
|
import BarChartIcon from '@mui/icons-material/BarChart';
|
||||||
import { isKategorienPath } from './components/category/categoryRoute';
|
import { isKategorienPath } from './components/category/categoryRoute';
|
||||||
import { deliveryEditorAutomat } from './automats/deliveryEditorAutomat';
|
import { deliveryEditorAutomat } from './automats/deliveryEditorAutomat';
|
||||||
|
|
||||||
@@ -229,6 +232,29 @@ export const SUB_NAV = {
|
|||||||
help: 'Kundengruppen für Staffelpreise und Rabatte verwalten',
|
help: 'Kundengruppen für Staffelpreise und Rabatte verwalten',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'/infos': [
|
||||||
|
{
|
||||||
|
segment: '',
|
||||||
|
label: 'ArticleHistory',
|
||||||
|
title: 'ArticleHistory',
|
||||||
|
icon: HistoryIcon,
|
||||||
|
help: 'Historie aller Artikeländerungen und Ereignisse',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
segment: 'globalhistory',
|
||||||
|
label: 'GlobalHistory',
|
||||||
|
title: 'GlobalHistory',
|
||||||
|
icon: PublicIcon,
|
||||||
|
help: 'Globale System- und Änderungshistorie',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
segment: 'statistics',
|
||||||
|
label: 'Statistics',
|
||||||
|
title: 'Statistics',
|
||||||
|
icon: BarChartIcon,
|
||||||
|
help: 'Statistische Auswertungen und Kennzahlen',
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isNavItemActive(pathname, navPath) {
|
export function isNavItemActive(pathname, navPath) {
|
||||||
@@ -281,6 +307,11 @@ export function resolveActiveSub(pathname, sectionPath) {
|
|||||||
return rest;
|
return rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Treat /infos/articlehistory as segment ''
|
||||||
|
if (sectionPath === '/infos' && rest === 'articlehistory') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
const active = subNav.find((item) => {
|
const active = subNav.find((item) => {
|
||||||
if (item.segment === 'kategorien') {
|
if (item.segment === 'kategorien') {
|
||||||
return isKategorienPath(pathname);
|
return isKategorienPath(pathname);
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import PageLayout from '../components/PageLayout';
|
|
||||||
|
|
||||||
export default function InfosPage() {
|
|
||||||
return (
|
|
||||||
<PageLayout title="Infos">
|
|
||||||
<Typography color="text.secondary">Infos — Inhalt folgt.</Typography>
|
|
||||||
</PageLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
152
src/routes/infos/ArticleHistoryPage.jsx
Normal file
152
src/routes/infos/ArticleHistoryPage.jsx
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { PanelTile } from 'dashgui/panels';
|
||||||
|
import { AutomatGrid } from 'react-virtual-aggridautomat';
|
||||||
|
import { articleHistoryGridAutomat } from '../../automats';
|
||||||
|
import { router } from '../../appRouter';
|
||||||
|
|
||||||
|
const COLUMN_DEFS = [
|
||||||
|
{
|
||||||
|
headerName: 'ID',
|
||||||
|
field: 'id',
|
||||||
|
maxWidth: 90,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agNumberColumnFilter',
|
||||||
|
cellStyle: { fontVariantNumeric: 'tabular-nums' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Datum',
|
||||||
|
field: 'date',
|
||||||
|
minWidth: 170,
|
||||||
|
maxWidth: 200,
|
||||||
|
sortable: true,
|
||||||
|
valueFormatter: (p) => (p.value ? new Date(p.value).toLocaleString('de-DE') : ''),
|
||||||
|
cellStyle: { fontVariantNumeric: 'tabular-nums' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Artikel-Nr.',
|
||||||
|
field: 'sku',
|
||||||
|
minWidth: 130,
|
||||||
|
maxWidth: 160,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agTextColumnFilter',
|
||||||
|
cellRenderer: (p) => {
|
||||||
|
if (!p.value) return '';
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={`/artikel/${encodeURIComponent(p.value)}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
void router.navigate(`/artikel/${encodeURIComponent(p.value)}`);
|
||||||
|
}}
|
||||||
|
style={{ color: '#2563eb', fontWeight: 600, textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
{p.value}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Artikelname',
|
||||||
|
field: 'name',
|
||||||
|
minWidth: 180,
|
||||||
|
flex: 1,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agTextColumnFilter',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Änderung / Ereignis',
|
||||||
|
field: 'text',
|
||||||
|
minWidth: 300,
|
||||||
|
flex: 2,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agTextColumnFilter',
|
||||||
|
cellStyle: { whiteSpace: 'pre-wrap', lineHeight: '1.4' },
|
||||||
|
autoHeight: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Benutzer',
|
||||||
|
field: 'user',
|
||||||
|
maxWidth: 160,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agTextColumnFilter',
|
||||||
|
valueGetter: (p) =>
|
||||||
|
p.data?.user ||
|
||||||
|
p.data?.employee ||
|
||||||
|
p.data?.companyuser ||
|
||||||
|
(p.data?.companyuserId ? `User #${p.data.companyuserId}` : '—'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class ArticleHistoryPage extends PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const s = articleHistoryGridAutomat.state;
|
||||||
|
this.state = {
|
||||||
|
loading: s.loading,
|
||||||
|
error: s.error,
|
||||||
|
totalRows: s.totalRows,
|
||||||
|
};
|
||||||
|
this.handleReload = this.handleReload.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.unsub = articleHistoryGridAutomat.subscribe(this, (state) => ({
|
||||||
|
loading: state.loading,
|
||||||
|
error: state.error,
|
||||||
|
totalRows: state.totalRows,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unsub?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReload() {
|
||||||
|
articleHistoryGridAutomat.actions.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, totalRows } = this.state;
|
||||||
|
|
||||||
|
const actions = (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||||
|
{loading && <CircularProgress size={18} thickness={5} />}
|
||||||
|
{totalRows >= 0 && (
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{totalRows} Einträge
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<RefreshIcon />}
|
||||||
|
onClick={this.handleReload}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Aktualisieren
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelTile title="Artikel-Historie" actions={actions} sx={{ p: 0, height: 'calc(100vh - 140px)', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Box sx={{ flex: 1, minHeight: 0, width: '100%' }}>
|
||||||
|
<AutomatGrid
|
||||||
|
automat={articleHistoryGridAutomat}
|
||||||
|
columnDefs={COLUMN_DEFS}
|
||||||
|
rowIdField="id"
|
||||||
|
cacheBlockSize={50}
|
||||||
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
noRowsOverlayText="Keine Historieneinträge vorhanden"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</PanelTile>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/routes/infos/GlobalHistoryPage.jsx
Normal file
137
src/routes/infos/GlobalHistoryPage.jsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { PanelTile } from 'dashgui/panels';
|
||||||
|
import { AutomatGrid } from 'react-virtual-aggridautomat';
|
||||||
|
import { globalHistoryGridAutomat } from '../../automats';
|
||||||
|
|
||||||
|
const COLUMN_DEFS = [
|
||||||
|
{
|
||||||
|
headerName: 'ID',
|
||||||
|
field: 'id',
|
||||||
|
maxWidth: 90,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agNumberColumnFilter',
|
||||||
|
cellStyle: { fontVariantNumeric: 'tabular-nums' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Datum',
|
||||||
|
field: 'date',
|
||||||
|
minWidth: 170,
|
||||||
|
maxWidth: 200,
|
||||||
|
sortable: true,
|
||||||
|
valueFormatter: (p) => (p.value ? new Date(p.value).toLocaleString('de-DE') : ''),
|
||||||
|
cellStyle: { fontVariantNumeric: 'tabular-nums' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Typ',
|
||||||
|
field: 'type',
|
||||||
|
minWidth: 120,
|
||||||
|
maxWidth: 160,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agTextColumnFilter',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Referenz / Key',
|
||||||
|
field: 'reference',
|
||||||
|
minWidth: 140,
|
||||||
|
maxWidth: 200,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agTextColumnFilter',
|
||||||
|
valueGetter: (p) => p.data?.reference || p.data?.p1 || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Meldung / Beschreibung',
|
||||||
|
field: 'text',
|
||||||
|
minWidth: 300,
|
||||||
|
flex: 2,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agTextColumnFilter',
|
||||||
|
valueGetter: (p) => p.data?.text || p.data?.value || '',
|
||||||
|
cellStyle: { whiteSpace: 'pre-wrap', lineHeight: '1.4' },
|
||||||
|
autoHeight: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: 'Benutzer',
|
||||||
|
field: 'user',
|
||||||
|
maxWidth: 160,
|
||||||
|
sortable: true,
|
||||||
|
filter: 'agTextColumnFilter',
|
||||||
|
valueGetter: (p) =>
|
||||||
|
p.data?.user ||
|
||||||
|
p.data?.employee ||
|
||||||
|
(p.data?.companyuserId ? `User #${p.data.companyuserId}` : '—'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class GlobalHistoryPage extends PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const s = globalHistoryGridAutomat.state;
|
||||||
|
this.state = {
|
||||||
|
loading: s.loading,
|
||||||
|
error: s.error,
|
||||||
|
totalRows: s.totalRows,
|
||||||
|
};
|
||||||
|
this.handleReload = this.handleReload.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.unsub = globalHistoryGridAutomat.subscribe(this, (state) => ({
|
||||||
|
loading: state.loading,
|
||||||
|
error: state.error,
|
||||||
|
totalRows: state.totalRows,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unsub?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReload() {
|
||||||
|
globalHistoryGridAutomat.actions.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, totalRows } = this.state;
|
||||||
|
|
||||||
|
const actions = (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||||
|
{loading && <CircularProgress size={18} thickness={5} />}
|
||||||
|
{totalRows >= 0 && (
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{totalRows} Einträge
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<RefreshIcon />}
|
||||||
|
onClick={this.handleReload}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Aktualisieren
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelTile title="Globale Historie" actions={actions} sx={{ p: 0, height: 'calc(100vh - 140px)', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Box sx={{ flex: 1, minHeight: 0, width: '100%' }}>
|
||||||
|
<AutomatGrid
|
||||||
|
automat={globalHistoryGridAutomat}
|
||||||
|
columnDefs={COLUMN_DEFS}
|
||||||
|
rowIdField="id"
|
||||||
|
cacheBlockSize={50}
|
||||||
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
noRowsOverlayText="Keine globalen Historieneinträge vorhanden"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</PanelTile>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/routes/infos/StatisticsPage.jsx
Normal file
36
src/routes/infos/StatisticsPage.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { PureComponent } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import BarChartIcon from '@mui/icons-material/BarChart';
|
||||||
|
import { PanelTile } from 'dashgui/panels';
|
||||||
|
|
||||||
|
export default class StatisticsPage extends PureComponent {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<PanelTile title="Statistik">
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
py: 8,
|
||||||
|
px: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'text.secondary',
|
||||||
|
textAlign: 'center',
|
||||||
|
gap: 1.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BarChartIcon sx={{ fontSize: 48, opacity: 0.4 }} />
|
||||||
|
<Typography variant="h6" color="text.primary" fontWeight={600}>
|
||||||
|
Statistik & Auswertungen
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ maxWidth: 420 }}>
|
||||||
|
Hier entstehen demnächst grafische Auswertungen, Umsatzstatistiken und
|
||||||
|
Bestandskennzahlen.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</PanelTile>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/routes/infos/index.js
Normal file
3
src/routes/infos/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as ArticleHistoryPage } from './ArticleHistoryPage';
|
||||||
|
export { default as GlobalHistoryPage } from './GlobalHistoryPage';
|
||||||
|
export { default as StatisticsPage } from './StatisticsPage';
|
||||||
@@ -79,12 +79,14 @@ const sharedDeps = [
|
|||||||
'@emotion/styled',
|
'@emotion/styled',
|
||||||
'@mui/material',
|
'@mui/material',
|
||||||
'@mui/icons-material',
|
'@mui/icons-material',
|
||||||
|
'react-automat',
|
||||||
];
|
];
|
||||||
|
|
||||||
const sharedResolve = {
|
const sharedResolve = {
|
||||||
dedupe: sharedDeps,
|
dedupe: sharedDeps,
|
||||||
alias: {
|
alias: {
|
||||||
'node:module': path.resolve(__dirname, 'src/utils/emptyNodeModule.js'),
|
'node:module': path.resolve(__dirname, 'src/utils/emptyNodeModule.js'),
|
||||||
|
'react-automat': path.resolve(__dirname, '../automat'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user