Redo export buttons as JSX Elements that get passed into Widget.tsx, rather than "Component" objects. something to do with when things are getting bound, was making the export buttons never have data. This was done with labelAdditionalElementsLeft, similar to labelAdditionalComponentsLeft. In theory, maybe, this is better, and we should remove all the additionalComponents left & right...

This commit is contained in:
2023-10-17 19:23:02 -05:00
parent b968705a01
commit 791b50b893
3 changed files with 105 additions and 104 deletions

View File

@ -30,7 +30,7 @@ import Tooltip from "@mui/material/Tooltip/Tooltip";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import parse from "html-react-parser"; import parse from "html-react-parser";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {Link, NavigateFunction, useNavigate} from "react-router-dom"; import {NavigateFunction, useNavigate} from "react-router-dom";
import colors from "qqq/components/legacy/colors"; import colors from "qqq/components/legacy/colors";
import DropdownMenu, {DropdownOption} from "qqq/components/widgets/components/DropdownMenu"; import DropdownMenu, {DropdownOption} from "qqq/components/widgets/components/DropdownMenu";
@ -46,6 +46,7 @@ export interface WidgetData
dropdownNeedsSelectedText?: string; dropdownNeedsSelectedText?: string;
hasPermission?: boolean; hasPermission?: boolean;
errorLoading?: boolean; errorLoading?: boolean;
[other: string]: any; [other: string]: any;
} }
@ -53,6 +54,7 @@ export interface WidgetData
interface Props interface Props
{ {
labelAdditionalComponentsLeft: LabelComponent[]; labelAdditionalComponentsLeft: LabelComponent[];
labelAdditionalElementsLeft: JSX.Element[];
labelAdditionalComponentsRight: LabelComponent[]; labelAdditionalComponentsRight: LabelComponent[];
widgetMetaData?: QWidgetMetaData; widgetMetaData?: QWidgetMetaData;
widgetData?: WidgetData; widgetData?: WidgetData;
@ -70,6 +72,7 @@ Widget.defaultProps = {
widgetMetaData: {}, widgetMetaData: {},
widgetData: {}, widgetData: {},
labelAdditionalComponentsLeft: [], labelAdditionalComponentsLeft: [],
labelAdditionalElementsLeft: [],
labelAdditionalComponentsRight: [], labelAdditionalComponentsRight: [],
}; };
@ -88,34 +91,8 @@ export class LabelComponent
{ {
render = (args: LabelComponentRenderArgs): JSX.Element => render = (args: LabelComponentRenderArgs): JSX.Element =>
{ {
return (<div>Unsupported component type</div>) return (<div>Unsupported component type</div>);
} };
}
/*******************************************************************************
**
*******************************************************************************/
export class HeaderLink extends LabelComponent
{
label: string;
to: string
constructor(label: string, to: string)
{
super();
this.label = label;
this.to = to;
}
render = (args: LabelComponentRenderArgs): JSX.Element =>
{
return (
<Typography variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative" top="-0.25rem">
{this.to ? <Link to={this.to}>{this.label}</Link> : null}
</Typography>
);
}
} }
@ -141,8 +118,8 @@ export class AddNewRecordButton extends LabelComponent
openEditForm = (navigate: any, table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any) => openEditForm = (navigate: any, table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any) =>
{ {
navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`) navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`);
} };
render = (args: LabelComponentRenderArgs): JSX.Element => render = (args: LabelComponentRenderArgs): JSX.Element =>
{ {
@ -151,35 +128,7 @@ export class AddNewRecordButton extends LabelComponent
<Button sx={{mt: 0.75}} onClick={() => this.openEditForm(args.navigate, this.table, null, this.defaultValues, this.disabledFields)}>{this.label}</Button> <Button sx={{mt: 0.75}} onClick={() => this.openEditForm(args.navigate, this.table, null, this.defaultValues, this.disabledFields)}>{this.label}</Button>
</Typography> </Typography>
); );
} };
}
/*******************************************************************************
**
*******************************************************************************/
export class ExportDataButton extends LabelComponent
{
callbackToExport: any;
tooltipTitle: string;
isDisabled: boolean;
constructor(callbackToExport: any, isDisabled = false, tooltipTitle: string = "Export")
{
super();
this.callbackToExport = callbackToExport;
this.isDisabled = isDisabled;
this.tooltipTitle = tooltipTitle;
}
render = (args: LabelComponentRenderArgs): JSX.Element =>
{
return (
<Typography variant="body2" py={2} px={0} display="inline" position="relative" top="-0.375rem">
<Tooltip title={this.tooltipTitle}><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={() => this.callbackToExport()} disabled={this.isDisabled}><Icon>save_alt</Icon></Button></Tooltip>
</Typography>
);
}
} }
@ -227,7 +176,7 @@ export class Dropdown extends LabelComponent
/> />
</Box> </Box>
); );
} };
} }
@ -251,7 +200,7 @@ export class ReloadControl extends LabelComponent
<Tooltip title="Refresh"><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={() => this.callback()}><Icon>refresh</Icon></Button></Tooltip> <Tooltip title="Refresh"><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={() => this.callback()}><Icon>refresh</Icon></Button></Tooltip>
</Typography> </Typography>
); );
} };
} }
@ -372,7 +321,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
if (index < 0) if (index < 0)
{ {
throw(`Could not find table name for label ${tableName}`); throw (`Could not find table name for label ${tableName}`);
} }
dropdownData[index] = (changedData) ? changedData.id : null; dropdownData[index] = (changedData) ? changedData.id : null;
@ -394,7 +343,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
} }
} }
reloadWidget(dropdownData) reloadWidget(dropdownData);
} }
} }
@ -422,7 +371,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
{ {
console.log(`No reload widget callback in ${props.widgetMetaData.label}`); console.log(`No reload widget callback in ${props.widgetMetaData.label}`);
} }
} };
const toggleFullScreenWidget = () => const toggleFullScreenWidget = () =>
{ {
@ -434,14 +383,14 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
{ {
setFullScreenWidgetClassName("fullScreenWidget"); setFullScreenWidgetClassName("fullScreenWidget");
} }
} };
const hasPermission = props.widgetData?.hasPermission === undefined || props.widgetData?.hasPermission === true; const hasPermission = props.widgetData?.hasPermission === undefined || props.widgetData?.hasPermission === true;
const isSet = (v: any): boolean => const isSet = (v: any): boolean =>
{ {
return (v !== null && v !== undefined); return (v !== null && v !== undefined);
} };
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// to avoid taking up the space of the Box with the label and icon and label-components (since it has a height), only output that box if we need any of the components // // to avoid taking up the space of the Box with the label and icon and label-components (since it has a height), only output that box if we need any of the components //
@ -450,6 +399,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
if (hasPermission) if (hasPermission)
{ {
needLabelBox ||= (labelComponentsLeft && labelComponentsLeft.length > 0); needLabelBox ||= (labelComponentsLeft && labelComponentsLeft.length > 0);
needLabelBox ||= (props.labelAdditionalElementsLeft && props.labelAdditionalElementsLeft.length > 0);
needLabelBox ||= (labelComponentsRight && labelComponentsRight.length > 0); needLabelBox ||= (labelComponentsRight && labelComponentsRight.length > 0);
needLabelBox ||= isSet(props.widgetMetaData?.icon); needLabelBox ||= isSet(props.widgetMetaData?.icon);
needLabelBox ||= isSet(props.widgetData?.label); needLabelBox ||= isSet(props.widgetData?.label);
@ -530,6 +480,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
}) })
) )
} }
{props.labelAdditionalElementsLeft}
</Box> </Box>
<Box> <Box>
{ {

View File

@ -22,10 +22,14 @@
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import Button from "@mui/material/Button";
import Icon from "@mui/material/Icon";
import Tooltip from "@mui/material/Tooltip/Tooltip";
import Typography from "@mui/material/Typography";
import {DataGridPro, GridCallbackDetails, GridRowParams, MuiEvent} from "@mui/x-data-grid-pro"; import {DataGridPro, GridCallbackDetails, GridRowParams, MuiEvent} from "@mui/x-data-grid-pro";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {useNavigate} from "react-router-dom"; import {useNavigate, Link} from "react-router-dom";
import Widget, {AddNewRecordButton, ExportDataButton, HeaderLink, LabelComponent} from "qqq/components/widgets/Widget"; import Widget, {AddNewRecordButton, LabelComponent} from "qqq/components/widgets/Widget";
import DataGridUtils from "qqq/utils/DataGridUtils"; import DataGridUtils from "qqq/utils/DataGridUtils";
import HtmlUtils from "qqq/utils/HtmlUtils"; import HtmlUtils from "qqq/utils/HtmlUtils";
import Client from "qqq/utils/qqq/Client"; import Client from "qqq/utils/qqq/Client";
@ -47,6 +51,8 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
const [records, setRecords] = useState([] as QRecord[]) const [records, setRecords] = useState([] as QRecord[])
const [columns, setColumns] = useState([]); const [columns, setColumns] = useState([]);
const [allColumns, setAllColumns] = useState([]) const [allColumns, setAllColumns] = useState([])
const [csv, setCsv] = useState(null as string);
const [fileName, setFileName] = useState(null as string);
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => useEffect(() =>
@ -75,6 +81,7 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// capture all-columns to use for the export (before we might splice some away from the on-screen display) // // capture all-columns to use for the export (before we might splice some away from the on-screen display) //
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
const allColumns = [... columns];
setAllColumns(JSON.parse(JSON.stringify(columns))); setAllColumns(JSON.parse(JSON.stringify(columns)));
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
@ -95,39 +102,42 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
setRows(rows); setRows(rows);
setRecords(records) setRecords(records)
setColumns(columns); setColumns(columns);
}
}, [data]);
const exportCallback = () => let csv = "";
{ for (let i = 0; i < allColumns.length; i++)
let csv = "";
for (let i = 0; i < allColumns.length; i++)
{
csv += `${i > 0 ? "," : ""}"${ValueUtils.cleanForCsv(allColumns[i].headerName)}"`
}
csv += "\n";
for (let i = 0; i < records.length; i++)
{
for (let j = 0; j < allColumns.length; j++)
{ {
const value = records[i].displayValues.get(allColumns[j].field) ?? records[i].values.get(allColumns[j].field) csv += `${i > 0 ? "," : ""}"${ValueUtils.cleanForCsv(allColumns[i].headerName)}"`
csv += `${j > 0 ? "," : ""}"${ValueUtils.cleanForCsv(value)}"`
} }
csv += "\n"; csv += "\n";
}
const fileName = (data?.label ?? widgetMetaData.label) + " " + ValueUtils.formatDateTimeForFileName(new Date()) + ".csv"; for (let i = 0; i < records.length; i++)
HtmlUtils.download(fileName, csv); {
} for (let j = 0; j < allColumns.length; j++)
{
const value = records[i].displayValues.get(allColumns[j].field) ?? records[i].values.get(allColumns[j].field)
csv += `${j > 0 ? "," : ""}"${ValueUtils.cleanForCsv(value)}"`
}
csv += "\n";
}
const fileName = (data?.label ?? widgetMetaData.label) + " " + ValueUtils.formatDateTimeForFileName(new Date()) + ".csv";
setCsv(csv);
setFileName(fileName);
}
}, [data]);
/////////////////// ///////////////////
// view all link // // view all link //
/////////////////// ///////////////////
const labelAdditionalComponentsLeft: LabelComponent[] = [] const labelAdditionalElementsLeft: JSX.Element[] = [];
if(data && data.viewAllLink) if(data && data.viewAllLink)
{ {
labelAdditionalComponentsLeft.push(new HeaderLink("View All", data.viewAllLink)); labelAdditionalElementsLeft.push(
<Typography variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative" top="-0.25rem">
<Link to={data.viewAllLink}>View All</Link>
</Typography>
)
} }
/////////////////// ///////////////////
@ -149,7 +159,26 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
} }
} }
labelAdditionalComponentsLeft.push(new ExportDataButton(() => exportCallback(), isExportDisabled, tooltipTitle)) const onExportClick = () =>
{
if(csv)
{
HtmlUtils.download(fileName, csv);
}
else
{
alert("There is no data available to export.")
}
}
if(widgetMetaData?.showExportButton)
{
labelAdditionalElementsLeft.push(
<Typography key={1} variant="body2" py={2} px={0} display="inline" position="relative" top="-0.375rem">
<Tooltip title={tooltipTitle}><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={onExportClick} disabled={isExportDisabled}><Icon>save_alt</Icon></Button></Tooltip>
</Typography>
);
}
//////////////////// ////////////////////
// add new button // // add new button //
@ -184,7 +213,7 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
<Widget <Widget
widgetMetaData={widgetMetaData} widgetMetaData={widgetMetaData}
widgetData={data} widgetData={data}
labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} labelAdditionalElementsLeft={labelAdditionalElementsLeft}
labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsRight={labelAdditionalComponentsRight}
> >
<DataGridPro <DataGridPro

View File

@ -21,11 +21,15 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import Button from "@mui/material/Button";
import Icon from "@mui/material/Icon";
import Tooltip from "@mui/material/Tooltip/Tooltip";
import Typography from "@mui/material/Typography";
// @ts-ignore // @ts-ignore
import {htmlToText} from "html-to-text"; import {htmlToText} from "html-to-text";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import TableCard from "qqq/components/widgets/tables/TableCard"; import TableCard from "qqq/components/widgets/tables/TableCard";
import Widget, {ExportDataButton, WidgetData} from "qqq/components/widgets/Widget"; import Widget, {WidgetData} from "qqq/components/widgets/Widget";
import HtmlUtils from "qqq/utils/HtmlUtils"; import HtmlUtils from "qqq/utils/HtmlUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils";
@ -43,6 +47,8 @@ TableWidget.defaultProps = {
function TableWidget(props: Props): JSX.Element function TableWidget(props: Props): JSX.Element
{ {
const [isExportDisabled, setIsExportDisabled] = useState(false); // hmm, would like true here, but it broke... const [isExportDisabled, setIsExportDisabled] = useState(false); // hmm, would like true here, but it broke...
const [csv, setCsv] = useState(null as string);
const [fileName, setFileName] = useState(null as string);
const rows = props.widgetData?.rows; const rows = props.widgetData?.rows;
const columns = props.widgetData?.columns; const columns = props.widgetData?.columns;
@ -56,14 +62,8 @@ function TableWidget(props: Props): JSX.Element
} }
setIsExportDisabled(isExportDisabled); setIsExportDisabled(isExportDisabled);
}, [props.widgetMetaData, props.widgetData]);
const exportCallback = () =>
{
if (props.widgetData && rows && columns) if (props.widgetData && rows && columns)
{ {
console.log(props.widgetData);
let csv = ""; let csv = "";
for (let j = 0; j < columns.length; j++) for (let j = 0; j < columns.length; j++)
{ {
@ -98,16 +98,37 @@ function TableWidget(props: Props): JSX.Element
csv += "\n"; csv += "\n";
} }
console.log(csv); setCsv(csv);
const fileName = (props.widgetData.label ?? props.widgetMetaData.label) + " " + ValueUtils.formatDateTimeForFileName(new Date()) + ".csv"; const fileName = (props.widgetData.label ?? props.widgetMetaData.label) + " " + ValueUtils.formatDateTimeForFileName(new Date()) + ".csv";
setFileName(fileName)
console.log(`useEffect, setting fileName ${fileName}`);
}
}, [props.widgetMetaData, props.widgetData]);
const onExportClick = () =>
{
if(csv)
{
HtmlUtils.download(fileName, csv); HtmlUtils.download(fileName, csv);
} }
else else
{ {
alert("There is no data available to export."); alert("There is no data available to export.")
} }
}; }
const labelAdditionalElementsLeft: JSX.Element[] = [];
if(props.widgetMetaData?.showExportButton)
{
labelAdditionalElementsLeft.push(
<Typography key={1} variant="body2" py={2} px={0} display="inline" position="relative" top="-0.375rem">
<Tooltip title="Export"><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={onExportClick} disabled={false}><Icon>save_alt</Icon></Button></Tooltip>
</Typography>
);
}
return ( return (
<Widget <Widget
@ -116,7 +137,7 @@ function TableWidget(props: Props): JSX.Element
reloadWidgetCallback={(data) => props.reloadWidgetCallback(data)} reloadWidgetCallback={(data) => props.reloadWidgetCallback(data)}
footerHTML={props.widgetData?.footerHTML} footerHTML={props.widgetData?.footerHTML}
isChild={props.isChild} isChild={props.isChild}
labelAdditionalComponentsLeft={props.widgetMetaData?.showExportButton ? [new ExportDataButton(() => exportCallback(), isExportDisabled)] : []} labelAdditionalElementsLeft={labelAdditionalElementsLeft}
> >
<TableCard <TableCard
noRowsFoundHTML={props.widgetData?.noRowsFoundHTML} noRowsFoundHTML={props.widgetData?.noRowsFoundHTML}