mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 13:20:43 +00:00
Checkpoint - nearing releasable!
This commit is contained in:
@ -28,13 +28,16 @@ import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
|||||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
import {TablePagination} from "@mui/material";
|
import {TablePagination} from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
import LinearProgress from "@mui/material/LinearProgress";
|
import LinearProgress from "@mui/material/LinearProgress";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import {DataGridPro} from "@mui/x-data-grid-pro";
|
import {DataGridPro, GridSortModel} from "@mui/x-data-grid-pro";
|
||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import DataGridUtils from "qqq/utils/DataGridUtils";
|
import DataGridUtils from "qqq/utils/DataGridUtils";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
@ -51,33 +54,62 @@ const qController = Client.getInstance();
|
|||||||
function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
|
function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const [statusString, setStatusString] = useState("Calculating statistics...");
|
const [statusString, setStatusString] = useState("Calculating statistics...");
|
||||||
const [isLoaded, setIsLoaded] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [valueCounts, setValueCounts] = useState(null as QRecord[]);
|
const [valueCounts, setValueCounts] = useState(null as QRecord[]);
|
||||||
|
const [statsRecord, setStatsRecord] = useState(null as QRecord);
|
||||||
|
const [orderBy, setOrderBy] = useState(null as string);
|
||||||
|
const [statsFields, setStatsFields] = useState([] as QFieldMetaData[]);
|
||||||
const [countDistinct, setCountDistinct] = useState(null as number);
|
const [countDistinct, setCountDistinct] = useState(null as number);
|
||||||
const [rows, setRows] = useState([]);
|
const [rows, setRows] = useState([]);
|
||||||
const [columns, setColumns] = useState([]);
|
const [columns, setColumns] = useState([]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
|
if(!loading)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("tableName", tableMetaData.name);
|
formData.append("tableName", tableMetaData.name);
|
||||||
formData.append("fieldName", fieldMetaData.name);
|
formData.append("fieldName", fieldMetaData.name);
|
||||||
formData.append("filterJSON", JSON.stringify(filter));
|
formData.append("filterJSON", JSON.stringify(filter));
|
||||||
|
if(orderBy)
|
||||||
|
{
|
||||||
|
formData.append("orderBy", orderBy);
|
||||||
|
}
|
||||||
const processResult = await qController.processRun("tableStats", formData);
|
const processResult = await qController.processRun("tableStats", formData);
|
||||||
|
|
||||||
setStatusString(null)
|
setStatusString(null)
|
||||||
if (processResult instanceof QJobError)
|
if (processResult instanceof QJobError)
|
||||||
{
|
{
|
||||||
const jobError = processResult as QJobError;
|
const jobError = processResult as QJobError;
|
||||||
// todo setErrorAlert();
|
setStatusString("Error fetching column stats: " + jobError.error);
|
||||||
console.error("Error fetching column stats" + jobError.error);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const result = processResult as QJobComplete;
|
const result = processResult as QJobComplete;
|
||||||
setCountDistinct(result.values.countDistinct);
|
|
||||||
|
const statFieldObjects = result.values.statsFields;
|
||||||
|
if(statFieldObjects && statFieldObjects.length)
|
||||||
|
{
|
||||||
|
const newStatsFields = [] as QFieldMetaData[];
|
||||||
|
for(let i = 0; i<statFieldObjects.length; i++)
|
||||||
|
{
|
||||||
|
newStatsFields.push(new QFieldMetaData(statFieldObjects[i]))
|
||||||
|
}
|
||||||
|
setStatsFields(newStatsFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
let qRecord = new QRecord(result.values.statsRecord);
|
||||||
|
setStatsRecord(qRecord);
|
||||||
|
if(qRecord.values.has("countDistinct"))
|
||||||
|
{
|
||||||
|
setCountDistinct(qRecord.values.get("countDistinct"));
|
||||||
|
}
|
||||||
|
|
||||||
const valueCounts = [] as QRecord[];
|
const valueCounts = [] as QRecord[];
|
||||||
for(let i = 0; i < result.values.valueCounts.length; i++)
|
for(let i = 0; i < result.values.valueCounts.length; i++)
|
||||||
@ -95,43 +127,33 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
|
|||||||
|
|
||||||
const {rows, columnsToRender} = DataGridUtils.makeRows(valueCounts, fakeTableMetaData);
|
const {rows, columnsToRender} = DataGridUtils.makeRows(valueCounts, fakeTableMetaData);
|
||||||
const columns = DataGridUtils.setupGridColumns(fakeTableMetaData, columnsToRender);
|
const columns = DataGridUtils.setupGridColumns(fakeTableMetaData, columnsToRender);
|
||||||
|
columns[0].width = 200;
|
||||||
columns[1].sortComparator = (v1, v2): number =>
|
columns[1].width = 200;
|
||||||
{
|
|
||||||
const n1 = parseInt(v1.replaceAll(",", ""));
|
|
||||||
const n2 = parseInt(v2.replaceAll(",", ""));
|
|
||||||
return (n1 - n2);
|
|
||||||
}
|
|
||||||
|
|
||||||
setRows(rows);
|
setRows(rows);
|
||||||
setColumns(columns);
|
setColumns(columns);
|
||||||
|
setLoading(false);
|
||||||
setIsLoaded(true);
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, [loading]);
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const defaultLabelDisplayedRows = ({from, to, count}) =>
|
|
||||||
{
|
|
||||||
// todo - not done
|
|
||||||
return ("Showing stuff");
|
|
||||||
};
|
|
||||||
|
|
||||||
function CustomPagination()
|
function CustomPagination()
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<TablePagination
|
<Box pr={3}>
|
||||||
component="div"
|
{rows && rows.length && countDistinct && rows.length < countDistinct ? <span>Showing the first {rows.length.toLocaleString()} of {countDistinct.toLocaleString()} values</span> : <></>}
|
||||||
page={1}
|
{rows && rows.length && countDistinct && rows.length >= countDistinct && rows.length == 1 ? <span>Showing the only value</span> : <></>}
|
||||||
count={1}
|
{rows && rows.length && countDistinct && rows.length >= countDistinct && rows.length > 1 ? <span>Showing all {rows.length.toLocaleString()} values</span> : <></>}
|
||||||
rowsPerPage={1000}
|
</Box>
|
||||||
onPageChange={null}
|
|
||||||
labelDisplayedRows={defaultLabelDisplayedRows}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refresh = () =>
|
||||||
|
{
|
||||||
|
setLoading(true)
|
||||||
|
setStatusString("Refreshing...")
|
||||||
|
}
|
||||||
|
|
||||||
function Loading()
|
function Loading()
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
@ -139,43 +161,72 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSortChange = (gridSort: GridSortModel) =>
|
||||||
|
{
|
||||||
|
if (gridSort && gridSort.length > 0)
|
||||||
|
{
|
||||||
|
console.log("Sort: ", gridSort[0]);
|
||||||
|
setOrderBy(`${gridSort[0].field}.${gridSort[0].sort}`);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box p={3} display="flex" flexDirection="row" justifyContent="space-between" alignItems="flex-start">
|
<Box p={3} display="flex" flexDirection="row" justifyContent="space-between" alignItems="flex-start">
|
||||||
<Typography variant="h5" pb={3}>
|
<Typography variant="h5" pb={3}>
|
||||||
Column Statistics for {tableMetaData.label}: {fieldMetaData.label}
|
Column Statistics for {fieldMetaData.label}
|
||||||
<Typography fontSize={14}>
|
<Typography fontSize={14}>
|
||||||
{statusString}
|
{statusString ?? <> </>}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Button onClick={() => refresh()}>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={3} fontSize="1rem">
|
<Grid container>
|
||||||
<div>
|
<Grid item xs={8}>
|
||||||
<div className="fieldLabel">Distinct Values: </div> <div className="fieldValue">{Number(countDistinct).toLocaleString()}</div>
|
<Box sx={{overflow: "auto", height: "calc( 100vh - 18rem )", position: "relative"}} px={3}>
|
||||||
</div>
|
<DataGridPro
|
||||||
</Box>
|
components={{LoadingOverlay: Loading, Pagination: CustomPagination}}
|
||||||
|
rows={rows}
|
||||||
<Box sx={{overflow: "auto", height: "calc( 100vh - 19rem )", position: "relative"}} px={3}>
|
disableSelectionOnClick
|
||||||
<DataGridPro
|
columns={columns}
|
||||||
components={{LoadingOverlay: Loading, Pagination: CustomPagination}}
|
loading={loading}
|
||||||
rows={rows}
|
rowBuffer={10}
|
||||||
disableSelectionOnClick
|
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
||||||
columns={columns}
|
sortingMode={"server"}
|
||||||
loading={!isLoaded}
|
onSortModelChange={handleSortChange}
|
||||||
rowBuffer={10}
|
sortingOrder={["desc", "asc"]}
|
||||||
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
pagination={true}
|
||||||
initialState={{
|
paginationMode={"server"}
|
||||||
sorting: {
|
initialState={{
|
||||||
sortModel: [
|
sorting: {
|
||||||
{
|
sortModel: [
|
||||||
field: "count",
|
{
|
||||||
sort: "desc",
|
field: "count",
|
||||||
|
sort: "desc",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
}}
|
||||||
},
|
/>
|
||||||
}}
|
</Box>
|
||||||
/>
|
</Grid>
|
||||||
</Box>
|
<Grid item xs={4} sx={{whiteSpace: "nowrap", overflowX: "auto"}}>
|
||||||
|
<Box px={3} fontSize="1rem">
|
||||||
|
{
|
||||||
|
statsFields && statsFields.map((field) =>
|
||||||
|
(
|
||||||
|
<Box key={field.name} pb={1}>
|
||||||
|
<div className="fieldLabel">{field.label}: </div>
|
||||||
|
<div className="fieldValue">{ValueUtils.getValueForDisplay(field, statsRecord?.values.get(field.name), statsRecord?.displayValues.get(field.name))}</div>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
</Box>);
|
</Box>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +57,13 @@ export default class DataGridUtils
|
|||||||
if(!row["id"])
|
if(!row["id"])
|
||||||
{
|
{
|
||||||
row["id"] = record.values.get(tableMetaData.primaryKeyField) ?? row[tableMetaData.primaryKeyField];
|
row["id"] = record.values.get(tableMetaData.primaryKeyField) ?? row[tableMetaData.primaryKeyField];
|
||||||
|
if(row["id"] === null || row["id"] === undefined)
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// DataGrid gets very upset about a null or undefined here, so, try to make it happier //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
row["id"] = "--";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
|
Reference in New Issue
Block a user