@@ -379,7 +379,9 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
+ reloadWidgetCallback={(data) => reloadWidget(i, data)}
+ isChild={areChildren}
+ >
void;
+ showReloadControl: boolean;
isChild?: boolean;
footerHTML?: string;
storeDropdownSelections?: boolean;
@@ -61,6 +64,7 @@ interface Props
Widget.defaultProps = {
isChild: false,
+ showReloadControl: true,
widgetMetaData: {},
widgetData: {},
labelAdditionalComponentsLeft: [],
@@ -68,9 +72,22 @@ Widget.defaultProps = {
};
+interface LabelComponentRenderArgs
+{
+ navigate: NavigateFunction;
+ widgetProps: Props;
+ dropdownData: any[];
+ componentIndex: number;
+ reloadFunction: () => void;
+}
+
+
export class LabelComponent
{
-
+ render = (args: LabelComponentRenderArgs): JSX.Element =>
+ {
+ return (Unsupported component type
)
+ }
}
@@ -86,6 +103,15 @@ export class HeaderLink extends LabelComponent
this.label = label;
this.to = to;
}
+
+ render = (args: LabelComponentRenderArgs): JSX.Element =>
+ {
+ return (
+
+ {this.to ? {this.label} : null}
+
+ );
+ }
}
@@ -97,6 +123,7 @@ export class AddNewRecordButton extends LabelComponent
defaultValues: any;
disabledFields: any;
+
constructor(table: QTableMetaData, defaultValues: any, label: string = "Add new", disabledFields: any = defaultValues)
{
super();
@@ -105,6 +132,45 @@ export class AddNewRecordButton extends LabelComponent
this.defaultValues = defaultValues;
this.disabledFields = disabledFields;
}
+
+ openEditForm = (navigate: any, table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any) =>
+ {
+ navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`)
+ }
+
+ render = (args: LabelComponentRenderArgs): JSX.Element =>
+ {
+ return (
+
+
+
+ );
+ }
+}
+
+
+export class ExportDataButton extends LabelComponent
+{
+ callbackToExport: any;
+ label: string;
+ isDisabled: boolean;
+
+ constructor(callbackToExport: any, isDisabled = false, label: string = "Export")
+ {
+ super();
+ this.callbackToExport = callbackToExport;
+ this.isDisabled = isDisabled;
+ this.label = label;
+ }
+
+ render = (args: LabelComponentRenderArgs): JSX.Element =>
+ {
+ return (
+
+
+
+ );
+ }
}
@@ -121,6 +187,55 @@ export class Dropdown extends LabelComponent
this.options = options;
this.onChangeCallback = onChangeCallback;
}
+
+ render = (args: LabelComponentRenderArgs): JSX.Element =>
+ {
+ let defaultValue = null;
+ const dropdownName = args.widgetProps.widgetData.dropdownNameList[args.componentIndex];
+ const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${args.widgetProps.widgetMetaData.name}.${dropdownName}`;
+ if(args.widgetProps.storeDropdownSelections)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////
+ // see if an existing value is stored in local storage, and if so set it in dropdown //
+ ///////////////////////////////////////////////////////////////////////////////////////
+ defaultValue = JSON.parse(localStorage.getItem(localStorageKey));
+ args.dropdownData[args.componentIndex] = defaultValue?.id;
+ }
+
+ return (
+
+
+
+ );
+ }
+}
+
+
+export class ReloadControl extends LabelComponent
+{
+ callback: () => void;
+
+ constructor(callback: () => void)
+ {
+ super();
+ this.callback = callback;
+ }
+
+ render = (args: LabelComponentRenderArgs): JSX.Element =>
+ {
+ return (
+
+
+
+ );
+ }
}
@@ -132,64 +247,11 @@ function Widget(props: React.PropsWithChildren): JSX.Element
const navigate = useNavigate();
const [dropdownData, setDropdownData] = useState([]);
const [fullScreenWidgetClassName, setFullScreenWidgetClassName] = useState("");
+ const [reloading, setReloading] = useState(false);
- function openEditForm(table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any)
+ function renderComponent(component: LabelComponent, componentIndex: number)
{
- navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`)
- }
-
- function renderComponent(component: LabelComponent, index: number)
- {
- if(component instanceof HeaderLink)
- {
- const link = component as HeaderLink
- return (
-
- {link.to ? {link.label} : null}
-
- );
- }
-
- if (component instanceof AddNewRecordButton)
- {
- const addNewRecordButton = component as AddNewRecordButton
- return (
-
-
-
- );
- }
-
- if (component instanceof Dropdown)
- {
- let defaultValue = null;
- const dropdownName = props.widgetData.dropdownNameList[index];
- const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${props.widgetMetaData.name}.${dropdownName}`;
- if(props.storeDropdownSelections)
- {
- ///////////////////////////////////////////////////////////////////////////////////////
- // see if an existing value is stored in local storage, and if so set it in dropdown //
- ///////////////////////////////////////////////////////////////////////////////////////
- defaultValue = JSON.parse(localStorage.getItem(localStorageKey));
- dropdownData[index] = defaultValue?.id;
- }
-
- const dropdown = component as Dropdown
- return (
-
-
-
- );
- }
-
- return (Unsupported component type.
)
+ return component.render({navigate: navigate, widgetProps: props, dropdownData: dropdownData, componentIndex: componentIndex, reloadFunction: doReload})
}
@@ -209,6 +271,27 @@ function Widget(props: React.PropsWithChildren): JSX.Element
});
}
+ const doReload = () =>
+ {
+ setReloading(true);
+ reloadWidget(dropdownData);
+ }
+
+ useEffect(() =>
+ {
+ setReloading(false);
+ }, [props.widgetData]);
+
+ const effectiveLabelAdditionalComponentsLeft: LabelComponent[] = [];
+ if(props.labelAdditionalComponentsLeft)
+ {
+ props.labelAdditionalComponentsLeft.map((component) => effectiveLabelAdditionalComponentsLeft.push(component));
+ }
+
+ if(props.reloadWidgetCallback && props.widgetData && props.showReloadControl)
+ {
+ effectiveLabelAdditionalComponentsLeft.push(new ReloadControl(doReload))
+ }
function handleDataChange(dropdownLabel: string, changedData: any)
{
@@ -299,7 +382,7 @@ function Widget(props: React.PropsWithChildren): JSX.Element
const hasPermission = props.widgetData?.hasPermission === undefined || props.widgetData?.hasPermission === true;
const widgetContent =
-
+
{
hasPermission ?
@@ -367,7 +450,7 @@ function Widget(props: React.PropsWithChildren): JSX.Element
*/}
{
hasPermission && (
- props.labelAdditionalComponentsLeft.map((component, i) =>
+ effectiveLabelAdditionalComponentsLeft.map((component, i) =>
{
return ({renderComponent(component, i)});
})
@@ -385,6 +468,9 @@ function Widget(props: React.PropsWithChildren): JSX.Element
}
+ {
+ props.widgetMetaData?.isCard && (reloading ? : )
+ }
{
hasPermission && props.widgetData?.dropdownNeedsSelectedText ? (
@@ -407,7 +493,11 @@ function Widget(props: React.PropsWithChildren): JSX.Element
}
;
- return props.widgetMetaData?.isCard ? {widgetContent} : widgetContent;
+ return props.widgetMetaData?.isCard
+ ?
+ {widgetContent}
+
+ : widgetContent;
}
export default Widget;
diff --git a/src/qqq/components/widgets/misc/RecordGridWidget.tsx b/src/qqq/components/widgets/misc/RecordGridWidget.tsx
index 419934b..2daa5fc 100644
--- a/src/qqq/components/widgets/misc/RecordGridWidget.tsx
+++ b/src/qqq/components/widgets/misc/RecordGridWidget.tsx
@@ -123,6 +123,7 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
return (
diff --git a/src/qqq/components/widgets/tables/TableWidget.tsx b/src/qqq/components/widgets/tables/TableWidget.tsx
new file mode 100644
index 0000000..156f4ec
--- /dev/null
+++ b/src/qqq/components/widgets/tables/TableWidget.tsx
@@ -0,0 +1,157 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. 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 .
+ */
+
+
+import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
+// @ts-ignore
+import {htmlToText} from "html-to-text";
+import React, {useEffect, useState} from "react";
+import TableCard from "qqq/components/widgets/tables/TableCard";
+import Widget, {ExportDataButton, WidgetData} from "qqq/components/widgets/Widget";
+import ValueUtils from "qqq/utils/qqq/ValueUtils";
+
+interface Props
+{
+ widgetMetaData?: QWidgetMetaData;
+ widgetData?: WidgetData;
+ reloadWidgetCallback?: (params: string) => void;
+ isChild?: boolean;
+}
+
+TableWidget.defaultProps = {
+ foo: null,
+};
+
+function download(filename: string, text: string)
+{
+ var element = document.createElement("a");
+ element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
+ element.setAttribute("download", filename);
+
+ element.style.display = "none";
+ document.body.appendChild(element);
+
+ element.click();
+
+ document.body.removeChild(element);
+}
+
+function TableWidget(props: Props): JSX.Element
+{
+ const rows = props.widgetData?.rows;
+ const columns = props.widgetData?.columns;
+
+ const exportCallback = () =>
+ {
+ if (props.widgetData && rows && columns)
+ {
+ console.log(props.widgetData);
+
+ let csv = "";
+ for (let j = 0; j < columns.length; j++)
+ {
+ if (j > 0)
+ {
+ csv += ",";
+ }
+ csv += `"${columns[j].header}"`;
+ }
+ csv += "\n";
+
+ for (let i = 0; i < rows.length; i++)
+ {
+ for (let j = 0; j < columns.length; j++)
+ {
+ if (j > 0)
+ {
+ csv += ",";
+ }
+
+ const cell = rows[i][columns[j].accessor];
+ const text = htmlToText(cell,
+ {
+ selectors: [
+ {selector: "a", format: "inline"},
+ {selector: ".MuiIcon-root", format: "skip"},
+ {selector: ".button", format: "skip"}
+ ]
+ });
+ csv += `"${text}"`;
+ }
+ csv += "\n";
+ }
+
+ console.log(csv);
+
+ const fileName = props.widgetData.label + "-" + ValueUtils.formatDateTimeISO8601(new Date()) + ".csv";
+ download(fileName, csv);
+ }
+ else
+ {
+ alert("Error exporting widget data.");
+ }
+ };
+
+
+ const [exportDataButton, setExportDataButton] = useState(new ExportDataButton(() => exportCallback(), true));
+ const [isExportDisabled, setIsExportDisabled] = useState(true);
+ const [componentLeft, setComponentLeft] = useState([exportDataButton])
+
+ useEffect(() =>
+ {
+ if (props.widgetData && columns && rows && rows.length > 0)
+ {
+ console.log("Setting export disabled false")
+ setIsExportDisabled(false);
+ }
+ else
+ {
+ console.log("Setting export disabled true")
+ setIsExportDisabled(true);
+ }
+ }, [props.widgetData])
+
+ useEffect(() =>
+ {
+ console.log("Setting new export button with disabled=" + isExportDisabled)
+ setComponentLeft([new ExportDataButton(() => exportCallback(), isExportDisabled)]);
+ }, [isExportDisabled])
+
+ return (
+ props.reloadWidgetCallback(data)}
+ footerHTML={props.widgetData?.footerHTML}
+ isChild={props.isChild}
+ labelAdditionalComponentsLeft={componentLeft}
+ >
+
+
+ );
+}
+
+export default TableWidget;