Compare commits

...

8 Commits

5 changed files with 486 additions and 356 deletions

View File

@ -36,7 +36,7 @@ import Icon from "@mui/material/Icon";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import {makeStyles} from "@mui/styles"; import {makeStyles} from "@mui/styles";
import {Command} from "cmdk"; import {Command} from "cmdk";
import React, {useContext, useEffect, useRef} from "react"; import React, {useContext, useEffect, useRef, useState} from "react";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
import QContext from "QContext"; import QContext from "QContext";
import HistoryUtils, {QHistoryEntry} from "qqq/utils/HistoryUtils"; import HistoryUtils, {QHistoryEntry} from "qqq/utils/HistoryUtils";
@ -62,8 +62,13 @@ const useStyles = makeStyles((theme: any) => ({
} }
})); }));
const A_FIRST = -1;
const B_FIRST = 1;
const CommandMenu = ({metaData}: Props) => const CommandMenu = ({metaData}: Props) =>
{ {
const [searchString, setSearchString] = useState("");
const navigate = useNavigate(); const navigate = useNavigate();
const pathParts = location.pathname.replace(/\/+$/, "").split("/"); const pathParts = location.pathname.replace(/\/+$/, "").split("/");
@ -71,7 +76,7 @@ const CommandMenu = ({metaData}: Props) =>
const classes = useStyles(); const classes = useStyles();
function evalueKeyPress(e: KeyboardEvent) function evaluateKeyPress(e: KeyboardEvent)
{ {
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// if a dot pressed, not from a "text" element, then toggle command menu // // if a dot pressed, not from a "text" element, then toggle command menu //
@ -107,20 +112,20 @@ const CommandMenu = ({metaData}: Props) =>
const down = (e: KeyboardEvent) => const down = (e: KeyboardEvent) =>
{ {
evalueKeyPress(e); evaluateKeyPress(e);
} };
document.addEventListener("keydown", down) document.addEventListener("keydown", down);
return () => return () =>
{ {
document.removeEventListener("keydown", down) document.removeEventListener("keydown", down);
} };
}, [tableMetaData, dotMenuOpen, keyboardHelpOpen]) }, [tableMetaData, dotMenuOpen, keyboardHelpOpen]);
useEffect(() => useEffect(() =>
{ {
setDotMenuOpen(false); setDotMenuOpen(false);
}, [location.pathname]) }, [location.pathname]);
function goToItem(path: string) function goToItem(path: string)
{ {
@ -162,73 +167,117 @@ const CommandMenu = ({metaData}: Props) =>
return (null); return (null);
} }
/*******************************************************************************
** sort a section (e.g, tables, apps).
**
** put labels that start-with the search word first.
*******************************************************************************/
function comparator(labelA: string, labelB: string)
{
if (searchString != "")
{
let aStartsWith = labelA.toLowerCase().startsWith(searchString.toLowerCase());
let bStartsWith = labelB.toLowerCase().startsWith(searchString.toLowerCase());
if (aStartsWith && !bStartsWith)
{
return A_FIRST;
}
else if (bStartsWith && !aStartsWith)
{
return B_FIRST;
}
const indexOfSpace = searchString.indexOf(" ");
if (indexOfSpace > 0)
{
aStartsWith = labelA.toLowerCase().startsWith(searchString.substring(0, indexOfSpace).toLowerCase());
bStartsWith = labelB.toLowerCase().startsWith(searchString.substring(0, indexOfSpace).toLowerCase());
if (aStartsWith && !bStartsWith)
{
return A_FIRST;
}
else if (bStartsWith && !aStartsWith)
{
return B_FIRST;
}
}
}
return (labelA.localeCompare(labelB));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
function ActionsSection() function ActionsSection()
{ {
let tableNames : string[]= []; let tableNames: string[] = [];
metaData.tables.forEach((value: QTableMetaData, key: string) => metaData.tables.forEach((value: QTableMetaData, key: string) =>
{ {
tableNames.push(value.name); tableNames.push(value.name);
}) });
tableNames = tableNames.sort((a: string, b:string) => tableNames = tableNames.sort((a: string, b: string) =>
{ {
const labelA = metaData.tables.get(a).label ?? ""; const labelA = metaData.tables.get(a).label ?? "";
const labelB = metaData.tables.get(b).label ?? ""; const labelB = metaData.tables.get(b).label ?? "";
return (labelA.localeCompare(labelB)); return comparator(labelA, labelB);
}) });
const path = location.pathname; const path = location.pathname;
return tableMetaData && !path.endsWith("/edit") && !path.endsWith("/create") && !path.endsWith("#audit") && ! path.endsWith("copy") && return tableMetaData && !path.endsWith("/edit") && !path.endsWith("/create") && !path.endsWith("#audit") && !path.endsWith("copy") &&
( (
<Command.Group heading={`${tableMetaData.label} Actions`}> <Command.Group heading={`${tableMetaData.label} Actions`}>
{ {
tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission && tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission &&
<Command.Item onSelect={() => goToItem(`${pathParts.slice(0, -1).join("/")}/create`)} key={`${tableMetaData.label}-new`} value="New"><Icon sx={{color: accentColor}}>add</Icon>New</Command.Item> <Command.Item onSelect={() => goToItem(`${pathParts.slice(0, -1).join("/")}/create`)} key={`${tableMetaData.label}-new`} value="New"><Icon sx={{color: accentColor}}>add</Icon>New</Command.Item>
} }
{ {
tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission && tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission &&
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/copy`)} key={`${tableMetaData.label}-copy`} value="Copy"><Icon sx={{color: accentColor}}>copy</Icon>Copy</Command.Item> <Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/copy`)} key={`${tableMetaData.label}-copy`} value="Copy"><Icon sx={{color: accentColor}}>copy</Icon>Copy</Command.Item>
} }
{ {
tableMetaData.capabilities.has(Capability.TABLE_UPDATE) && tableMetaData.editPermission && tableMetaData.capabilities.has(Capability.TABLE_UPDATE) && tableMetaData.editPermission &&
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/edit`)} key={`${tableMetaData.label}-edit`} value="Edit"><Icon sx={{color: accentColor}}>edit</Icon>Edit</Command.Item> <Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/edit`)} key={`${tableMetaData.label}-edit`} value="Edit"><Icon sx={{color: accentColor}}>edit</Icon>Edit</Command.Item>
} }
{ {
metaData && metaData.tables.has("audit") && metaData && metaData.tables.has("audit") &&
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}#audit`)} key={`${tableMetaData.label}-audit`} value="Audit"><Icon sx={{color: accentColor}}>checklist</Icon>Audit</Command.Item> <Command.Item onSelect={() => goToItem(`${pathParts.join("/")}#audit`)} key={`${tableMetaData.label}-audit`} value="Audit"><Icon sx={{color: accentColor}}>checklist</Icon>Audit</Command.Item>
} }
{ {
tableProcesses && tableProcesses.length > 0 && tableProcesses && tableProcesses.length > 0 &&
( (
tableProcesses.map((process) => ( tableProcesses.map((process) => (
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/${process.name}`)} key={`${process.name}`} value={`${process.label}`}><Icon sx={{color: accentColor}}>{getIconName(process.iconName, "play_arrow")}</Icon>{process.label}</Command.Item> <Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/${process.name}`)} key={`${process.name}`} value={`${process.label}`}><Icon sx={{color: accentColor}}>{getIconName(process.iconName, "play_arrow")}</Icon>{process.label}</Command.Item>
)) ))
) )
} }
<Command.Separator /> <Command.Separator />
</Command.Group> </Command.Group>
); );
} }
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
function TablesSection() function TablesSection()
{ {
let tableNames : string[]= []; let tableNames: string[] = [];
metaData.tables.forEach((value: QTableMetaData, key: string) => metaData.tables.forEach((value: QTableMetaData, key: string) =>
{ {
tableNames.push(value.name); tableNames.push(value.name);
}) });
tableNames = tableNames.sort((a: string, b:string) => tableNames = tableNames.sort((a: string, b: string) =>
{ {
const labelA = metaData.tables.get(a).label ?? ""; const labelA = metaData.tables.get(a).label ?? "";
const labelB = metaData.tables.get(b).label ?? ""; const labelB = metaData.tables.get(b).label ?? "";
return (labelA.localeCompare(labelB)); return comparator(labelA, labelB);
}) });
return( return (
<Command.Group heading="Tables"> <Command.Group heading="Tables">
{ {
tableNames.map((tableName: string, index: number) => tableNames.map((tableName: string, index: number) =>
@ -243,6 +292,7 @@ const CommandMenu = ({metaData}: Props) =>
); );
} }
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -252,16 +302,16 @@ const CommandMenu = ({metaData}: Props) =>
metaData.apps.forEach((value: QAppMetaData, key: string) => metaData.apps.forEach((value: QAppMetaData, key: string) =>
{ {
appNames.push(value.name); appNames.push(value.name);
}) });
appNames = appNames.sort((a: string, b:string) => appNames = appNames.sort((a: string, b: string) =>
{ {
const labelA = getFullAppLabel(metaData.appTree, a, 1, "") ?? ""; const labelA = getFullAppLabel(metaData.appTree, a, 1, "") ?? "";
const labelB = getFullAppLabel(metaData.appTree, b, 1, "") ?? ""; const labelB = getFullAppLabel(metaData.appTree, b, 1, "") ?? "";
return (labelA.localeCompare(labelB)); return comparator(labelA, labelB);
}) });
return( return (
<Command.Group heading="Apps"> <Command.Group heading="Apps">
{ {
appNames.map((appName: string, index: number) => appNames.map((appName: string, index: number) =>
@ -276,33 +326,37 @@ const CommandMenu = ({metaData}: Props) =>
); );
} }
/*******************************************************************************
**
*******************************************************************************/
function RecentlyViewedSection() function RecentlyViewedSection()
{ {
const history = HistoryUtils.get(); const history = HistoryUtils.get();
const options = [] as any; const options = [] as any;
history.entries.reverse().forEach((entry, index) => history.entries.reverse().forEach((entry, index) =>
options.push({label: `${entry.label} index`, id: index, key: index, path: entry.path, iconName: entry.iconName}) options.push({label: `${entry.label} index`, id: index, key: index, path: entry.path, iconName: entry.iconName})
) );
let appNames: string[] = []; let appNames: string[] = [];
metaData.apps.forEach((value: QAppMetaData, key: string) => metaData.apps.forEach((value: QAppMetaData, key: string) =>
{ {
appNames.push(value.name); appNames.push(value.name);
}) });
appNames = appNames.sort((a: string, b:string) => appNames = appNames.sort((a: string, b: string) =>
{ {
const labelA = metaData.apps.get(a).label ?? ""; const labelA = metaData.apps.get(a).label ?? "";
const labelB = metaData.apps.get(b).label ?? ""; const labelB = metaData.apps.get(b).label ?? "";
return (labelA.localeCompare(labelB)); return comparator(labelA, labelB);
}) });
const entryMap = new Map<string, boolean>(); const entryMap = new Map<string, boolean>();
return( return (
<Command.Group heading="Recently Viewed Records"> <Command.Group heading="Recently Viewed Records">
{ {
history.entries.reverse().map((entry: QHistoryEntry, index: number) => history.entries.reverse().map((entry: QHistoryEntry, index: number) =>
! entryMap.has(entry.label) && entryMap.set(entry.label, true) && ( !entryMap.has(entry.label) && entryMap.set(entry.label, true) && (
<Command.Item onSelect={() => goToItem(`${entry.path}`)} key={`${entry.label}-${index}`} value={entry.label}><Icon sx={{color: accentColor}}>{entry.iconName}</Icon>{entry.label}</Command.Item> <Command.Item onSelect={() => goToItem(`${entry.path}`)} key={`${entry.label}-${index}`} value={entry.label}><Icon sx={{color: accentColor}}>{entry.iconName}</Icon>{entry.label}</Command.Item>
) )
) )
@ -311,29 +365,90 @@ const CommandMenu = ({metaData}: Props) =>
); );
} }
const containerElement = useRef(null) const containerElement = useRef(null);
/*******************************************************************************
**
*******************************************************************************/
function closeKeyboardHelp() function closeKeyboardHelp()
{ {
setKeyboardHelpOpen(false); setKeyboardHelpOpen(false);
} }
/*******************************************************************************
**
*******************************************************************************/
function closeDotMenu() function closeDotMenu()
{ {
setDotMenuOpen(false); setDotMenuOpen(false);
} }
/*******************************************************************************
** filter function for cmd-k library
**
*******************************************************************************/
function doFilter(value: string, search: string)
{
setSearchString(search);
/////////////////////
// split on spaces //
/////////////////////
const searchParts = search.toLowerCase().split(" ");
if (searchParts.length == 1)
{
//////////////////////////////////////////////
// if only 1 word, just do an includes test //
//////////////////////////////////////////////
return (value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0);
}
else
{
////////////////////////////////////////
// else split the value on spaces too //
////////////////////////////////////////
const valueParts = value.toLowerCase().split(" ");
if (searchParts.length > valueParts.length)
{
//////////////////////////////////////////////////////////////////////////////////
// if there are more words in the search than in the value, then it can't match //
// e.g. "order c" can't ever match, say "order" //
//////////////////////////////////////////////////////////////////////////////////
return (0);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// iterate over the search parts - if any don't match the corresponding value parts, then it's a non-match //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
for (let i = 0; i < searchParts.length; i++)
{
if (!valueParts[i].includes(searchParts[i]))
{
return (0);
}
}
/////////////////////////////////
// if no failure, return a hit //
/////////////////////////////////
return (1);
}
}
return ( return (
<React.Fragment> <React.Fragment>
<Box ref={containerElement} className="raycast" sx={{position: "relative", zIndex: 10_000}}> <Box ref={containerElement} className="raycast" sx={{position: "relative", zIndex: 10_000}}>
{ {
<Dialog open={dotMenuOpen} onClose={closeDotMenu}> <Dialog open={dotMenuOpen} onClose={closeDotMenu}>
<Command.Dialog open={dotMenuOpen} onOpenChange={setDotMenuOpen} container={containerElement.current} label="Test Global Command Menu"> <Command.Dialog open={dotMenuOpen} onOpenChange={setDotMenuOpen} container={containerElement.current} filter={(value, search) => doFilter(value, search)}>
<Box sx={{display: "flex"}}> <Box sx={{display: "flex"}}>
<Command.Input placeholder="Search for Tables, Actions, or Recently Viewed Items..."/> <Command.Input placeholder="Search for Tables, Actions, or Recently Viewed Items..." />
<Button onClick={closeDotMenu}><Icon>close</Icon></Button> <Button onClick={closeDotMenu}><Icon>close</Icon></Button>
</Box> </Box>
<Command.Loading /> <Command.Loading />
<Command.Separator /> <Command.Separator />
<Command.List> <Command.List>
<Command.Empty>No results found.</Command.Empty> <Command.Empty>No results found.</Command.Empty>
@ -381,6 +496,6 @@ const CommandMenu = ({metaData}: Props) =>
</Dialog> </Dialog>
} }
</React.Fragment> </React.Fragment>
) );
} };
export default CommandMenu; export default CommandMenu;

View File

@ -205,7 +205,7 @@ function EntityForm(props: Props): JSX.Element
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
const deleteChildRecord = (name: string, widgetData: any, rowIndex: number) => function deleteChildRecord(name: string, widgetData: any, rowIndex: number)
{ {
updateChildRecordList(name, "delete", rowIndex); updateChildRecordList(name, "delete", rowIndex);
}; };
@ -377,7 +377,7 @@ function EntityForm(props: Props): JSX.Element
widgetData.viewAllLink = null; widgetData.viewAllLink = null;
widgetMetaData.showExportButton = false; widgetMetaData.showExportButton = false;
return <RecordGridWidget return Object.keys(childListWidgetData).length > 0 && (<RecordGridWidget
key={`${formValues["tableName"]}-${modalDataChangedCounter}`} key={`${formValues["tableName"]}-${modalDataChangedCounter}`}
widgetMetaData={widgetMetaData} widgetMetaData={widgetMetaData}
data={widgetData} data={widgetData}
@ -387,7 +387,7 @@ function EntityForm(props: Props): JSX.Element
addNewRecordCallback={() => openAddChildRecord(widgetMetaData.name, widgetData)} addNewRecordCallback={() => openAddChildRecord(widgetMetaData.name, widgetData)}
editRecordCallback={(rowIndex) => openEditChildRecord(widgetMetaData.name, widgetData, rowIndex)} editRecordCallback={(rowIndex) => openEditChildRecord(widgetMetaData.name, widgetData, rowIndex)}
deleteRecordCallback={(rowIndex) => deleteChildRecord(widgetMetaData.name, widgetData, rowIndex)} deleteRecordCallback={(rowIndex) => deleteChildRecord(widgetMetaData.name, widgetData, rowIndex)}
/>; />);
} }
if (widgetMetaData.type == "filterAndColumnsSetup") if (widgetMetaData.type == "filterAndColumnsSetup")
@ -480,83 +480,164 @@ function EntityForm(props: Props): JSX.Element
////////////////// //////////////////
// initial load // // initial load //
////////////////// //////////////////
if (!asyncLoadInited) useEffect(() =>
{ {
setAsyncLoadInited(true); if (!asyncLoadInited)
(async () =>
{ {
const tableMetaData = await qController.loadTableMetaData(tableName); setAsyncLoadInited(true);
setTableMetaData(tableMetaData); (async () =>
recordAnalytics({location: window.location, title: (props.isCopy ? "Copy" : props.id ? "Edit" : "New") + ": " + tableMetaData.label});
setupFieldRules(tableMetaData);
const metaData = await qController.loadMetaData();
setMetaData(metaData);
/////////////////////////////////////////////////
// define the sections, e.g., for the left-bar //
/////////////////////////////////////////////////
const tableSections = TableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()], (section: QTableSection) =>
{ {
const widget = metaData?.widgets.get(section.widgetName); const tableMetaData = await qController.loadTableMetaData(tableName);
if (widget) setTableMetaData(tableMetaData);
recordAnalytics({location: window.location, title: (props.isCopy ? "Copy" : props.id ? "Edit" : "New") + ": " + tableMetaData.label});
setupFieldRules(tableMetaData);
const metaData = await qController.loadMetaData();
setMetaData(metaData);
/////////////////////////////////////////////////
// define the sections, e.g., for the left-bar //
/////////////////////////////////////////////////
const tableSections = TableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()], (section: QTableSection) =>
{ {
if (widget.type == "childRecordList" && widget.defaultValues?.has("manageAssociationName")) const widget = metaData?.widgets.get(section.widgetName);
if (widget)
{ {
return (true); if (widget.type == "childRecordList" && widget.defaultValues?.has("manageAssociationName"))
{
return (true);
}
if (widget.type == "filterAndColumnsSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm")
{
return (true);
}
} }
if (widget.type == "filterAndColumnsSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm") return (false);
{ });
return (true); setTableSections(tableSections);
}
}
return (false); const fieldArray = [] as QFieldMetaData[];
}); const sortedKeys = [...tableMetaData.fields.keys()].sort();
setTableSections(tableSections); sortedKeys.forEach((key) =>
const fieldArray = [] as QFieldMetaData[];
const sortedKeys = [...tableMetaData.fields.keys()].sort();
sortedKeys.forEach((key) =>
{
const fieldMetaData = tableMetaData.fields.get(key);
fieldArray.push(fieldMetaData);
});
/////////////////////////////////////////////////////////////////////////////////////////
// if doing an edit or copy, fetch the record and pre-populate the form values from it //
/////////////////////////////////////////////////////////////////////////////////////////
let record: QRecord = null;
let defaultDisplayValues = new Map<string, string>();
if (props.id !== null)
{
record = await qController.get(tableName, props.id);
setRecord(record);
recordAnalytics({category: "tableEvents", action: props.isCopy ? "copy" : "edit", label: tableMetaData?.label + " / " + record?.recordLabel});
const titleVerb = props.isCopy ? "Copy" : "Edit";
setFormTitle(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
if (!props.isModal)
{ {
setPageHeader(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`); const fieldMetaData = tableMetaData.fields.get(key);
} fieldArray.push(fieldMetaData);
tableMetaData.fields.forEach((fieldMetaData, key) =>
{
if (props.isCopy && fieldMetaData.name == tableMetaData.primaryKeyField)
{
return;
}
initialValues[key] = record.values.get(key);
}); });
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// these checks are only for updating records, if copying, it is actually an insert, which is checked after this block // // if doing an edit or copy, fetch the record and pre-populate the form values from it //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
if (!props.isCopy) let record: QRecord = null;
let defaultDisplayValues = new Map<string, string>();
if (props.id !== null)
{
record = await qController.get(tableName, props.id);
setRecord(record);
recordAnalytics({category: "tableEvents", action: props.isCopy ? "copy" : "edit", label: tableMetaData?.label + " / " + record?.recordLabel});
const titleVerb = props.isCopy ? "Copy" : "Edit";
setFormTitle(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
if (!props.isModal)
{
setPageHeader(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
}
tableMetaData.fields.forEach((fieldMetaData, key) =>
{
if (props.isCopy && fieldMetaData.name == tableMetaData.primaryKeyField)
{
return;
}
initialValues[key] = record.values.get(key);
});
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// these checks are only for updating records, if copying, it is actually an insert, which is checked after this block //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (!props.isCopy)
{
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
{
setNotAllowedError("Records may not be edited in this table");
}
else if (!tableMetaData.editPermission)
{
setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`);
}
}
}
else
{
///////////////////////////////////////////
// else handle preparing to do an insert //
///////////////////////////////////////////
setFormTitle(`Creating New ${tableMetaData?.label}`);
recordAnalytics({category: "tableEvents", action: "new", label: tableMetaData?.label});
if (!props.isModal)
{
setPageHeader(`Creating New ${tableMetaData?.label}`);
}
////////////////////////////////////////////////////////////////////////////////////////////////
// if default values were supplied for a new record, then populate initialValues, for formik. //
////////////////////////////////////////////////////////////////////////////////////////////////
for (let i = 0; i < fieldArray.length; i++)
{
const fieldMetaData = fieldArray[i];
const fieldName = fieldMetaData.name;
const defaultValue = (defaultValues && defaultValues[fieldName]) ? defaultValues[fieldName] : fieldMetaData.defaultValue;
if (defaultValue)
{
initialValues[fieldName] = defaultValue;
///////////////////////////////////////////////////////////////////////////////////////////
// we need to set the initialDisplayValue for possible value fields with a default value //
// so, look them up here now if needed //
///////////////////////////////////////////////////////////////////////////////////////////
if (fieldMetaData.possibleValueSourceName)
{
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]]);
if (results && results.length > 0)
{
defaultDisplayValues.set(fieldName, results[0].label);
}
}
}
}
}
///////////////////////////////////////////////////
// if an override heading was passed in, use it. //
///////////////////////////////////////////////////
if (props.overrideHeading)
{
setFormTitle(props.overrideHeading);
if (!props.isModal)
{
setPageHeader(props.overrideHeading);
}
}
//////////////////////////////////////
// check capabilities & permissions //
//////////////////////////////////////
if (props.isCopy || !props.id)
{
if (!tableMetaData.capabilities.has(Capability.TABLE_INSERT))
{
setNotAllowedError("Records may not be created in this table");
}
else if (!tableMetaData.insertPermission)
{
setNotAllowedError(`You do not have permission to create ${tableMetaData.label} records`);
}
}
else
{ {
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE)) if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
{ {
@ -567,201 +648,123 @@ function EntityForm(props: Props): JSX.Element
setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`); setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`);
} }
} }
}
else
{
///////////////////////////////////////////
// else handle preparing to do an insert //
///////////////////////////////////////////
setFormTitle(`Creating New ${tableMetaData?.label}`);
recordAnalytics({category: "tableEvents", action: "new", label: tableMetaData?.label});
if (!props.isModal) /////////////////////////////////////////////////////////////////////
{ // make sure all initialValues are properly formatted for the form //
setPageHeader(`Creating New ${tableMetaData?.label}`); /////////////////////////////////////////////////////////////////////
}
////////////////////////////////////////////////////////////////////////////////////////////////
// if default values were supplied for a new record, then populate initialValues, for formik. //
////////////////////////////////////////////////////////////////////////////////////////////////
for (let i = 0; i < fieldArray.length; i++) for (let i = 0; i < fieldArray.length; i++)
{ {
const fieldMetaData = fieldArray[i]; const fieldMetaData = fieldArray[i];
const fieldName = fieldMetaData.name; if (fieldMetaData.type == QFieldType.DATE_TIME && initialValues[fieldMetaData.name])
const defaultValue = (defaultValues && defaultValues[fieldName]) ? defaultValues[fieldName] : fieldMetaData.defaultValue;
if (defaultValue)
{ {
initialValues[fieldName] = defaultValue; initialValues[fieldMetaData.name] = ValueUtils.formatDateTimeValueForForm(initialValues[fieldMetaData.name]);
}
}
/////////////////////////////////////////////////////////////////////////////////////////// setInitialValues(initialValues);
// we need to set the initialDisplayValue for possible value fields with a default value //
// so, look them up here now if needed // /////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// // get formField and formValidation objects for Formik //
if (fieldMetaData.possibleValueSourceName) /////////////////////////////////////////////////////////
const {
dynamicFormFields,
formValidations,
} = DynamicFormUtils.getFormData(fieldArray, disabledFields);
DynamicFormUtils.addPossibleValueProps(dynamicFormFields, fieldArray, tableName, null, record ? record.displayValues : defaultDisplayValues);
/////////////////////////////////////
// group the formFields by section //
/////////////////////////////////////
const dynamicFormFieldsBySection = new Map<string, any>();
let t1sectionName;
let t1section;
const nonT1Sections: QTableSection[] = [];
const newRenderedWidgetSections: { [name: string]: JSX.Element } = {};
const newChildListWidgetData: { [name: string]: ChildRecordListData } = {};
for (let i = 0; i < tableSections.length; i++)
{
const section = tableSections[i];
const sectionDynamicFormFields: any[] = [];
if (section.isHidden)
{
continue;
}
const hasFields = section.fieldNames && section.fieldNames.length > 0;
if (hasFields)
{
for (let j = 0; j < section.fieldNames.length; j++)
{ {
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]]); const fieldName = section.fieldNames[j];
if (results && results.length > 0) const field = tableMetaData.fields.get(fieldName);
if (!field)
{ {
defaultDisplayValues.set(fieldName, results[0].label); console.log(`Omitting un-found field ${fieldName} from form`);
continue;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if id !== null (and we're not copying) - means we're on the edit screen -- show all fields on the edit screen. //
// || (or) we're on the insert screen in which case, only show editable fields. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if ((props.id !== null && !props.isCopy) || field.isEditable)
{
sectionDynamicFormFields.push(dynamicFormFields[fieldName]);
} }
} }
}
}
}
/////////////////////////////////////////////////// if (sectionDynamicFormFields.length === 0)
// if an override heading was passed in, use it. //
///////////////////////////////////////////////////
if (props.overrideHeading)
{
setFormTitle(props.overrideHeading);
if (!props.isModal)
{
setPageHeader(props.overrideHeading);
}
}
//////////////////////////////////////
// check capabilities & permissions //
//////////////////////////////////////
if (props.isCopy || !props.id)
{
if (!tableMetaData.capabilities.has(Capability.TABLE_INSERT))
{
setNotAllowedError("Records may not be created in this table");
}
else if (!tableMetaData.insertPermission)
{
setNotAllowedError(`You do not have permission to create ${tableMetaData.label} records`);
}
}
else
{
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
{
setNotAllowedError("Records may not be edited in this table");
}
else if (!tableMetaData.editPermission)
{
setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`);
}
}
/////////////////////////////////////////////////////////////////////
// make sure all initialValues are properly formatted for the form //
/////////////////////////////////////////////////////////////////////
for (let i = 0; i < fieldArray.length; i++)
{
const fieldMetaData = fieldArray[i];
if (fieldMetaData.type == QFieldType.DATE_TIME && initialValues[fieldMetaData.name])
{
initialValues[fieldMetaData.name] = ValueUtils.formatDateTimeValueForForm(initialValues[fieldMetaData.name]);
}
}
setInitialValues(initialValues);
/////////////////////////////////////////////////////////
// get formField and formValidation objects for Formik //
/////////////////////////////////////////////////////////
const {
dynamicFormFields,
formValidations,
} = DynamicFormUtils.getFormData(fieldArray, disabledFields);
DynamicFormUtils.addPossibleValueProps(dynamicFormFields, fieldArray, tableName, null, record ? record.displayValues : defaultDisplayValues);
/////////////////////////////////////
// group the formFields by section //
/////////////////////////////////////
const dynamicFormFieldsBySection = new Map<string, any>();
let t1sectionName;
let t1section;
const nonT1Sections: QTableSection[] = [];
const newRenderedWidgetSections: { [name: string]: JSX.Element } = {};
const newChildListWidgetData: { [name: string]: ChildRecordListData } = {};
for (let i = 0; i < tableSections.length; i++)
{
const section = tableSections[i];
const sectionDynamicFormFields: any[] = [];
if (section.isHidden)
{
continue;
}
const hasFields = section.fieldNames && section.fieldNames.length > 0;
if (hasFields)
{
for (let j = 0; j < section.fieldNames.length; j++)
{
const fieldName = section.fieldNames[j];
const field = tableMetaData.fields.get(fieldName);
if (!field)
{ {
console.log(`Omitting un-found field ${fieldName} from form`); ////////////////////////////////////////////////////////////////////////////////////////////////
// in case there are no active fields in this section, remove it from the tableSections array //
////////////////////////////////////////////////////////////////////////////////////////////////
tableSections.splice(i, 1);
i--;
continue; continue;
} }
else
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if id !== null (and we're not copying) - means we're on the edit screen -- show all fields on the edit screen. //
// || (or) we're on the insert screen in which case, only show editable fields. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if ((props.id !== null && !props.isCopy) || field.isEditable)
{ {
sectionDynamicFormFields.push(dynamicFormFields[fieldName]); dynamicFormFieldsBySection.set(section.name, sectionDynamicFormFields);
} }
} }
if (sectionDynamicFormFields.length === 0)
{
////////////////////////////////////////////////////////////////////////////////////////////////
// in case there are no active fields in this section, remove it from the tableSections array //
////////////////////////////////////////////////////////////////////////////////////////////////
tableSections.splice(i, 1);
i--;
continue;
}
else else
{ {
dynamicFormFieldsBySection.set(section.name, sectionDynamicFormFields); const widgetMetaData = metaData?.widgets.get(section.widgetName);
const widgetData = await qController.widget(widgetMetaData.name, makeQueryStringWithIdAndObject(tableMetaData, defaultValues));
newRenderedWidgetSections[section.widgetName] = getWidgetSection(widgetMetaData, widgetData);
newChildListWidgetData[section.widgetName] = widgetData;
}
//////////////////////////////////////
// capture the tier1 section's name //
//////////////////////////////////////
if (section.tier === "T1")
{
t1sectionName = section.name;
t1section = section;
}
else
{
nonT1Sections.push(section);
} }
} }
else
{
const widgetMetaData = metaData?.widgets.get(section.widgetName);
const widgetData = await qController.widget(widgetMetaData.name, makeQueryStringWithIdAndObject(tableMetaData, defaultValues));
newRenderedWidgetSections[section.widgetName] = getWidgetSection(widgetMetaData, widgetData); setT1SectionName(t1sectionName);
newChildListWidgetData[section.widgetName] = widgetData; setT1Section(t1section);
} setNonT1Sections(nonT1Sections);
setFormFields(dynamicFormFieldsBySection);
setValidations(Yup.object().shape(formValidations));
setRenderedWidgetSections(newRenderedWidgetSections);
setChildListWidgetData(newChildListWidgetData);
////////////////////////////////////// forceUpdate();
// capture the tier1 section's name // })();
////////////////////////////////////// }
if (section.tier === "T1") }, []);
{
t1sectionName = section.name;
t1section = section;
}
else
{
nonT1Sections.push(section);
}
}
setT1SectionName(t1sectionName);
setT1Section(t1section);
setNonT1Sections(nonT1Sections);
setFormFields(dynamicFormFieldsBySection);
setValidations(Yup.object().shape(formValidations));
setRenderedWidgetSections(newRenderedWidgetSections);
setChildListWidgetData(newChildListWidgetData);
forceUpdate();
})();
}
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
@ -881,16 +884,28 @@ function EntityForm(props: Props): JSX.Element
let haveAssociationsToPost = false; let haveAssociationsToPost = false;
for (let name of Object.keys(childListWidgetData)) for (let name of Object.keys(childListWidgetData))
{ {
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if cannot find association name, continue loop, since cannot tell backend which association this is for //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
const manageAssociationName = metaData.widgets.get(name)?.defaultValues?.get("manageAssociationName"); const manageAssociationName = metaData.widgets.get(name)?.defaultValues?.get("manageAssociationName");
if (!manageAssociationName) if (!manageAssociationName)
{ {
console.log(`Cannot send association data to backend - missing a manageAssociationName defaultValue in widget meta data for widget name ${name}`); console.log(`Cannot send association data to backend - missing a manageAssociationName defaultValue in widget meta data for widget name ${name}`);
continue;
} }
associationsToPost[manageAssociationName] = [];
haveAssociationsToPost = true; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for (let i = 0; i < childListWidgetData[name].queryOutput?.records?.length; i++) // if the records array exists, add to associations to post - note: even if empty list, the backend will expect this //
// association name to be present if it is to act on it (for the case when all associations have been deleted) //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (childListWidgetData[name].queryOutput.records)
{ {
associationsToPost[manageAssociationName].push(childListWidgetData[name].queryOutput.records[i].values); associationsToPost[manageAssociationName] = [];
haveAssociationsToPost = true;
for (let i = 0; i < childListWidgetData[name].queryOutput?.records?.length; i++)
{
associationsToPost[manageAssociationName].push(childListWidgetData[name].queryOutput.records[i].values);
}
} }
} }
if (haveAssociationsToPost) if (haveAssociationsToPost)

View File

@ -24,7 +24,6 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
import {Alert, Collapse} from "@mui/material"; import {Alert, Collapse} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
@ -108,32 +107,38 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
let columns: QQueryColumns = null; let columns: QQueryColumns = null;
let usingDefaultEmptyFilter = false; let usingDefaultEmptyFilter = false;
let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter; let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter;
const defaultFilterFields = getDefaultFilterFieldNames(widgetMetaData);
if (!queryFilter) if (!queryFilter)
{ {
queryFilter = new QQueryFilter(); queryFilter = new QQueryFilter();
if (defaultFilterFields?.length == 0)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if there is no queryFilter provided, see if there are default fields from which a query should be seeded //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
const defaultFilterFields = getDefaultFilterFieldNames(widgetMetaData);
if (defaultFilterFields?.length > 0)
{
defaultFilterFields.forEach((fieldName: string) =>
{
if (recordValues[fieldName])
{
queryFilter.addCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [recordValues[fieldName]]));
}
});
queryFilter.addOrderBy(new QFilterOrderBy("id", false));
queryFilter = Object.assign({}, queryFilter);
}
else
{ {
usingDefaultEmptyFilter = true; usingDefaultEmptyFilter = true;
} }
} }
else
{
queryFilter = Object.assign(new QQueryFilter(), queryFilter);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// if there are default fields from which a query should be seeded, add/update the filter with them //
//////////////////////////////////////////////////////////////////////////////////////////////////////
if (defaultFilterFields?.length > 0)
{
defaultFilterFields.forEach((fieldName: string) =>
{
////////////////////////////////////////////////////////////////////////////////////////////
// if a value for the default field exists, remove the criteria for it in our query first //
////////////////////////////////////////////////////////////////////////////////////////////
queryFilter.criteria = queryFilter.criteria?.filter(c => c.fieldName != fieldName);
if (recordValues[fieldName])
{
queryFilter.addCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [recordValues[fieldName]]));
}
});
}
if (recordValues["columnsJson"]) if (recordValues["columnsJson"])
{ {
@ -202,7 +207,7 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
if (missingRequiredFields.length > 0) if (missingRequiredFields.length > 0)
{ {
setAlertContent("The following fields must first be selected to add Additional Order Filters: '" + missingRequiredFields.join(", ") + "'"); setAlertContent("The following fields must first be selected to edit the filter: '" + missingRequiredFields.join(", ") + "'");
return; return;
} }

View File

@ -40,14 +40,14 @@ import {Link, useNavigate} from "react-router-dom";
export interface ChildRecordListData extends WidgetData export interface ChildRecordListData extends WidgetData
{ {
title: string; title: string;
queryOutput: {records: {values: any}[]} queryOutput: { records: { values: any }[] };
childTableMetaData: QTableMetaData; childTableMetaData: QTableMetaData;
tablePath: string; tablePath: string;
viewAllLink: string; viewAllLink: string;
totalRows: number; totalRows: number;
canAddChildRecord: boolean; canAddChildRecord: boolean;
defaultValuesForNewChildRecords: {[fieldName: string]: any}; defaultValuesForNewChildRecords: { [fieldName: string]: any };
disabledFieldsForNewChildRecords: {[fieldName: string]: any}; disabledFieldsForNewChildRecords: { [fieldName: string]: any };
} }
interface Props interface Props
@ -75,9 +75,9 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
{ {
const instance = useRef({timer: null}); const instance = useRef({timer: null});
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
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 [csv, setCsv] = useState(null as string);
const [fileName, setFileName] = useState(null as string); const [fileName, setFileName] = useState(null as string);
const [gridMouseDownX, setGridMouseDownX] = useState(0); const [gridMouseDownX, setGridMouseDownX] = useState(0);
@ -110,20 +110,20 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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]; const allColumns = [...columns];
setAllColumns(JSON.parse(JSON.stringify(columns))); setAllColumns(JSON.parse(JSON.stringify(columns)));
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// do not not show the foreign-key column of the parent table // // do not not show the foreign-key column of the parent table //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
if(data.defaultValuesForNewChildRecords) if (data.defaultValuesForNewChildRecords)
{ {
for (let i = 0; i < columns.length; i++) for (let i = 0; i < columns.length; i++)
{ {
if(data.defaultValuesForNewChildRecords[columns[i].field]) if (data.defaultValuesForNewChildRecords[columns[i].field])
{ {
columns.splice(i, 1); columns.splice(i, 1);
i-- i--;
} }
} }
} }
@ -131,7 +131,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
//////////////////////////////////// ////////////////////////////////////
// add actions cell, if available // // add actions cell, if available //
//////////////////////////////////// ////////////////////////////////////
if(allowRecordEdit || allowRecordDelete) if (allowRecordEdit || allowRecordDelete)
{ {
columns.unshift({ columns.unshift({
field: "_actions", field: "_actions",
@ -145,19 +145,19 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
return <Box> return <Box>
{allowRecordEdit && <IconButton onClick={() => editRecordCallback(params.row.__rowIndex)}><Icon>edit</Icon></IconButton>} {allowRecordEdit && <IconButton onClick={() => editRecordCallback(params.row.__rowIndex)}><Icon>edit</Icon></IconButton>}
{allowRecordDelete && <IconButton onClick={() => deleteRecordCallback(params.row.__rowIndex)}><Icon>delete</Icon></IconButton>} {allowRecordDelete && <IconButton onClick={() => deleteRecordCallback(params.row.__rowIndex)}><Icon>delete</Icon></IconButton>}
</Box> </Box>;
}) })
}) });
} }
setRows(rows); setRows(rows);
setRecords(records) setRecords(records);
setColumns(columns); setColumns(columns);
let csv = ""; let csv = "";
for (let i = 0; i < allColumns.length; i++) for (let i = 0; i < allColumns.length; i++)
{ {
csv += `${i > 0 ? "," : ""}"${ValueUtils.cleanForCsv(allColumns[i].headerName)}"` csv += `${i > 0 ? "," : ""}"${ValueUtils.cleanForCsv(allColumns[i].headerName)}"`;
} }
csv += "\n"; csv += "\n";
@ -165,8 +165,8 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
{ {
for (let j = 0; j < allColumns.length; j++) for (let j = 0; j < allColumns.length; j++)
{ {
const value = records[i].displayValues.get(allColumns[j].field) ?? records[i].values.get(allColumns[j].field) const value = records[i].displayValues.get(allColumns[j].field) ?? records[i].values.get(allColumns[j].field);
csv += `${j > 0 ? "," : ""}"${ValueUtils.cleanForCsv(value)}"` csv += `${j > 0 ? "," : ""}"${ValueUtils.cleanForCsv(value)}"`;
} }
csv += "\n"; csv += "\n";
} }
@ -182,13 +182,13 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
// view all link // // view all link //
/////////////////// ///////////////////
const labelAdditionalElementsLeft: JSX.Element[] = []; const labelAdditionalElementsLeft: JSX.Element[] = [];
if(data && data.viewAllLink) if (data && data.viewAllLink)
{ {
labelAdditionalElementsLeft.push( labelAdditionalElementsLeft.push(
<Typography key={"viewAllLink"} variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative"> <Typography key={"viewAllLink"} variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative">
<Link to={data.viewAllLink}>View All</Link> <Link to={data.viewAllLink}>View All</Link>
</Typography> </Typography>
) );
} }
/////////////////// ///////////////////
@ -200,10 +200,10 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
{ {
isExportDisabled = false; isExportDisabled = false;
if(data.totalRows && data.queryOutput.records.length < data.totalRows) if (data.totalRows && data.queryOutput.records.length < data.totalRows)
{ {
tooltipTitle = "Export these " + data.queryOutput.records.length + " records." tooltipTitle = "Export these " + data.queryOutput.records.length + " records.";
if(data.viewAllLink) if (data.viewAllLink)
{ {
tooltipTitle += "\nClick View All to export all records."; tooltipTitle += "\nClick View All to export all records.";
} }
@ -212,17 +212,17 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
const onExportClick = () => const onExportClick = () =>
{ {
if(csv) 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.");
} }
} };
if(widgetMetaData?.showExportButton) if (widgetMetaData?.showExportButton)
{ {
labelAdditionalElementsLeft.push( labelAdditionalElementsLeft.push(
<Typography key={"exportButton"} variant="body2" px={0} display="inline" position="relative"> <Typography key={"exportButton"} variant="body2" px={0} display="inline" position="relative">
@ -234,15 +234,15 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
//////////////////// ////////////////////
// add new button // // add new button //
//////////////////// ////////////////////
const labelAdditionalComponentsRight: LabelComponent[] = [] const labelAdditionalComponentsRight: LabelComponent[] = [];
if(data && data.canAddChildRecord) if (data && data.canAddChildRecord)
{ {
let disabledFields = data.disabledFieldsForNewChildRecords; let disabledFields = data.disabledFieldsForNewChildRecords;
if(!disabledFields) if (!disabledFields)
{ {
disabledFields = data.defaultValuesForNewChildRecords; disabledFields = data.defaultValuesForNewChildRecords;
} }
labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, data.defaultValuesForNewChildRecords, "Add new", disabledFields, addNewRecordCallback)) labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, data.defaultValuesForNewChildRecords, "Add new", disabledFields, addNewRecordCallback));
} }
@ -251,16 +251,16 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
const handleRowClick = (params: GridRowParams, event: MuiEvent<React.MouseEvent>, details: GridCallbackDetails) => const handleRowClick = (params: GridRowParams, event: MuiEvent<React.MouseEvent>, details: GridCallbackDetails) =>
{ {
if(disableRowClick) if (disableRowClick)
{ {
return; return;
} }
(async () => (async () =>
{ {
const qInstance = await qController.loadMetaData() const qInstance = await qController.loadMetaData();
let tablePath = qInstance.getTablePathByName(data.childTableMetaData.name) let tablePath = qInstance.getTablePathByName(data.childTableMetaData.name);
if(tablePath) if (tablePath)
{ {
tablePath = `${tablePath}/${params.row[data.childTableMetaData.primaryKeyField]}`; tablePath = `${tablePath}/${params.row[data.childTableMetaData.primaryKeyField]}`;
DataGridUtils.handleRowClick(tablePath, event, gridMouseDownX, gridMouseDownY, navigate, instance); DataGridUtils.handleRowClick(tablePath, event, gridMouseDownX, gridMouseDownY, navigate, instance);
@ -276,7 +276,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
*******************************************************************************/ *******************************************************************************/
function CustomToolbar() function CustomToolbar()
{ {
const handleMouseDown: GridEventListener<"cellMouseDown"> = ( params, event, details ) => const handleMouseDown: GridEventListener<"cellMouseDown"> = (params, event, details) =>
{ {
setGridMouseDownX(event.clientX); setGridMouseDownX(event.clientX);
setGridMouseDownY(event.clientY); setGridMouseDownY(event.clientY);
@ -304,8 +304,8 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsRight={labelAdditionalComponentsRight}
labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}} labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}}
> >
<Box mx={-2} mb={-3}> <Box mx={-3} mb={-3}>
<Box className="recordGridWidget"> <Box>
<DataGridPro <DataGridPro
autoHeight autoHeight
sx={{ sx={{

View File

@ -421,7 +421,7 @@ input[type="search"]::-webkit-search-results-decoration
font-size: 2rem !important; font-size: 2rem !important;
} }
.dashboard-order-release-icon .dashboard-table-actions-icon
{ {
font-size: 1.5rem !important; font-size: 1.5rem !important;
position: relative; position: relative;
@ -711,11 +711,6 @@ input[type="search"]::-webkit-search-results-decoration
padding: 24px; padding: 24px;
} }
.recordView .widget .recordGridWidget
{
margin: -8px;
}
.MuiPickersDay-root.Mui-selected, .MuiPickersDay-root.MuiPickersDay-dayWithMargin:hover .MuiPickersDay-root.Mui-selected, .MuiPickersDay-root.MuiPickersDay-dayWithMargin:hover
{ {
color: white; color: white;