mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Merged main into feature/select-text-on-child-tables
This commit is contained in:
@ -29,8 +29,8 @@ import React, {SyntheticEvent} from "react";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
|
||||
const AntSwitch = styled(Switch)(({theme}) => ({
|
||||
width: 28,
|
||||
height: 16,
|
||||
width: 32,
|
||||
height: 20,
|
||||
padding: 0,
|
||||
display: "flex",
|
||||
"&:active": {
|
||||
@ -54,18 +54,19 @@ const AntSwitch = styled(Switch)(({theme}) => ({
|
||||
},
|
||||
"& .MuiSwitch-thumb": {
|
||||
boxShadow: "0 2px 4px 0 rgb(0 35 11 / 20%)",
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: 6,
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 8,
|
||||
transition: theme.transitions.create([ "width" ], {
|
||||
duration: 200,
|
||||
}),
|
||||
},
|
||||
"&.nullSwitch .MuiSwitch-thumb": {
|
||||
width: 24,
|
||||
width: 28,
|
||||
},
|
||||
"& .MuiSwitch-track": {
|
||||
borderRadius: 16 / 2,
|
||||
height: 20,
|
||||
borderRadius: 20 / 2,
|
||||
opacity: 1,
|
||||
backgroundColor:
|
||||
theme.palette.mode === "dark" ? "rgba(255,255,255,.35)" : "rgba(0,0,0,.25)",
|
||||
@ -106,9 +107,9 @@ function BooleanFieldSwitch({name, label, value, isDisabled}: Props) : JSX.Eleme
|
||||
return (
|
||||
<Box bgcolor={isDisabled ? colors.grey[200] : ""}>
|
||||
<InputLabel shrink={true}>{label}</InputLabel>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Stack direction="row" spacing={1} alignItems="center" height="37px">
|
||||
<Typography
|
||||
fontSize="0.875rem"
|
||||
fontSize="1rem"
|
||||
color={value === false ? "auto" : "#bfbfbf" }
|
||||
onClick={(e) => setSwitch(e, false)}
|
||||
sx={{cursor: value === false || isDisabled ? "inherit" : "pointer"}}>
|
||||
@ -116,7 +117,7 @@ function BooleanFieldSwitch({name, label, value, isDisabled}: Props) : JSX.Eleme
|
||||
</Typography>
|
||||
<AntSwitch className={classNullSwitch} name={name} checked={value} onClick={toggleSwitch} disabled={isDisabled} />
|
||||
<Typography
|
||||
fontSize="0.875rem"
|
||||
fontSize="1rem"
|
||||
color={value === true ? "auto" : "#bfbfbf"}
|
||||
onClick={(e) => setSwitch(e, true)}
|
||||
sx={{cursor: value === true || isDisabled ? "inherit" : "pointer"}}>
|
||||
|
@ -32,6 +32,7 @@ import React, {useState} from "react";
|
||||
import QDynamicFormField from "qqq/components/forms/DynamicFormField";
|
||||
import DynamicSelect from "qqq/components/forms/DynamicSelect";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import HelpContent from "qqq/components/misc/HelpContent";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
|
||||
interface Props
|
||||
@ -41,16 +42,13 @@ interface Props
|
||||
bulkEditMode?: boolean;
|
||||
bulkEditSwitchChangeHandler?: any;
|
||||
record?: QRecord;
|
||||
helpRoles?: string[];
|
||||
helpContentKeyPrefix?: string;
|
||||
}
|
||||
|
||||
function QDynamicForm(props: Props): JSX.Element
|
||||
function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHandler, record, helpRoles, helpContentKeyPrefix}: Props): JSX.Element
|
||||
{
|
||||
const {
|
||||
formData, formLabel, bulkEditMode, bulkEditSwitchChangeHandler,
|
||||
} = props;
|
||||
const {
|
||||
formFields, values, errors, touched,
|
||||
} = formData;
|
||||
const {formFields, values, errors, touched} = formData;
|
||||
|
||||
const formikProps = useFormikContext();
|
||||
const [fileName, setFileName] = useState(null as string);
|
||||
@ -70,8 +68,8 @@ function QDynamicForm(props: Props): JSX.Element
|
||||
{
|
||||
setFileName(null);
|
||||
formikProps.setFieldValue(fieldName, null);
|
||||
props.record?.values.delete(fieldName)
|
||||
props.record?.displayValues.delete(fieldName)
|
||||
record?.values.delete(fieldName)
|
||||
record?.displayValues.delete(fieldName)
|
||||
};
|
||||
|
||||
const bulkEditSwitchChanged = (name: string, value: boolean) =>
|
||||
@ -79,6 +77,7 @@ function QDynamicForm(props: Props): JSX.Element
|
||||
bulkEditSwitchChangeHandler(name, value);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box lineHeight={0}>
|
||||
@ -96,29 +95,38 @@ function QDynamicForm(props: Props): JSX.Element
|
||||
&& Object.keys(formFields).map((fieldName: any) =>
|
||||
{
|
||||
const field = formFields[fieldName];
|
||||
if (field.omitFromQDynamicForm)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (values[fieldName] === undefined)
|
||||
{
|
||||
values[fieldName] = "";
|
||||
}
|
||||
|
||||
if (field.omitFromQDynamicForm)
|
||||
let formattedHelpContent = <HelpContent helpContents={field.fieldMetaData.helpContents} roles={helpRoles} helpContentKey={`${helpContentKeyPrefix ?? ""}field:${fieldName}`} />;
|
||||
if(formattedHelpContent)
|
||||
{
|
||||
return null;
|
||||
formattedHelpContent = <Box color="#757575" fontSize="0.875rem" mt="-0.25rem">{formattedHelpContent}</Box>
|
||||
}
|
||||
|
||||
const labelElement = <Box fontSize="1rem" fontWeight="500" marginBottom="0.25rem">
|
||||
<label htmlFor={field.name}>{field.label}</label>
|
||||
</Box>
|
||||
|
||||
if (field.type === "file")
|
||||
{
|
||||
const pseudoField = new QFieldMetaData({name: fieldName, type: QFieldType.BLOB});
|
||||
return (
|
||||
<Grid item xs={12} sm={6} key={fieldName}>
|
||||
<Box mb={1.5}>
|
||||
|
||||
<InputLabel shrink={true}>{field.label}</InputLabel>
|
||||
{labelElement}
|
||||
{
|
||||
props.record && props.record.values.get(fieldName) && <Box fontSize="0.875rem" pb={1}>
|
||||
record && record.values.get(fieldName) && <Box fontSize="0.875rem" pb={1}>
|
||||
Current File:
|
||||
<Box display="inline-flex" pl={1}>
|
||||
{ValueUtils.getDisplayValue(pseudoField, props.record, "view")}
|
||||
{ValueUtils.getDisplayValue(pseudoField, record, "view")}
|
||||
<Tooltip placement="bottom" title="Remove current file">
|
||||
<Icon className="blobIcon" fontSize="small" onClick={(e) => removeFile(fieldName)}>delete</Icon>
|
||||
</Tooltip>
|
||||
@ -162,18 +170,20 @@ function QDynamicForm(props: Props): JSX.Element
|
||||
|
||||
return (
|
||||
<Grid item xs={12} sm={6} key={fieldName}>
|
||||
{labelElement}
|
||||
<DynamicSelect
|
||||
tableName={field.possibleValueProps.tableName}
|
||||
processName={field.possibleValueProps.processName}
|
||||
fieldName={fieldName}
|
||||
isEditable={field.isEditable}
|
||||
fieldLabel={field.label}
|
||||
fieldLabel=""
|
||||
initialValue={values[fieldName]}
|
||||
initialDisplayValue={field.possibleValueProps.initialDisplayValue}
|
||||
bulkEditMode={bulkEditMode}
|
||||
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
|
||||
otherValues={otherValuesMap}
|
||||
/>
|
||||
{formattedHelpContent}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -182,9 +192,11 @@ function QDynamicForm(props: Props): JSX.Element
|
||||
// todo? placeholder={password.placeholder}
|
||||
return (
|
||||
<Grid item xs={12} sm={6} key={fieldName}>
|
||||
{labelElement}
|
||||
<QDynamicFormField
|
||||
id={field.name}
|
||||
type={field.type}
|
||||
label={field.label}
|
||||
label=""
|
||||
isEditable={field.isEditable}
|
||||
name={fieldName}
|
||||
displayFormat={field.displayFormat}
|
||||
@ -195,6 +207,7 @@ function QDynamicForm(props: Props): JSX.Element
|
||||
success={`${values[fieldName]}` !== "" && !errors[fieldName] && touched[fieldName]}
|
||||
formFieldObject={field}
|
||||
/>
|
||||
{formattedHelpContent}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
@ -207,6 +220,7 @@ function QDynamicForm(props: Props): JSX.Element
|
||||
QDynamicForm.defaultProps = {
|
||||
formLabel: undefined,
|
||||
bulkEditMode: false,
|
||||
helpRoles: ["ALL_SCREENS"],
|
||||
bulkEditSwitchChangeHandler: () =>
|
||||
{
|
||||
},
|
||||
|
@ -25,6 +25,7 @@ import Switch from "@mui/material/Switch";
|
||||
import {ErrorMessage, Field, useFormikContext} from "formik";
|
||||
import React, {useState} from "react";
|
||||
import AceEditor from "react-ace";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import BooleanFieldSwitch from "qqq/components/forms/BooleanFieldSwitch";
|
||||
import MDInput from "qqq/components/legacy/MDInput";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
@ -52,6 +53,7 @@ function QDynamicFormField({
|
||||
{
|
||||
const [switchChecked, setSwitchChecked] = useState(false);
|
||||
const [isDisabled, setIsDisabled] = useState(!isEditable || bulkEditMode);
|
||||
const {inputBorderColor} = colors;
|
||||
|
||||
const {setFieldValue} = useFormikContext();
|
||||
|
||||
@ -122,7 +124,7 @@ function QDynamicFormField({
|
||||
width="100%"
|
||||
height="300px"
|
||||
value={value}
|
||||
style={{border: "1px solid gray"}}
|
||||
style={{border: `1px solid ${inputBorderColor}`, borderRadius: "0.75rem"}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@ -131,7 +133,7 @@ function QDynamicFormField({
|
||||
{
|
||||
field = (
|
||||
<>
|
||||
<Field {...rest} onWheel={handleOnWheel} name={name} type={type} as={MDInput} variant="standard" label={label} InputLabelProps={inputLabelProps} InputProps={inputProps} fullWidth disabled={isDisabled}
|
||||
<Field {...rest} onWheel={handleOnWheel} name={name} type={type} as={MDInput} variant="outlined" label={label} InputLabelProps={inputLabelProps} InputProps={inputProps} fullWidth disabled={isDisabled}
|
||||
onKeyPress={(e: any) =>
|
||||
{
|
||||
if (e.key === "Enter")
|
||||
@ -171,6 +173,14 @@ function QDynamicFormField({
|
||||
id={`bulkEditSwitch-${name}`}
|
||||
checked={switchChecked}
|
||||
onClick={bulkEditSwitchChanged}
|
||||
sx={{top: "-4px",
|
||||
"& .MuiSwitch-track": {
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
top: -3,
|
||||
position: "relative"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box width="100%" sx={{background: (type == "checkbox" && isDisabled) ? "#f0f2f5!important" : "initial"}}>
|
||||
|
@ -89,6 +89,7 @@ class DynamicFormUtils
|
||||
label += field.isRequired ? " *" : "";
|
||||
|
||||
return ({
|
||||
fieldMetaData: field,
|
||||
name: field.name,
|
||||
label: label,
|
||||
isRequired: field.isRequired,
|
||||
|
@ -29,6 +29,7 @@ import Switch from "@mui/material/Switch";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {ErrorMessage, useFormikContext} from "formik";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
|
||||
@ -49,6 +50,7 @@ interface Props
|
||||
bulkEditMode?: boolean;
|
||||
bulkEditSwitchChangeHandler?: any;
|
||||
otherValues?: Map<string, any>;
|
||||
variant: "standard" | "outlined";
|
||||
}
|
||||
|
||||
DynamicSelect.defaultProps = {
|
||||
@ -63,6 +65,7 @@ DynamicSelect.defaultProps = {
|
||||
isMultiple: false,
|
||||
bulkEditMode: false,
|
||||
otherValues: new Map<string, any>(),
|
||||
variant: "outlined",
|
||||
bulkEditSwitchChangeHandler: () =>
|
||||
{
|
||||
},
|
||||
@ -70,12 +73,13 @@ DynamicSelect.defaultProps = {
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabel, inForm, initialValue, initialDisplayValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues}: Props)
|
||||
function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabel, inForm, initialValue, initialDisplayValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues, variant}: Props)
|
||||
{
|
||||
const [open, setOpen] = useState(false);
|
||||
const [options, setOptions] = useState<readonly QPossibleValue[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState(null);
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
const {inputBorderColor} = colors;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// default value - needs to be an array (from initialValues (array) prop) for multiple mode - //
|
||||
@ -230,7 +234,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
||||
// attributes. so, doing this, w/ key=id, seemed to fix it. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (
|
||||
<li {...props} key={option.id}>
|
||||
<li {...props} key={option.id} style={{fontSize: "1rem"}}>
|
||||
{content}
|
||||
</li>
|
||||
);
|
||||
@ -244,13 +248,35 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
||||
bulkEditSwitchChangeHandler(fieldName, newSwitchValue);
|
||||
};
|
||||
|
||||
// console.log(`default value: ${JSON.stringify(defaultValue)}`);
|
||||
////////////////////////////////////////////
|
||||
// for outlined style, adjust some styles //
|
||||
////////////////////////////////////////////
|
||||
let autocompleteSX = {};
|
||||
if (variant == "outlined")
|
||||
{
|
||||
autocompleteSX = {
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: "0.75rem",
|
||||
},
|
||||
"& .MuiInputBase-root": {
|
||||
padding: "0.5rem",
|
||||
background: isDisabled ? "#f0f2f5!important" : "initial",
|
||||
},
|
||||
"& .MuiOutlinedInput-root .MuiAutocomplete-input": {
|
||||
padding: "0",
|
||||
fontSize: "1rem"
|
||||
},
|
||||
"& .Mui-disabled .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: inputBorderColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const autocomplete = (
|
||||
<Box>
|
||||
<Autocomplete
|
||||
id={overrideId ?? fieldName}
|
||||
sx={{background: isDisabled ? "#f0f2f5!important" : "initial"}}
|
||||
sx={autocompleteSX}
|
||||
open={open}
|
||||
fullWidth
|
||||
onOpen={() =>
|
||||
@ -305,7 +331,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
||||
<TextField
|
||||
{...params}
|
||||
label={fieldLabel}
|
||||
variant="standard"
|
||||
variant={variant}
|
||||
autoComplete="off"
|
||||
type="search"
|
||||
InputProps={{
|
||||
@ -341,6 +367,14 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
||||
id={`bulkEditSwitch-${fieldName}`}
|
||||
checked={switchChecked}
|
||||
onClick={bulkEditSwitchChanged}
|
||||
sx={{top: "-4px",
|
||||
"& .MuiSwitch-track": {
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
top: -3,
|
||||
position: "relative"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box width="100%">
|
||||
|
@ -37,10 +37,12 @@ import React, {useContext, useEffect, useReducer, useState} from "react";
|
||||
import {useLocation, useNavigate, useParams} from "react-router-dom";
|
||||
import * as Yup from "yup";
|
||||
import QContext from "QContext";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
|
||||
import QDynamicForm from "qqq/components/forms/DynamicForm";
|
||||
import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import HelpContent from "qqq/components/misc/HelpContent";
|
||||
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
|
||||
import HtmlUtils from "qqq/utils/HtmlUtils";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
@ -79,6 +81,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
const [validations, setValidations] = useState({});
|
||||
const [initialValues, setInitialValues] = useState({} as { [key: string]: any });
|
||||
const [formFields, setFormFields] = useState(null as Map<string, any>);
|
||||
const [t1section, setT1Section] = useState(null as QTableSection);
|
||||
const [t1sectionName, setT1SectionName] = useState(null as string);
|
||||
const [nonT1Sections, setNonT1Sections] = useState([] as QTableSection[]);
|
||||
|
||||
@ -151,7 +154,9 @@ function EntityForm(props: Props): JSX.Element
|
||||
{
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return <QDynamicForm formData={formData} record={record} />;
|
||||
|
||||
const helpRoles = [props.id ? "EDIT_SCREEN" : "INSERT_SCREEN", "WRITE_SCREENS", "ALL_SCREENS"]
|
||||
return <QDynamicForm formData={formData} record={record} helpRoles={helpRoles} helpContentKeyPrefix={`table:${tableName};`} />;
|
||||
}
|
||||
|
||||
if (!asyncLoadInited)
|
||||
@ -330,6 +335,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
/////////////////////////////////////
|
||||
const dynamicFormFieldsBySection = new Map<string, any>();
|
||||
let t1sectionName;
|
||||
let t1section;
|
||||
const nonT1Sections: QTableSection[] = [];
|
||||
for (let i = 0; i < tableSections.length; i++)
|
||||
{
|
||||
@ -382,6 +388,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
if (section.tier === "T1")
|
||||
{
|
||||
t1sectionName = section.name;
|
||||
t1section = section;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -389,6 +396,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
}
|
||||
setT1SectionName(t1sectionName);
|
||||
setT1Section(t1section);
|
||||
setNonT1Sections(nonT1Sections);
|
||||
setFormFields(dynamicFormFieldsBySection);
|
||||
setValidations(Yup.object().shape(formValidations));
|
||||
@ -552,6 +560,19 @@ function EntityForm(props: Props): JSX.Element
|
||||
const formId = props.id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
|
||||
|
||||
let body;
|
||||
|
||||
const getSectionHelp = (section: QTableSection) =>
|
||||
{
|
||||
const helpRoles = [props.id ? "EDIT_SCREEN" : "INSERT_SCREEN", "WRITE_SCREENS", "ALL_SCREENS"]
|
||||
const formattedHelpContent = <HelpContent helpContents={section.helpContents} roles={helpRoles} helpContentKey={`table:${tableMetaData.name};section:${section.name}`} />;
|
||||
|
||||
return formattedHelpContent && (
|
||||
<Box px={"1.5rem"} fontSize={"0.875rem"}>
|
||||
{formattedHelpContent}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
if (notAllowedError)
|
||||
{
|
||||
body = (
|
||||
@ -573,23 +594,26 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
else
|
||||
{
|
||||
const cardElevation = props.isModal ? 3 : 1;
|
||||
const cardElevation = props.isModal ? 3 : 0;
|
||||
body = (
|
||||
<Box mb={3}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
{alertContent ? (
|
||||
<Box mb={3}>
|
||||
<Alert severity="error" onClose={() => setAlertContent(null)}>{alertContent}</Alert>
|
||||
</Box>
|
||||
) : ("")}
|
||||
{warningContent ? (
|
||||
<Box mb={3}>
|
||||
<Alert severity="warning" onClose={() => setWarningContent(null)}>{warningContent}</Alert>
|
||||
</Box>
|
||||
) : ("")}
|
||||
{
|
||||
(alertContent || warningContent) &&
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
{alertContent ? (
|
||||
<Box mb={3}>
|
||||
<Alert severity="error" onClose={() => setAlertContent(null)}>{alertContent}</Alert>
|
||||
</Box>
|
||||
) : ("")}
|
||||
{warningContent ? (
|
||||
<Box mb={3}>
|
||||
<Alert severity="warning" onClose={() => setWarningContent(null)}>{warningContent}</Alert>
|
||||
</Box>
|
||||
) : ("")}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
}
|
||||
<Grid container spacing={3}>
|
||||
{
|
||||
!props.isModal &&
|
||||
@ -627,10 +651,11 @@ function EntityForm(props: Props): JSX.Element
|
||||
<MDTypography variant="h5">{formTitle}</MDTypography>
|
||||
</Box>
|
||||
</Box>
|
||||
{t1section && getSectionHelp(t1section)}
|
||||
{
|
||||
t1sectionName && formFields ? (
|
||||
<Box pb={1} px={3}>
|
||||
<Box p={3} width="100%">
|
||||
<Box px={3}>
|
||||
<Box pb={"0.25rem"} width="100%">
|
||||
{getFormSection(values, touched, formFields.get(t1sectionName), errors)}
|
||||
</Box>
|
||||
</Box>
|
||||
@ -644,8 +669,9 @@ function EntityForm(props: Props): JSX.Element
|
||||
<MDTypography variant="h6" p={3} pb={1}>
|
||||
{section.label}
|
||||
</MDTypography>
|
||||
{getSectionHelp(section)}
|
||||
<Box pb={1} px={3}>
|
||||
<Box p={3} width="100%">
|
||||
<Box pb={"0.75rem"} width="100%">
|
||||
{getFormSection(values, touched, formFields.get(section.name), errors)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -69,7 +69,15 @@ export default styled(TextField)(({theme, ownerState}: { theme?: Theme; ownerSta
|
||||
});
|
||||
|
||||
return {
|
||||
backgroundColor: disabled ? `${grey[200]} !important` : transparent.main,
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: disabled ? `${grey[200]} !important` : transparent.main,
|
||||
borderRadius: "0.75rem",
|
||||
},
|
||||
"& input": {
|
||||
backgroundColor: `${transparent.main}!important`,
|
||||
padding: "0.5rem",
|
||||
fontSize: "1rem",
|
||||
},
|
||||
pointerEvents: disabled ? "none" : "auto",
|
||||
...(error && errorStyles()),
|
||||
...(success && successStyles()),
|
||||
|
139
src/qqq/components/misc/HelpContent.tsx
Normal file
139
src/qqq/components/misc/HelpContent.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QHelpContent} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QHelpContent";
|
||||
import Box from "@mui/material/Box";
|
||||
import parse from "html-react-parser";
|
||||
import React, {useContext} from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import QContext from "QContext";
|
||||
|
||||
interface Props
|
||||
{
|
||||
helpContents: QHelpContent[];
|
||||
roles: string[];
|
||||
heading?: string;
|
||||
helpContentKey?: string;
|
||||
}
|
||||
|
||||
HelpContent.defaultProps = {};
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** format some content - meaning, change it from string to JSX element(s) or string.
|
||||
** does a parse() for HTML, and a <Markdown> for markdown, else just text.
|
||||
*******************************************************************************/
|
||||
const formatHelpContent = (content: string, format: string): string | JSX.Element | JSX.Element[] =>
|
||||
{
|
||||
if (format == "HTML")
|
||||
{
|
||||
return parse(content);
|
||||
}
|
||||
else if (format == "MARKDOWN")
|
||||
{
|
||||
return (<Markdown>{content}</Markdown>)
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** return the first help content from the list that matches the first role
|
||||
** in the roles list.
|
||||
*******************************************************************************/
|
||||
const getMatchingHelpContent = (helpContents: QHelpContent[], roles: string[]): QHelpContent =>
|
||||
{
|
||||
if (helpContents)
|
||||
{
|
||||
if (helpContents.length == 1 && helpContents[0].roles.size == 0)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's only 1 entry, and it has no roles, then assume user wanted it globally and use it //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (helpContents[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (let i = 0; i < roles.length; i++)
|
||||
{
|
||||
for (let j = 0; j < helpContents.length; j++)
|
||||
{
|
||||
if (helpContents[j].roles.has(roles[i]))
|
||||
{
|
||||
return(helpContents[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test if a list of help contents would find any matches from a list of roles.
|
||||
*******************************************************************************/
|
||||
export const hasHelpContent = (helpContents: QHelpContent[], roles: string[]) =>
|
||||
{
|
||||
return getMatchingHelpContent(helpContents, roles) != null;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** component that renders a box of formatted help content, from a list of
|
||||
** helpContents (from meta-data), and for a list of roles (based on what screen
|
||||
*******************************************************************************/
|
||||
function HelpContent({helpContents, roles, heading, helpContentKey}: Props): JSX.Element
|
||||
{
|
||||
const {helpHelpActive} = useContext(QContext);
|
||||
let selectedHelpContent = getMatchingHelpContent(helpContents, roles);
|
||||
|
||||
let content = null;
|
||||
if (helpHelpActive)
|
||||
{
|
||||
if (!selectedHelpContent)
|
||||
{
|
||||
selectedHelpContent = new QHelpContent({content: ""});
|
||||
}
|
||||
content = selectedHelpContent.content + ` [${helpContentKey ?? "?"}]`;
|
||||
}
|
||||
else if(selectedHelpContent)
|
||||
{
|
||||
content = selectedHelpContent.content;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// if content was found, format it and return it //
|
||||
///////////////////////////////////////////////////
|
||||
if (content)
|
||||
{
|
||||
return <Box display="inline" className="helpContent">
|
||||
{heading && <span className="header">{heading}</span>}
|
||||
{formatHelpContent(content, selectedHelpContent.format)}
|
||||
</Box>;
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
export default HelpContent;
|
@ -76,7 +76,7 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: P
|
||||
|
||||
|
||||
return (
|
||||
<Card sx={{borderRadius: ({borders: {borderRadius}}) => borderRadius.lg, position: "sticky", top: stickyTop}}>
|
||||
<Card sx={{borderRadius: "0.75rem", position: "sticky", top: stickyTop, overflow: "auto", maxHeight: "calc(100vh - 200px)"}}>
|
||||
<Box component="ul" display="flex" flexDirection="column" p={2} m={0} sx={{listStyle: "none"}}>
|
||||
{
|
||||
sidebarEntries ? sidebarEntries.map((entry: SidebarEntry, key: number) => (
|
||||
|
@ -215,6 +215,7 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC
|
||||
initialDisplayValue={selectedPossibleValue?.label}
|
||||
inForm={false}
|
||||
onChange={(value: any) => valueChangeHandler(null, 0, value)}
|
||||
variant="standard"
|
||||
/>
|
||||
</Box>;
|
||||
case ValueMode.PVS_MULTI:
|
||||
@ -242,6 +243,7 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC
|
||||
initialValues={initialValues}
|
||||
inForm={false}
|
||||
onChange={(value: any) => valueChangeHandler(null, "all", value)}
|
||||
variant="standard"
|
||||
/>
|
||||
</Box>;
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ interface Props
|
||||
labelAdditionalComponentsLeft: LabelComponent[];
|
||||
labelAdditionalElementsLeft: JSX.Element[];
|
||||
labelAdditionalComponentsRight: LabelComponent[];
|
||||
labelBoxAdditionalSx?: any;
|
||||
widgetMetaData?: QWidgetMetaData;
|
||||
widgetData?: WidgetData;
|
||||
children: JSX.Element;
|
||||
@ -75,6 +76,7 @@ Widget.defaultProps = {
|
||||
labelAdditionalComponentsLeft: [],
|
||||
labelAdditionalElementsLeft: [],
|
||||
labelAdditionalComponentsRight: [],
|
||||
labelBoxAdditionalSx: {},
|
||||
omitPadding: false,
|
||||
};
|
||||
|
||||
@ -174,7 +176,7 @@ export class AddNewRecordButton extends LabelComponent
|
||||
render = (args: LabelComponentRenderArgs): JSX.Element =>
|
||||
{
|
||||
return (
|
||||
<Typography variant="body2" p={2} pr={0} display="inline" position="relative" top="0.25rem">
|
||||
<Typography variant="body2" p={2} pr={0} display="inline" position="relative" top="-0.5rem">
|
||||
<Button sx={{mt: 0.75}} onClick={() => this.openEditForm(args.navigate, this.table, null, this.defaultValues, this.disabledFields)}>{this.label}</Button>
|
||||
</Typography>
|
||||
);
|
||||
@ -552,7 +554,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
<Box sx={{width: "100%", height: "100%", minHeight: props.widgetMetaData?.minHeight ? props.widgetMetaData?.minHeight : "initial"}}>
|
||||
{
|
||||
needLabelBox &&
|
||||
<Box display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%"}} minHeight={"2.5rem"}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%", ...props.labelBoxAdditionalSx}} minHeight={"2.5rem"}>
|
||||
<Box>
|
||||
{
|
||||
hasPermission ?
|
||||
|
@ -138,7 +138,7 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
|
||||
if(data && data.viewAllLink)
|
||||
{
|
||||
labelAdditionalElementsLeft.push(
|
||||
<Typography variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative" top="-0.25rem">
|
||||
<Typography variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative">
|
||||
<Link to={data.viewAllLink}>View All</Link>
|
||||
</Typography>
|
||||
)
|
||||
@ -178,8 +178,8 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
|
||||
if(widgetMetaData?.showExportButton)
|
||||
{
|
||||
labelAdditionalElementsLeft.push(
|
||||
<Typography key={1} variant="body2" py={2} px={0} display="inline" position="relative" top="-0.25rem">
|
||||
<Tooltip title={tooltipTitle}><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={onExportClick} disabled={isExportDisabled}><Icon sx={{color: "#757575", fontSize: 1.125}}>save_alt</Icon></Button></Tooltip>
|
||||
<Typography key={1} variant="body2" py={2} px={0} display="inline" position="relative">
|
||||
<Tooltip title={tooltipTitle}><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={onExportClick} disabled={isExportDisabled}><Icon sx={{color: "#757575", fontSize: 1.25}}>save_alt</Icon></Button></Tooltip>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
@ -250,6 +250,7 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
|
||||
widgetData={data}
|
||||
labelAdditionalElementsLeft={labelAdditionalElementsLeft}
|
||||
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
|
||||
labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}}
|
||||
>
|
||||
<Box mx={-2} mb={-3}>
|
||||
<DataGridPro
|
||||
|
@ -86,14 +86,18 @@ function TableWidget(props: Props): JSX.Element
|
||||
}
|
||||
|
||||
const cell = rows[i][columns[j].accessor];
|
||||
const text = htmlToText(cell,
|
||||
{
|
||||
selectors: [
|
||||
{selector: "a", format: "inline"},
|
||||
{selector: ".MuiIcon-root", format: "skip"},
|
||||
{selector: ".button", format: "skip"}
|
||||
]
|
||||
});
|
||||
let text = cell;
|
||||
if(columns[j].type != "default")
|
||||
{
|
||||
text = htmlToText(cell,
|
||||
{
|
||||
selectors: [
|
||||
{selector: "a", format: "inline"},
|
||||
{selector: ".MuiIcon-root", format: "skip"},
|
||||
{selector: ".button", format: "skip"}
|
||||
]
|
||||
});
|
||||
}
|
||||
csv += `"${ValueUtils.cleanForCsv(text)}"`;
|
||||
}
|
||||
csv += "\n";
|
||||
|
Reference in New Issue
Block a user