mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
CE-1115 - Initial working version
This commit is contained in:
289
src/qqq/components/widgets/misc/ReportSetupWidget.tsx
Normal file
289
src/qqq/components/widgets/misc/ReportSetupWidget.tsx
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||||
|
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
|
import {Alert, Collapse} from "@mui/material";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Card from "@mui/material/Card";
|
||||||
|
import Link from "@mui/material/Link";
|
||||||
|
import Modal from "@mui/material/Modal";
|
||||||
|
import QContext from "QContext";
|
||||||
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
|
import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
|
||||||
|
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||||
|
import AdvancedQueryPreview from "qqq/components/query/AdvancedQueryPreview";
|
||||||
|
import Widget, {HeaderLinkButton, LabelComponent} from "qqq/components/widgets/Widget";
|
||||||
|
import QQueryColumns, {Column} from "qqq/models/query/QQueryColumns";
|
||||||
|
import RecordQuery from "qqq/pages/records/query/RecordQuery";
|
||||||
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
||||||
|
import React, {useContext, useEffect, useRef, useState} from "react";
|
||||||
|
|
||||||
|
interface ReportSetupWidgetProps
|
||||||
|
{
|
||||||
|
isEditable: boolean;
|
||||||
|
widgetMetaData: QWidgetMetaData;
|
||||||
|
recordValues: {[name: string]: any};
|
||||||
|
onSaveCallback?: (values: {[name: string]: any}) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportSetupWidget.defaultProps = {
|
||||||
|
onSaveCallback: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const qController = Client.getInstance();
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Component for editing the main setup of a report - that is: filter & columns
|
||||||
|
*******************************************************************************/
|
||||||
|
export default function ReportSetupWidget({isEditable, widgetMetaData, recordValues, onSaveCallback}: ReportSetupWidgetProps): JSX.Element
|
||||||
|
{
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
|
||||||
|
|
||||||
|
const [alertContent, setAlertContent] = useState(null as string);
|
||||||
|
|
||||||
|
const recordQueryRef = useRef();
|
||||||
|
|
||||||
|
|
||||||
|
let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter;
|
||||||
|
if(!queryFilter)
|
||||||
|
{
|
||||||
|
queryFilter = new QQueryFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
let columns = recordValues["columnsJson"] && JSON.parse(recordValues["columnsJson"]) as QQueryColumns;
|
||||||
|
if(!columns)
|
||||||
|
{
|
||||||
|
columns = new QQueryColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if (recordValues["tableName"] && (tableMetaData == null || tableMetaData.name != recordValues["tableName"]))
|
||||||
|
{
|
||||||
|
(async () =>
|
||||||
|
{
|
||||||
|
const tableMetaData = await qController.loadTableMetaData(recordValues["tableName"])
|
||||||
|
setTableMetaData(tableMetaData);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}, [recordValues]);
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function openEditor()
|
||||||
|
{
|
||||||
|
if(recordValues["tableName"])
|
||||||
|
{
|
||||||
|
setModalOpen(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setAlertContent("You must select a table before you can edit filters and columns")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function saveClicked()
|
||||||
|
{
|
||||||
|
if(!onSaveCallback)
|
||||||
|
{
|
||||||
|
console.log("onSaveCallback was not defined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore possibly 'undefined'.
|
||||||
|
const view = recordQueryRef?.current?.getCurrentView();
|
||||||
|
onSaveCallback({queryFilterJson: JSON.stringify(view.queryFilter), columnsJson: JSON.stringify(view.queryColumns)});
|
||||||
|
|
||||||
|
closeEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function closeEditor(event?: {}, reason?: "backdropClick" | "escapeKeyDown")
|
||||||
|
{
|
||||||
|
if(reason == "backdropClick" || reason == "escapeKeyDown")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setModalOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelAdditionalComponentsRight: LabelComponent[] = []
|
||||||
|
if(isEditable)
|
||||||
|
{
|
||||||
|
labelAdditionalComponentsRight.push(new HeaderLinkButton("Edit Filters and Columns", openEditor))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function renderColumn(column: Column): JSX.Element
|
||||||
|
{
|
||||||
|
const [field, table] = FilterUtils.getField(tableMetaData, column.name)
|
||||||
|
|
||||||
|
if(!column || !column.isVisible || column.name == "__check__" || !field)
|
||||||
|
{
|
||||||
|
return (<React.Fragment />);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableLabelPart = table.name != tableMetaData.name ? table.label + ": " : "";
|
||||||
|
|
||||||
|
return (<Box mr="0.375rem" mb="0.5rem" border={`1px solid ${colors.grayLines.main}`} borderRadius="0.75rem" p="0.25rem 0.75rem">
|
||||||
|
{tableLabelPart}{field.label}
|
||||||
|
</Box>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function mayShowQueryPreview(): boolean
|
||||||
|
{
|
||||||
|
if(tableMetaData)
|
||||||
|
{
|
||||||
|
if(queryFilter?.criteria?.length > 0 || queryFilter?.subFilters?.length > 0)
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function mayShowColumnsPreview(): boolean
|
||||||
|
{
|
||||||
|
if(tableMetaData)
|
||||||
|
{
|
||||||
|
if(columns?.columns?.length > 0)
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// load help text //
|
||||||
|
////////////////////
|
||||||
|
const helpRoles = ["ALL_SCREENS"]
|
||||||
|
const key = "slot:reportSetupSubheader"; // todo - ??
|
||||||
|
const {helpHelpActive} = useContext(QContext);
|
||||||
|
const showHelp = helpHelpActive || hasHelpContent(widgetMetaData?.helpContent?.get(key), helpRoles);
|
||||||
|
const formattedHelpContent = <HelpContent helpContents={widgetMetaData?.helpContent?.get(key)} roles={helpRoles} helpContentKey={key} />;
|
||||||
|
// const formattedHelpContent = "Add and edit filter and columns for your report."
|
||||||
|
|
||||||
|
return (<Widget widgetMetaData={widgetMetaData} labelAdditionalComponentsRight={labelAdditionalComponentsRight}>
|
||||||
|
<React.Fragment>
|
||||||
|
<Collapse in={Boolean(alertContent)}>
|
||||||
|
<Alert severity="error" sx={{mt: 1.5, mb: 0.5}} onClose={() => setAlertContent(null)}>{alertContent}</Alert>
|
||||||
|
</Collapse>
|
||||||
|
<Box pt="0.5rem">
|
||||||
|
<h5>Query Filter</h5>
|
||||||
|
{
|
||||||
|
mayShowQueryPreview() &&
|
||||||
|
<AdvancedQueryPreview tableMetaData={tableMetaData} queryFilter={queryFilter} isEditable={false} isQueryTooComplex={queryFilter.subFilters?.length > 0} removeCriteriaByIndexCallback={null} />
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!mayShowQueryPreview() &&
|
||||||
|
<Box width="100%" sx={{fontSize: "1rem", background: "#FFFFFF"}} minHeight={"2.5rem"} p={"0.5rem"} pb={"0.125rem"} borderRadius="0.75rem" border={`1px solid ${colors.grayLines.main}`}>
|
||||||
|
{
|
||||||
|
isEditable && <Link sx={{cursor: "pointer"}} onClick={openEditor} color={colors.gray.main}>+ Add Filters</Link>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isEditable && <Box color={colors.gray.main}>Your report has no filters.</Box>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
<Box pt="1rem">
|
||||||
|
<h5>Columns</h5>
|
||||||
|
<Box display="flex" flexWrap="wrap" fontSize="1rem">
|
||||||
|
{
|
||||||
|
mayShowColumnsPreview() &&
|
||||||
|
columns.columns.map((column, i) => <React.Fragment key={i}>{renderColumn(column)}</React.Fragment>)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!mayShowColumnsPreview() &&
|
||||||
|
<Box width="100%" sx={{fontSize: "1rem", background: "#FFFFFF"}} minHeight={"2.375rem"} p={"0.5rem"} pb={"0.125rem"}>
|
||||||
|
{
|
||||||
|
isEditable && <Link sx={{cursor: "pointer"}} onClick={openEditor} color={colors.gray.main}>+ Add Columns</Link>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isEditable && <Box color={colors.gray.main}>Your report has no filters.</Box>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{
|
||||||
|
modalOpen &&
|
||||||
|
<Modal open={modalOpen} onClose={(event, reason) => closeEditor(event, reason)}>
|
||||||
|
<div>
|
||||||
|
<Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%"}}>
|
||||||
|
<Card sx={{m: "2rem", p: "2rem"}}>
|
||||||
|
<h3>Edit Filters and Columns</h3>
|
||||||
|
{
|
||||||
|
showHelp &&
|
||||||
|
<Box color={colors.gray.main} pb={"0.5rem"}>
|
||||||
|
{formattedHelpContent}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
tableMetaData && <RecordQuery
|
||||||
|
ref={recordQueryRef}
|
||||||
|
table={tableMetaData}
|
||||||
|
usage="reportSetup"
|
||||||
|
isModal={true} />
|
||||||
|
}
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Box display="flex" justifyContent="flex-end">
|
||||||
|
<QCancelButton disabled={false} onClickHandler={closeEditor} />
|
||||||
|
<QSaveButton label="OK" iconName="check" disabled={false} onClickHandler={saveClicked} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
</Widget>);
|
||||||
|
}
|
Reference in New Issue
Block a user