Add dropdowns to fieldValueList widget; various cleanups re: widgets

This commit is contained in:
2022-12-14 16:45:33 -06:00
parent a55f87946a
commit e362e7be44
9 changed files with 71 additions and 218 deletions

View File

@ -240,7 +240,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
<SimpleStatisticsCard <SimpleStatisticsCard
title={widgetMetaData.label} title={widgetMetaData.label}
data={widgetData[i]} data={widgetData[i]}
increaseIsGood={true} increaseIsGood={widgetData[i].increaseIsGood}
isCurrency={widgetData[i].isCurrency} isCurrency={widgetData[i].isCurrency}
/> />
) )
@ -320,6 +320,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
<FieldValueListWidget <FieldValueListWidget
title={widgetMetaData.label} title={widgetMetaData.label}
data={widgetData[i]} data={widgetData[i]}
reloadWidgetCallback={(data) => reloadWidget(i, data)}
/> />
) )
} }

View File

@ -61,6 +61,9 @@ function DropdownMenu({label, dropdownOptions, onChangeCallback, sx}: Props): JS
sx={{...sx, cursor: "pointer"}} sx={{...sx, cursor: "pointer"}}
onChange={handleOnChange} onChange={handleOnChange}
renderInput={(params: any) => <TextField {...params} label={label} />} renderInput={(params: any) => <TextField {...params} label={label} />}
renderOption={(props, option: DropdownOption) => (
<li {...props} style={{whiteSpace: "normal"}}>{option.label}</li>
)}
/> />
</span> </span>
) : null ) : null

View File

@ -68,6 +68,7 @@ const options = {
}, },
scales: { scales: {
y: { y: {
beginAtZero: true,
grid: { grid: {
drawBorder: false, drawBorder: false,
display: true, display: true,

View File

@ -33,12 +33,22 @@ interface Props
{ {
title: string; title: string;
data: any; data: any;
reloadWidgetCallback?: (params: string) => void;
} }
FieldValueListWidget.defaultProps = {}; FieldValueListWidget.defaultProps = {};
function FieldValueListWidget({title, data}: Props): JSX.Element function FieldValueListWidget({title, data, reloadWidgetCallback}: Props): JSX.Element
{ {
if(data?.dropdownNeedsSelectedText)
{
return (
<Widget label={title} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
<br />
</Widget>
);
}
if(!data.fields || !data.record) if(!data.fields || !data.record)
{ {
const skeletons = [75, 50, 90]; const skeletons = [75, 50, 90];
@ -69,7 +79,7 @@ function FieldValueListWidget({title, data}: Props): JSX.Element
const fieldIndentLevels = data.fieldIndentLevels ?? {}; const fieldIndentLevels = data.fieldIndentLevels ?? {};
return ( return (
<Widget label={title}> <Widget label={title} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
<Box p={3} pt={0} display="flex" flexDirection="column"> <Box p={3} pt={0} display="flex" flexDirection="column">
{ {
fields.map((field: QFieldMetaData, index: number) => ( fields.map((field: QFieldMetaData, index: number) => (

View File

@ -1,209 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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 Card from "@mui/material/Card";
import Icon from "@mui/material/Icon";
import {ReactNode, useMemo} from "react";
import {Line} from "react-chartjs-2";
import colors from "qqq/components/Temporary/colors";
import MDBox from "qqq/components/Temporary/MDBox";
import MDTypography from "qqq/components/Temporary/MDTypography";
import configs from "qqq/pages/dashboards/Widgets/Configs/LineChartConfigs";
///////////////////////////////////////////
// structure of expected line chart data //
///////////////////////////////////////////
export interface LineChartData
{
labels: string[];
datasets: {
label: string;
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
data: number[];
}[];
};
////////////////////////
// line chart options //
////////////////////////
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
interaction: {
intersect: false,
mode: "index",
},
scales: {
y: {
grid: {
drawBorder: false,
display: true,
drawOnChartArea: true,
drawTicks: false,
borderDash: [5, 5],
color: "rgba(255, 255, 255, .2)",
},
ticks: {
display: true,
color: "#f8f9fa",
padding: 10,
font: {
size: 14,
weight: 300,
family: "Roboto",
style: "normal",
lineHeight: 2,
},
},
},
x: {
grid: {
drawBorder: false,
display: false,
drawOnChartArea: false,
drawTicks: false,
borderDash: [5, 5],
},
ticks: {
display: true,
color: "#f8f9fa",
padding: 10,
font: {
size: 14,
weight: 300,
family: "Roboto",
style: "normal",
lineHeight: 2,
},
},
},
},
};
//////////////////////////////////////////
// define input properties and defaults //
//////////////////////////////////////////
interface Props
{
icon?: {
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
component: ReactNode;
};
title?: string;
description?: string | ReactNode;
height?: string | number;
chart: {
labels: string[];
datasets: {
label: string;
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
data: number[];
}[];
};
[key: string]: any;
}
LineChart.defaultProps = {
icon: {color: "info", component: ""},
title: "",
description: "",
height: "19.125rem",
};
function LineChart({icon, title, description, height, chart}: Props): JSX.Element
{
const chartDatasets = chart.datasets
? chart.datasets.map((dataset) => ({
...dataset,
tension: 0,
pointRadius: 3,
borderWidth: 4,
backgroundColor: "transparent",
fill: true,
pointBackgroundColor: colors[dataset.color]
? colors[dataset.color || "dark"].main
: colors.dark.main,
borderColor: colors[dataset.color]
? colors[dataset.color || "dark"].main
: colors.dark.main,
maxBarThickness: 6,
}))
: [];
const {data} = configs(chart.labels || [], chartDatasets);
const renderChart = (
<MDBox py={2} pr={2} pl={icon.component ? 1 : 2}>
{title || description ? (
<MDBox display="flex" px={description ? 1 : 0} pt={description ? 1 : 0}>
{icon.component && (
<MDBox
width="4rem"
height="4rem"
bgColor={icon.color || "info"}
variant="gradient"
coloredShadow={icon.color || "info"}
borderRadius="xl"
display="flex"
justifyContent="center"
alignItems="center"
color="white"
mt={-5}
mr={2}
>
<Icon fontSize="medium">{icon.component}</Icon>
</MDBox>
)}
<MDBox mt={icon.component ? -2 : 0}>
{title && <MDTypography variant="h5">{title}</MDTypography>}
<MDBox mb={2}>
<MDTypography component="div" variant="button" color="text">
{description}
</MDTypography>
</MDBox>
</MDBox>
</MDBox>
) : null}
{useMemo(
() => (
<MDBox height={height}>
<Line data={data} options={options} />
</MDBox>
),
[chart, height]
)}
</MDBox>
);
return title || description ? <Card>{renderChart}</Card> : renderChart;
}
export default LineChart;

View File

@ -66,9 +66,7 @@ function ParentWidget({widgetIndex, label, data, reloadWidgetCallback, entityPri
{ {
const [childUrlParams, setChildUrlParams] = useState(""); const [childUrlParams, setChildUrlParams] = useState("");
const [qInstance, setQInstance] = useState(null as QInstance); const [qInstance, setQInstance] = useState(null as QInstance);
const [dropdownData, setDropdownData] = useState([]);
const [widgets, setWidgets] = useState([] as any[]); const [widgets, setWidgets] = useState([] as any[]);
const [counter, setCounter] = useState(0);
useEffect(() => useEffect(() =>
{ {

View File

@ -21,9 +21,11 @@
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {DataGridPro} 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 DataGridUtils from "qqq/utils/DataGridUtils"; import DataGridUtils from "qqq/utils/DataGridUtils";
import QClient from "qqq/utils/QClient";
import Widget, {AddNewRecordButton, HeaderLink, LabelComponent} from "./Widget"; import Widget, {AddNewRecordButton, HeaderLink, LabelComponent} from "./Widget";
interface Props interface Props
@ -34,10 +36,13 @@ interface Props
RecordGridWidget.defaultProps = {}; RecordGridWidget.defaultProps = {};
const qController = QClient.getInstance();
function RecordGridWidget({title, data}: Props): JSX.Element function RecordGridWidget({title, data}: Props): JSX.Element
{ {
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [columns, setColumns] = useState([]); const [columns, setColumns] = useState([]);
const navigate = useNavigate();
useEffect(() => useEffect(() =>
{ {
@ -59,6 +64,21 @@ function RecordGridWidget({title, data}: Props): JSX.Element
const childTablePath = data.tablePath + (data.tablePath.endsWith("/") ? "" : "/") const childTablePath = data.tablePath + (data.tablePath.endsWith("/") ? "" : "/")
const columns = DataGridUtils.setupGridColumns(tableMetaData, columnsToRender, childTablePath); const columns = DataGridUtils.setupGridColumns(tableMetaData, columnsToRender, childTablePath);
////////////////////////////////////////////////////////////////
// do not not show the foreign-key column of the parent table //
////////////////////////////////////////////////////////////////
if(data.defaultValuesForNewChildRecords)
{
for (let i = 0; i < columns.length; i++)
{
if(data.defaultValuesForNewChildRecords[columns[i].field])
{
columns.splice(i, 1);
i--
}
}
}
setRows(rows); setRows(rows);
setColumns(columns); setColumns(columns);
} }
@ -81,6 +101,21 @@ function RecordGridWidget({title, data}: Props): JSX.Element
labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, data.defaultValuesForNewChildRecords, "Add new", disabledFields)) labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, data.defaultValuesForNewChildRecords, "Add new", disabledFields))
} }
const handleRowClick = (params: GridRowParams, event: MuiEvent<React.MouseEvent>, details: GridCallbackDetails) =>
{
(async () =>
{
const qInstance = await qController.loadMetaData()
const tablePath = qInstance.getTablePathByName(data.childTableMetaData.name)
if(tablePath)
{
navigate(`${tablePath}/${params.id}`);
}
})();
};
return ( return (
<Widget <Widget
label={title} label={title}
@ -94,6 +129,7 @@ function RecordGridWidget({title, data}: Props): JSX.Element
columns={columns} columns={columns}
rowBuffer={10} rowBuffer={10}
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")} getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
onRowClick={handleRowClick}
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells... // getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
// components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}} // components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}}
// pinnedColumns={pinnedColumns} // pinnedColumns={pinnedColumns}
@ -106,7 +142,6 @@ function RecordGridWidget({title, data}: Props): JSX.Element
// checkboxSelection // checkboxSelection
// rowCount={totalRecords === null ? 0 : totalRecords} // rowCount={totalRecords === null ? 0 : totalRecords}
// onPageSizeChange={handleRowsPerPageChange} // onPageSizeChange={handleRowsPerPageChange}
// onRowClick={handleRowClick}
// onStateChange={handleStateChange} // onStateChange={handleStateChange}
// density={density} // density={density}
// loading={loading} // loading={loading}

View File

@ -155,7 +155,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
{ {
const dropdown = component as Dropdown const dropdown = component as Dropdown
return ( return (
<Box my={3} mr={2} sx={{float: "right"}}> <Box my={2} mr={2} sx={{float: "right"}}>
<DropdownMenu <DropdownMenu
sx={{width: 200, marginLeft: "15px"}} sx={{width: 200, marginLeft: "15px"}}
label={`Select ${dropdown.label}`} label={`Select ${dropdown.label}`}

View File

@ -204,6 +204,20 @@ function EntityView({table, launchProcess}: Props): JSX.Element
return; return;
} }
} }
///////////////////////////////////////////////////////////////////////////////////
// look for anchor links - e.g., table section names. return w/ no-op if found. //
///////////////////////////////////////////////////////////////////////////////////
if(tableSections)
{
for (let i = 0; i < tableSections.length; i++)
{
if("#" + tableSections[i].name === location.hash)
{
return;
}
}
}
} }
catch (e) catch (e)
{ {
@ -564,7 +578,7 @@ function EntityView({table, launchProcess}: Props): JSX.Element
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={12} mb={3}> <Grid item xs={12} mb={3}>
<Card id={t1SectionName}> <Card id={t1SectionName} sx={{scrollMarginTop: "100px"}}>
<MDBox display="flex" p={3} pb={1}> <MDBox display="flex" p={3} pb={1}>
<MDBox mr={1.5}> <MDBox mr={1.5}>
<Avatar sx={{bgcolor: colors.info.main}}> <Avatar sx={{bgcolor: colors.info.main}}>