Add tableSection.isHidden; table.capabilities; adornmentType.CODE_EDITOR

This commit is contained in:
2022-11-01 16:29:27 -05:00
parent 860c79c405
commit 61394f4a52
10 changed files with 149 additions and 44 deletions

View File

@ -13,7 +13,7 @@
"@fullcalendar/interaction": "5.10.0", "@fullcalendar/interaction": "5.10.0",
"@fullcalendar/react": "5.10.0", "@fullcalendar/react": "5.10.0",
"@fullcalendar/timegrid": "5.10.0", "@fullcalendar/timegrid": "5.10.0",
"@kingsrook/qqq-frontend-core": "1.0.30", "@kingsrook/qqq-frontend-core": "1.0.31",
"@mui/icons-material": "5.4.1", "@mui/icons-material": "5.4.1",
"@mui/material": "5.4.1", "@mui/material": "5.4.1",
"@mui/styled-engine": "5.4.1", "@mui/styled-engine": "5.4.1",
@ -75,7 +75,7 @@
"geff-ham": "rm -rf node_modules/ && rm -rf package-lock.json && npm install --legacy-peer-deps && npm start", "geff-ham": "rm -rf node_modules/ && rm -rf package-lock.json && npm install --legacy-peer-deps && npm start",
"install-legacy-peer-deps": "npm install --legacy-peer-deps", "install-legacy-peer-deps": "npm install --legacy-peer-deps",
"prepublishOnly": "tsc -p ./ --outDir lib/", "prepublishOnly": "tsc -p ./ --outDir lib/",
"start": "react-scripts start", "start": "BROWSER=none react-scripts start",
"test": "react-scripts test", "test": "react-scripts test",
"cypress:open": "cypress open" "cypress:open": "cypress open"
}, },

View File

@ -19,6 +19,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
@ -73,6 +74,8 @@ function EntityForm({table, id}: Props): JSX.Element
const [tableSections, setTableSections] = useState(null as QTableSection[]); const [tableSections, setTableSections] = useState(null as QTableSection[]);
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
const [noCapabilityError, setNoCapabilityError] = useState(null as string);
const {pageHeader, setPageHeader} = useContext(QContext); const {pageHeader, setPageHeader} = useContext(QContext);
const navigate = useNavigate(); const navigate = useNavigate();
@ -140,11 +143,21 @@ function EntityForm({table, id}: Props): JSX.Element
}); });
setFormValues(formValues); setFormValues(formValues);
if(!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
{
setNoCapabilityError("You may not edit records in this table");
}
} }
else else
{ {
setFormTitle(`Creating New ${tableMetaData?.label}`); setFormTitle(`Creating New ${tableMetaData?.label}`);
setPageHeader(`Creating New ${tableMetaData?.label}`); setPageHeader(`Creating New ${tableMetaData?.label}`);
if(!tableMetaData.capabilities.has(Capability.TABLE_INSERT))
{
setNoCapabilityError("You may not create records in this table");
}
} }
setInitialValues(initialValues); setInitialValues(initialValues);
@ -168,6 +181,11 @@ function EntityForm({table, id}: Props): JSX.Element
const section = tableSections[i]; const section = tableSections[i];
const sectionDynamicFormFields: any[] = []; const sectionDynamicFormFields: any[] = [];
if(section.isHidden)
{
continue;
}
for (let j = 0; j < section.fieldNames.length; j++) for (let j = 0; j < section.fieldNames.length; j++)
{ {
const fieldName = section.fieldNames[j]; const fieldName = section.fieldNames[j];
@ -277,6 +295,19 @@ function EntityForm({table, id}: Props): JSX.Element
const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`; const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
if(noCapabilityError)
{
return <MDBox mb={3}>
<Grid container spacing={3}>
<Grid item xs={12}>
<MDBox mb={3}>
<Alert severity="error">{noCapabilityError}</Alert>
</MDBox>
</Grid>
</Grid>
</MDBox>;
}
return ( return (
<MDBox mb={3}> <MDBox mb={3}>
<Grid container spacing={3}> <Grid container spacing={3}>

View File

@ -71,7 +71,7 @@ interface QDeleteButtonProps
export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
{ {
return ( return (
<MDBox ml={3} mr={3} width={standardWidth}> <MDBox ml={3} width={standardWidth}>
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}> <MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}>
Delete Delete
</MDButton> </MDButton>
@ -82,7 +82,7 @@ export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
export function QEditButton(): JSX.Element export function QEditButton(): JSX.Element
{ {
return ( return (
<MDBox width={standardWidth}> <MDBox ml={3} width={standardWidth}>
<Link to="edit"> <Link to="edit">
<MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}> <MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}>
Edit Edit

View File

@ -59,6 +59,11 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: P
const sidebarEntries = [] as SidebarEntry[]; const sidebarEntries = [] as SidebarEntry[];
tableSections && tableSections.forEach((section, index) => tableSections && tableSections.forEach((section, index) =>
{ {
if(section.isHidden)
{
return;
}
if (index === 1 && widgetMetaDataList) if (index === 1 && widgetMetaDataList)
{ {
widgetMetaDataList.forEach((widget) => widgetMetaDataList.forEach((widget) =>

View File

@ -20,6 +20,7 @@
*/ */
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType"; import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
@ -584,9 +585,10 @@ function EntityList({table, launchProcess}: Props): JSX.Element
const row: any = {}; const row: any = {};
fields.forEach((field) => fields.forEach((field) =>
{ {
const value = QValueUtils.getDisplayValue(field, record); const value = QValueUtils.getDisplayValue(field, record, "query");
if (typeof value !== "string") if (typeof value !== "string")
{ {
console.log(`Need to render [${field.name}]`);
columnsToRender[field.name] = true; columnsToRender[field.name] = true;
} }
row[field.name] = value; row[field.name] = value;
@ -1146,18 +1148,27 @@ function EntityList({table, launchProcess}: Props): JSX.Element
onClose={closeActionsMenu} onClose={closeActionsMenu}
keepMounted keepMounted
> >
<MenuItem onClick={bulkLoadClicked}> {
<ListItemIcon><Icon>library_add</Icon></ListItemIcon> table.capabilities.has(Capability.TABLE_INSERT) &&
Bulk Load <MenuItem onClick={bulkLoadClicked}>
</MenuItem> <ListItemIcon><Icon>library_add</Icon></ListItemIcon>
<MenuItem onClick={bulkEditClicked}> Bulk Load
<ListItemIcon><Icon>edit</Icon></ListItemIcon> </MenuItem>
Bulk Edit }
</MenuItem> {
<MenuItem onClick={bulkDeleteClicked}> table.capabilities.has(Capability.TABLE_UPDATE) &&
<ListItemIcon><Icon>delete</Icon></ListItemIcon> <MenuItem onClick={bulkEditClicked}>
Bulk Delete <ListItemIcon><Icon>edit</Icon></ListItemIcon>
</MenuItem> Bulk Edit
</MenuItem>
}
{
table.capabilities.has(Capability.TABLE_DELETE) &&
<MenuItem onClick={bulkDeleteClicked}>
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
Bulk Delete
</MenuItem>
}
{tableProcesses.length > 0 && <Divider />} {tableProcesses.length > 0 && <Divider />}
{tableProcesses.map((process) => ( {tableProcesses.map((process) => (
<MenuItem key={process.name} onClick={() => processClicked(process)}> <MenuItem key={process.name} onClick={() => processClicked(process)}>
@ -1165,6 +1176,13 @@ function EntityList({table, launchProcess}: Props): JSX.Element
{process.label} {process.label}
</MenuItem> </MenuItem>
))} ))}
{
tableProcesses.length == 0 && !table.capabilities.has(Capability.TABLE_INSERT) && !table.capabilities.has(Capability.TABLE_UPDATE) && !table.capabilities.has(Capability.TABLE_DELETE) &&
<MenuItem disabled>
<ListItemIcon><Icon>block</Icon></ListItemIcon>
<i>No actions available</i>
</MenuItem>
}
</Menu> </Menu>
); );
@ -1226,7 +1244,10 @@ function EntityList({table, launchProcess}: Props): JSX.Element
{renderActionsMenu} {renderActionsMenu}
</MDBox> </MDBox>
<QCreateNewButton /> {
table.capabilities.has(Capability.TABLE_INSERT) &&
<QCreateNewButton />
}
</MDBox> </MDBox>
<Card> <Card>
@ -1244,6 +1265,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
disableSelectionOnClick disableSelectionOnClick
autoHeight autoHeight
rows={rows} rows={rows}
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
columns={columnsModel} columns={columnsModel}
rowBuffer={10} rowBuffer={10}
rowCount={totalRecords === null ? 0 : totalRecords} rowCount={totalRecords === null ? 0 : totalRecords}

View File

@ -423,7 +423,7 @@ function EntityDeveloperView({table}: Props): JSX.Element
</Card> </Card>
{ {
associatedScripts.map((object) => associatedScripts && associatedScripts.map((object) =>
{ {
let fieldName = object.associatedScript?.fieldName; let fieldName = object.associatedScript?.fieldName;
let field = tableMetaData.fields.get(fieldName); let field = tableMetaData.fields.get(fieldName);

View File

@ -20,6 +20,7 @@
*/ */
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException"; import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection"; import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
@ -234,6 +235,11 @@ function EntityView({table, launchProcess}: Props): JSX.Element
for (let i = 0; i < tableSections.length; i++) for (let i = 0; i < tableSections.length; i++)
{ {
const section = tableSections[i]; const section = tableSections[i];
if(section.isHidden)
{
continue;
}
sectionFieldElements.set( sectionFieldElements.set(
section.name, section.name,
<MDBox key={section.name} display="flex" flexDirection="column" py={1} pr={2}> <MDBox key={section.name} display="flex" flexDirection="column" py={1} pr={2}>
@ -244,7 +250,7 @@ function EntityView({table, launchProcess}: Props): JSX.Element
{tableMetaData.fields.get(fieldName).label}: {tableMetaData.fields.get(fieldName).label}:
</MDTypography> </MDTypography>
<MDTypography variant="button" fontWeight="regular" color="text"> <MDTypography variant="button" fontWeight="regular" color="text">
{QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record)} {QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")}
</MDTypography> </MDTypography>
</MDBox> </MDBox>
)) ))
@ -313,27 +319,33 @@ function EntityView({table, launchProcess}: Props): JSX.Element
onClose={closeActionsMenu} onClose={closeActionsMenu}
keepMounted keepMounted
> >
<MenuItem onClick={() => navigate("edit")}>
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
Edit
</MenuItem>
<MenuItem onClick={() =>
{ {
setActionsMenu(null); table.capabilities.has(Capability.TABLE_UPDATE) &&
handleClickDeleteButton(); <MenuItem onClick={() => navigate("edit")}>
}} <ListItemIcon><Icon>edit</Icon></ListItemIcon>
> Edit
<ListItemIcon><Icon>delete</Icon></ListItemIcon> </MenuItem>
Delete }
</MenuItem> {
{tableProcesses.length > 0 && <Divider />} table.capabilities.has(Capability.TABLE_DELETE) &&
<MenuItem onClick={() =>
{
setActionsMenu(null);
handleClickDeleteButton();
}}
>
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
Delete
</MenuItem>
}
{tableProcesses.length > 0 && (table.capabilities.has(Capability.TABLE_UPDATE) || table.capabilities.has(Capability.TABLE_DELETE)) && <Divider />}
{tableProcesses.map((process) => ( {tableProcesses.map((process) => (
<MenuItem key={process.name} onClick={() => processClicked(process)}> <MenuItem key={process.name} onClick={() => processClicked(process)}>
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon> <ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
{process.label} {process.label}
</MenuItem> </MenuItem>
))} ))}
<Divider /> {tableProcesses.length > 0 && <Divider />}
<MenuItem onClick={() => navigate("dev")}> <MenuItem onClick={() => navigate("dev")}>
<ListItemIcon><Icon>data_object</Icon></ListItemIcon> <ListItemIcon><Icon>data_object</Icon></ListItemIcon>
Developer Mode Developer Mode
@ -433,8 +445,12 @@ function EntityView({table, launchProcess}: Props): JSX.Element
)) : null} )) : null}
<MDBox component="form" p={3}> <MDBox component="form" p={3}>
<Grid container justifyContent="flex-end" spacing={3}> <Grid container justifyContent="flex-end" spacing={3}>
<QDeleteButton onClickHandler={handleClickDeleteButton} /> {
<QEditButton /> table.capabilities.has(Capability.TABLE_DELETE) && <QDeleteButton onClickHandler={handleClickDeleteButton} />
}
{
table.capabilities.has(Capability.TABLE_UPDATE) && <QEditButton />
}
</Grid> </Grid>
</MDBox> </MDBox>

View File

@ -253,7 +253,7 @@ function QValidationReview({
{" "} {" "}
&nbsp; &nbsp;
{" "} {" "}
{QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex])} {QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex], "view")}
</MDBox> </MDBox>
)) ))
} }

View File

@ -362,8 +362,11 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod
{localTableSections.map((section: QTableSection, index: number) => {localTableSections.map((section: QTableSection, index: number) =>
{ {
const name = section.name const name = section.name
console.log(formData);
console.log(section.fieldNames); if(section.isHidden)
{
return ;
}
const sectionFormFields = {}; const sectionFormFields = {};
for(let i = 0; i<section.fieldNames.length; i++) for(let i = 0; i<section.fieldNames.length; i++)
@ -424,7 +427,7 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod
: &nbsp; : &nbsp;
</MDTypography> </MDTypography>
<MDTypography variant="button" fontWeight="regular" color="text"> <MDTypography variant="button" fontWeight="regular" color="text">
{QValueUtils.getValueForDisplay(field, processValues[field.name])} {QValueUtils.getValueForDisplay(field, processValues[field.name], "view")}
</MDTypography> </MDTypography>
</MDBox> </MDBox>
))} ))}

View File

@ -25,8 +25,9 @@ import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QField
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import "datejs"; import "datejs";
import {Chip, Icon} from "@mui/material"; import {Chip, Icon, Typography} from "@mui/material";
import React, {Fragment} from "react"; import React, {Fragment} from "react";
import AceEditor from "react-ace";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import QClient from "qqq/utils/QClient"; import QClient from "qqq/utils/QClient";
@ -66,19 +67,19 @@ class QValueUtils
** When you have a field, and a record - call this method to get a string or ** When you have a field, and a record - call this method to get a string or
** element back to display the field's value. ** element back to display the field's value.
*******************************************************************************/ *******************************************************************************/
public static getDisplayValue(field: QFieldMetaData, record: QRecord): string | JSX.Element public static getDisplayValue(field: QFieldMetaData, record: QRecord, usage: "view" | "query" = "view"): string | JSX.Element
{ {
const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined; const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined;
const rawValue = record.values ? record.values.get(field.name) : undefined; const rawValue = record.values ? record.values.get(field.name) : undefined;
return QValueUtils.getValueForDisplay(field, rawValue, displayValue); return QValueUtils.getValueForDisplay(field, rawValue, displayValue, usage);
} }
/******************************************************************************* /*******************************************************************************
** When you have a field and a value (either just a raw value, or a raw and ** When you have a field and a value (either just a raw value, or a raw and
** display value), call this method to get a string Element to display. ** display value), call this method to get a string Element to display.
*******************************************************************************/ *******************************************************************************/
public static getValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any = rawValue): string | JSX.Element public static getValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any = rawValue, usage: "view" | "query" = "view"): string | JSX.Element
{ {
if (field.hasAdornment(AdornmentType.LINK)) if (field.hasAdornment(AdornmentType.LINK))
{ {
@ -141,6 +142,28 @@ class QValueUtils
return (<Chip label={displayValue} color={color} icon={iconElement} size="small" variant="outlined" sx={{fontWeight: 500}} />); return (<Chip label={displayValue} color={color} icon={iconElement} size="small" variant="outlined" sx={{fontWeight: 500}} />);
} }
if (field.hasAdornment(AdornmentType.CODE_EDITOR))
{
if(usage === "view")
{
return (<AceEditor
mode="javascript"
theme="github"
name={field.name}
editorProps={{$blockScrolling: true}}
value={rawValue}
readOnly
width="100%"
showPrintMargin={false}
height="200px"
/>);
}
else
{
return rawValue;
}
}
return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue)); return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue));
} }
@ -227,6 +250,11 @@ class QValueUtils
public static breakTextIntoLines(value: string): JSX.Element public static breakTextIntoLines(value: string): JSX.Element
{ {
if(!value)
{
return <Fragment />;
}
return ( return (
<Fragment> <Fragment>
{value.split(/\n/).map((value: string, index: number) => ( {value.split(/\n/).map((value: string, index: number) => (