mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-21 06:38:43 +00:00
Compare commits
8 Commits
snapshot-f
...
snapshot-i
Author | SHA1 | Date | |
---|---|---|---|
beb0b415fa | |||
d03e908a9d | |||
dc62f97219 | |||
aac579232d | |||
fe9e20715a | |||
6469d569c0 | |||
7b562aea50 | |||
3bf1cea9dd |
@ -36,7 +36,7 @@ import Icon from "@mui/material/Icon";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import {makeStyles} from "@mui/styles";
|
||||
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 QContext from "QContext";
|
||||
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 [searchString, setSearchString] = useState("");
|
||||
|
||||
const navigate = useNavigate();
|
||||
const pathParts = location.pathname.replace(/\/+$/, "").split("/");
|
||||
|
||||
@ -71,7 +76,7 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
function evalueKeyPress(e: KeyboardEvent)
|
||||
function evaluateKeyPress(e: KeyboardEvent)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// 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) =>
|
||||
{
|
||||
evalueKeyPress(e);
|
||||
}
|
||||
evaluateKeyPress(e);
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", down)
|
||||
document.addEventListener("keydown", down);
|
||||
return () =>
|
||||
{
|
||||
document.removeEventListener("keydown", down)
|
||||
}
|
||||
}, [tableMetaData, dotMenuOpen, keyboardHelpOpen])
|
||||
document.removeEventListener("keydown", down);
|
||||
};
|
||||
}, [tableMetaData, dotMenuOpen, keyboardHelpOpen]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setDotMenuOpen(false);
|
||||
}, [location.pathname])
|
||||
}, [location.pathname]);
|
||||
|
||||
function goToItem(path: string)
|
||||
{
|
||||
@ -162,73 +167,117 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
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()
|
||||
{
|
||||
let tableNames : string[]= [];
|
||||
let tableNames: string[] = [];
|
||||
metaData.tables.forEach((value: QTableMetaData, key: string) =>
|
||||
{
|
||||
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 labelB = metaData.tables.get(b).label ?? "";
|
||||
return (labelA.localeCompare(labelB));
|
||||
})
|
||||
return comparator(labelA, labelB);
|
||||
});
|
||||
|
||||
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`}>
|
||||
{
|
||||
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 &&
|
||||
<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 &&
|
||||
<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") &&
|
||||
<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.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>
|
||||
))
|
||||
)
|
||||
(
|
||||
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.Separator />
|
||||
</Command.Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function TablesSection()
|
||||
{
|
||||
let tableNames : string[]= [];
|
||||
let tableNames: string[] = [];
|
||||
metaData.tables.forEach((value: QTableMetaData, key: string) =>
|
||||
{
|
||||
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 labelB = metaData.tables.get(b).label ?? "";
|
||||
return (labelA.localeCompare(labelB));
|
||||
})
|
||||
return(
|
||||
return comparator(labelA, labelB);
|
||||
});
|
||||
return (
|
||||
<Command.Group heading="Tables">
|
||||
{
|
||||
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) =>
|
||||
{
|
||||
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 labelB = getFullAppLabel(metaData.appTree, b, 1, "") ?? "";
|
||||
return (labelA.localeCompare(labelB));
|
||||
})
|
||||
return comparator(labelA, labelB);
|
||||
});
|
||||
|
||||
return(
|
||||
return (
|
||||
<Command.Group heading="Apps">
|
||||
{
|
||||
appNames.map((appName: string, index: number) =>
|
||||
@ -276,33 +326,37 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function RecentlyViewedSection()
|
||||
{
|
||||
const history = HistoryUtils.get();
|
||||
const options = [] as any;
|
||||
history.entries.reverse().forEach((entry, index) =>
|
||||
options.push({label: `${entry.label} index`, id: index, key: index, path: entry.path, iconName: entry.iconName})
|
||||
)
|
||||
);
|
||||
|
||||
let appNames: string[] = [];
|
||||
metaData.apps.forEach((value: QAppMetaData, key: string) =>
|
||||
{
|
||||
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 labelB = metaData.apps.get(b).label ?? "";
|
||||
return (labelA.localeCompare(labelB));
|
||||
})
|
||||
return comparator(labelA, labelB);
|
||||
});
|
||||
|
||||
const entryMap = new Map<string, boolean>();
|
||||
return(
|
||||
return (
|
||||
<Command.Group heading="Recently Viewed Records">
|
||||
{
|
||||
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>
|
||||
)
|
||||
)
|
||||
@ -311,29 +365,90 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
);
|
||||
}
|
||||
|
||||
const containerElement = useRef(null)
|
||||
const containerElement = useRef(null);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function closeKeyboardHelp()
|
||||
{
|
||||
setKeyboardHelpOpen(false);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function closeDotMenu()
|
||||
{
|
||||
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 (
|
||||
<React.Fragment>
|
||||
<Box ref={containerElement} className="raycast" sx={{position: "relative", zIndex: 10_000}}>
|
||||
{
|
||||
<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"}}>
|
||||
<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>
|
||||
</Box>
|
||||
<Command.Loading />
|
||||
<Command.Loading />
|
||||
<Command.Separator />
|
||||
<Command.List>
|
||||
<Command.Empty>No results found.</Command.Empty>
|
||||
@ -381,6 +496,6 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
</Dialog>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
export default CommandMenu;
|
||||
|
@ -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);
|
||||
};
|
||||
@ -377,7 +377,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
widgetData.viewAllLink = null;
|
||||
widgetMetaData.showExportButton = false;
|
||||
|
||||
return <RecordGridWidget
|
||||
return Object.keys(childListWidgetData).length > 0 && (<RecordGridWidget
|
||||
key={`${formValues["tableName"]}-${modalDataChangedCounter}`}
|
||||
widgetMetaData={widgetMetaData}
|
||||
data={widgetData}
|
||||
@ -387,7 +387,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
addNewRecordCallback={() => openAddChildRecord(widgetMetaData.name, widgetData)}
|
||||
editRecordCallback={(rowIndex) => openEditChildRecord(widgetMetaData.name, widgetData, rowIndex)}
|
||||
deleteRecordCallback={(rowIndex) => deleteChildRecord(widgetMetaData.name, widgetData, rowIndex)}
|
||||
/>;
|
||||
/>);
|
||||
}
|
||||
|
||||
if (widgetMetaData.type == "filterAndColumnsSetup")
|
||||
@ -480,83 +480,164 @@ function EntityForm(props: Props): JSX.Element
|
||||
//////////////////
|
||||
// initial load //
|
||||
//////////////////
|
||||
if (!asyncLoadInited)
|
||||
useEffect(() =>
|
||||
{
|
||||
setAsyncLoadInited(true);
|
||||
(async () =>
|
||||
if (!asyncLoadInited)
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
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) =>
|
||||
setAsyncLoadInited(true);
|
||||
(async () =>
|
||||
{
|
||||
const widget = metaData?.widgets.get(section.widgetName);
|
||||
if (widget)
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
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 (true);
|
||||
}
|
||||
}
|
||||
return (false);
|
||||
});
|
||||
setTableSections(tableSections);
|
||||
|
||||
return (false);
|
||||
});
|
||||
setTableSections(tableSections);
|
||||
|
||||
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)
|
||||
const fieldArray = [] as QFieldMetaData[];
|
||||
const sortedKeys = [...tableMetaData.fields.keys()].sort();
|
||||
sortedKeys.forEach((key) =>
|
||||
{
|
||||
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);
|
||||
const fieldMetaData = tableMetaData.fields.get(key);
|
||||
fieldArray.push(fieldMetaData);
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// these checks are only for updating records, if copying, it is actually an insert, which is checked after this block //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (!props.isCopy)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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}`);
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
@ -567,201 +648,123 @@ function EntityForm(props: Props): JSX.Element
|
||||
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. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// make sure all initialValues are properly formatted for the form //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
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)
|
||||
if (fieldMetaData.type == QFieldType.DATE_TIME && initialValues[fieldMetaData.name])
|
||||
{
|
||||
initialValues[fieldName] = defaultValue;
|
||||
initialValues[fieldMetaData.name] = ValueUtils.formatDateTimeValueForForm(initialValues[fieldMetaData.name]);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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)
|
||||
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 results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]]);
|
||||
if (results && results.length > 0)
|
||||
const fieldName = section.fieldNames[j];
|
||||
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 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)
|
||||
if (sectionDynamicFormFields.length === 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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)
|
||||
else
|
||||
{
|
||||
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
|
||||
{
|
||||
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);
|
||||
newChildListWidgetData[section.widgetName] = widgetData;
|
||||
}
|
||||
setT1SectionName(t1sectionName);
|
||||
setT1Section(t1section);
|
||||
setNonT1Sections(nonT1Sections);
|
||||
setFormFields(dynamicFormFieldsBySection);
|
||||
setValidations(Yup.object().shape(formValidations));
|
||||
setRenderedWidgetSections(newRenderedWidgetSections);
|
||||
setChildListWidgetData(newChildListWidgetData);
|
||||
|
||||
//////////////////////////////////////
|
||||
// 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();
|
||||
})();
|
||||
}
|
||||
forceUpdate();
|
||||
})();
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
@ -881,16 +884,28 @@ function EntityForm(props: Props): JSX.Element
|
||||
let haveAssociationsToPost = false;
|
||||
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");
|
||||
if (!manageAssociationName)
|
||||
{
|
||||
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)
|
||||
|
@ -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 {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||
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 {Alert, Collapse} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
@ -108,32 +107,38 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
|
||||
let columns: QQueryColumns = null;
|
||||
let usingDefaultEmptyFilter = false;
|
||||
let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter;
|
||||
const defaultFilterFields = getDefaultFilterFieldNames(widgetMetaData);
|
||||
if (!queryFilter)
|
||||
{
|
||||
queryFilter = new QQueryFilter();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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
|
||||
if (defaultFilterFields?.length == 0)
|
||||
{
|
||||
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"])
|
||||
{
|
||||
@ -202,7 +207,7 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
|
||||
////////////////////////////////////////////////////////////////////
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -40,14 +40,14 @@ import {Link, useNavigate} from "react-router-dom";
|
||||
export interface ChildRecordListData extends WidgetData
|
||||
{
|
||||
title: string;
|
||||
queryOutput: {records: {values: any}[]}
|
||||
queryOutput: { records: { values: any }[] };
|
||||
childTableMetaData: QTableMetaData;
|
||||
tablePath: string;
|
||||
viewAllLink: string;
|
||||
totalRows: number;
|
||||
canAddChildRecord: boolean;
|
||||
defaultValuesForNewChildRecords: {[fieldName: string]: any};
|
||||
disabledFieldsForNewChildRecords: {[fieldName: string]: any};
|
||||
defaultValuesForNewChildRecords: { [fieldName: string]: any };
|
||||
disabledFieldsForNewChildRecords: { [fieldName: string]: any };
|
||||
}
|
||||
|
||||
interface Props
|
||||
@ -75,9 +75,9 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
{
|
||||
const instance = useRef({timer: null});
|
||||
const [rows, setRows] = useState([]);
|
||||
const [records, setRecords] = useState([] as QRecord[])
|
||||
const [records, setRecords] = useState([] as QRecord[]);
|
||||
const [columns, setColumns] = useState([]);
|
||||
const [allColumns, setAllColumns] = useState([])
|
||||
const [allColumns, setAllColumns] = useState([]);
|
||||
const [csv, setCsv] = useState(null as string);
|
||||
const [fileName, setFileName] = useState(null as string);
|
||||
const [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) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const allColumns = [... columns];
|
||||
const allColumns = [...columns];
|
||||
setAllColumns(JSON.parse(JSON.stringify(columns)));
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// 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++)
|
||||
{
|
||||
if(data.defaultValuesForNewChildRecords[columns[i].field])
|
||||
if (data.defaultValuesForNewChildRecords[columns[i].field])
|
||||
{
|
||||
columns.splice(i, 1);
|
||||
i--
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -131,7 +131,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
////////////////////////////////////
|
||||
// add actions cell, if available //
|
||||
////////////////////////////////////
|
||||
if(allowRecordEdit || allowRecordDelete)
|
||||
if (allowRecordEdit || allowRecordDelete)
|
||||
{
|
||||
columns.unshift({
|
||||
field: "_actions",
|
||||
@ -145,19 +145,19 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
return <Box>
|
||||
{allowRecordEdit && <IconButton onClick={() => editRecordCallback(params.row.__rowIndex)}><Icon>edit</Icon></IconButton>}
|
||||
{allowRecordDelete && <IconButton onClick={() => deleteRecordCallback(params.row.__rowIndex)}><Icon>delete</Icon></IconButton>}
|
||||
</Box>
|
||||
</Box>;
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setRows(rows);
|
||||
setRecords(records)
|
||||
setRecords(records);
|
||||
setColumns(columns);
|
||||
|
||||
let csv = "";
|
||||
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";
|
||||
|
||||
@ -165,8 +165,8 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
{
|
||||
for (let j = 0; j < allColumns.length; j++)
|
||||
{
|
||||
const value = records[i].displayValues.get(allColumns[j].field) ?? records[i].values.get(allColumns[j].field)
|
||||
csv += `${j > 0 ? "," : ""}"${ValueUtils.cleanForCsv(value)}"`
|
||||
const value = records[i].displayValues.get(allColumns[j].field) ?? records[i].values.get(allColumns[j].field);
|
||||
csv += `${j > 0 ? "," : ""}"${ValueUtils.cleanForCsv(value)}"`;
|
||||
}
|
||||
csv += "\n";
|
||||
}
|
||||
@ -182,13 +182,13 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
// view all link //
|
||||
///////////////////
|
||||
const labelAdditionalElementsLeft: JSX.Element[] = [];
|
||||
if(data && data.viewAllLink)
|
||||
if (data && data.viewAllLink)
|
||||
{
|
||||
labelAdditionalElementsLeft.push(
|
||||
<Typography key={"viewAllLink"} variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative">
|
||||
<Link to={data.viewAllLink}>View All</Link>
|
||||
</Typography>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////
|
||||
@ -200,10 +200,10 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
{
|
||||
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."
|
||||
if(data.viewAllLink)
|
||||
tooltipTitle = "Export these " + data.queryOutput.records.length + " records.";
|
||||
if (data.viewAllLink)
|
||||
{
|
||||
tooltipTitle += "\nClick View All to export all records.";
|
||||
}
|
||||
@ -212,17 +212,17 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
|
||||
const onExportClick = () =>
|
||||
{
|
||||
if(csv)
|
||||
if (csv)
|
||||
{
|
||||
HtmlUtils.download(fileName, csv);
|
||||
}
|
||||
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(
|
||||
<Typography key={"exportButton"} variant="body2" px={0} display="inline" position="relative">
|
||||
@ -234,15 +234,15 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
////////////////////
|
||||
// add new button //
|
||||
////////////////////
|
||||
const labelAdditionalComponentsRight: LabelComponent[] = []
|
||||
if(data && data.canAddChildRecord)
|
||||
const labelAdditionalComponentsRight: LabelComponent[] = [];
|
||||
if (data && data.canAddChildRecord)
|
||||
{
|
||||
let disabledFields = data.disabledFieldsForNewChildRecords;
|
||||
if(!disabledFields)
|
||||
if (!disabledFields)
|
||||
{
|
||||
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) =>
|
||||
{
|
||||
if(disableRowClick)
|
||||
if (disableRowClick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(async () =>
|
||||
{
|
||||
const qInstance = await qController.loadMetaData()
|
||||
let tablePath = qInstance.getTablePathByName(data.childTableMetaData.name)
|
||||
if(tablePath)
|
||||
const qInstance = await qController.loadMetaData();
|
||||
let tablePath = qInstance.getTablePathByName(data.childTableMetaData.name);
|
||||
if (tablePath)
|
||||
{
|
||||
tablePath = `${tablePath}/${params.row[data.childTableMetaData.primaryKeyField]}`;
|
||||
DataGridUtils.handleRowClick(tablePath, event, gridMouseDownX, gridMouseDownY, navigate, instance);
|
||||
@ -276,7 +276,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
*******************************************************************************/
|
||||
function CustomToolbar()
|
||||
{
|
||||
const handleMouseDown: GridEventListener<"cellMouseDown"> = ( params, event, details ) =>
|
||||
const handleMouseDown: GridEventListener<"cellMouseDown"> = (params, event, details) =>
|
||||
{
|
||||
setGridMouseDownX(event.clientX);
|
||||
setGridMouseDownY(event.clientY);
|
||||
@ -304,8 +304,8 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
|
||||
labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}}
|
||||
>
|
||||
<Box mx={-2} mb={-3}>
|
||||
<Box className="recordGridWidget">
|
||||
<Box mx={-3} mb={-3}>
|
||||
<Box>
|
||||
<DataGridPro
|
||||
autoHeight
|
||||
sx={{
|
||||
|
@ -421,7 +421,7 @@ input[type="search"]::-webkit-search-results-decoration
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
|
||||
.dashboard-order-release-icon
|
||||
.dashboard-table-actions-icon
|
||||
{
|
||||
font-size: 1.5rem !important;
|
||||
position: relative;
|
||||
@ -711,11 +711,6 @@ input[type="search"]::-webkit-search-results-decoration
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.recordView .widget .recordGridWidget
|
||||
{
|
||||
margin: -8px;
|
||||
}
|
||||
|
||||
.MuiPickersDay-root.Mui-selected, .MuiPickersDay-root.MuiPickersDay-dayWithMargin:hover
|
||||
{
|
||||
color: white;
|
||||
|
Reference in New Issue
Block a user