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
title={widgetMetaData.label}
data={widgetData[i]}
increaseIsGood={true}
increaseIsGood={widgetData[i].increaseIsGood}
isCurrency={widgetData[i].isCurrency}
/>
)
@ -320,6 +320,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
<FieldValueListWidget
title={widgetMetaData.label}
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"}}
onChange={handleOnChange}
renderInput={(params: any) => <TextField {...params} label={label} />}
renderOption={(props, option: DropdownOption) => (
<li {...props} style={{whiteSpace: "normal"}}>{option.label}</li>
)}
/>
</span>
) : null

View File

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

View File

@ -33,12 +33,22 @@ interface Props
{
title: string;
data: any;
reloadWidgetCallback?: (params: string) => void;
}
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)
{
const skeletons = [75, 50, 90];
@ -69,7 +79,7 @@ function FieldValueListWidget({title, data}: Props): JSX.Element
const fieldIndentLevels = data.fieldIndentLevels ?? {};
return (
<Widget label={title}>
<Widget label={title} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
<Box p={3} pt={0} display="flex" flexDirection="column">
{
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 [qInstance, setQInstance] = useState(null as QInstance);
const [dropdownData, setDropdownData] = useState([]);
const [widgets, setWidgets] = useState([] as any[]);
const [counter, setCounter] = useState(0);
useEffect(() =>
{

View File

@ -21,9 +21,11 @@
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
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 {useNavigate} from "react-router-dom";
import DataGridUtils from "qqq/utils/DataGridUtils";
import QClient from "qqq/utils/QClient";
import Widget, {AddNewRecordButton, HeaderLink, LabelComponent} from "./Widget";
interface Props
@ -34,10 +36,13 @@ interface Props
RecordGridWidget.defaultProps = {};
const qController = QClient.getInstance();
function RecordGridWidget({title, data}: Props): JSX.Element
{
const [rows, setRows] = useState([]);
const [columns, setColumns] = useState([]);
const navigate = useNavigate();
useEffect(() =>
{
@ -59,6 +64,21 @@ function RecordGridWidget({title, data}: Props): JSX.Element
const childTablePath = data.tablePath + (data.tablePath.endsWith("/") ? "" : "/")
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);
setColumns(columns);
}
@ -81,6 +101,21 @@ function RecordGridWidget({title, data}: Props): JSX.Element
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 (
<Widget
label={title}
@ -94,6 +129,7 @@ function RecordGridWidget({title, data}: Props): JSX.Element
columns={columns}
rowBuffer={10}
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
onRowClick={handleRowClick}
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
// components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}}
// pinnedColumns={pinnedColumns}
@ -106,7 +142,6 @@ function RecordGridWidget({title, data}: Props): JSX.Element
// checkboxSelection
// rowCount={totalRecords === null ? 0 : totalRecords}
// onPageSizeChange={handleRowsPerPageChange}
// onRowClick={handleRowClick}
// onStateChange={handleStateChange}
// density={density}
// loading={loading}

View File

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

View File

@ -204,6 +204,20 @@ function EntityView({table, launchProcess}: Props): JSX.Element
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)
{
@ -564,7 +578,7 @@ function EntityView({table, launchProcess}: Props): JSX.Element
<Grid container spacing={3}>
<Grid item xs={12} mb={3}>
<Card id={t1SectionName}>
<Card id={t1SectionName} sx={{scrollMarginTop: "100px"}}>
<MDBox display="flex" p={3} pb={1}>
<MDBox mr={1.5}>
<Avatar sx={{bgcolor: colors.info.main}}>