Merge branch 'feature/sprint-27' into feature/custom-filter-panel

This commit is contained in:
2023-06-20 10:17:56 -05:00
committed by GitHub
23 changed files with 3387 additions and 2275 deletions

View File

@ -19,15 +19,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {colors} from "@mui/material";
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {colors, Icon, InputLabel} from "@mui/material";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Grid from "@mui/material/Grid";
import Tooltip from "@mui/material/Tooltip";
import {useFormikContext} from "formik";
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 ValueUtils from "qqq/utils/qqq/ValueUtils";
interface Props
{
@ -35,6 +40,7 @@ interface Props
formData: any;
bulkEditMode?: boolean;
bulkEditSwitchChangeHandler?: any;
record?: QRecord;
}
function QDynamicForm(props: Props): JSX.Element
@ -60,6 +66,14 @@ function QDynamicForm(props: Props): JSX.Element
formikProps.setFieldValue(field.name, event.currentTarget.files[0]);
};
const removeFile = (fieldName: string) =>
{
setFileName(null);
formikProps.setFieldValue(fieldName, null);
props.record?.values.delete(fieldName)
props.record?.displayValues.delete(fieldName)
};
const bulkEditSwitchChanged = (name: string, value: boolean) =>
{
bulkEditSwitchChangeHandler(name, value);
@ -94,10 +108,23 @@ function QDynamicForm(props: Props): JSX.Element
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>
{
props.record && props.record.values.get(fieldName) && <Box fontSize="0.875rem" pb={1}>
Current File:
<Box display="inline-flex" pl={1}>
{ValueUtils.getDisplayValue(pseudoField, props.record, "view")}
<Tooltip placement="bottom" title="Remove current file">
<Icon className="blobIcon" fontSize="small" onClick={(e) => removeFile(fieldName)}>delete</Icon>
</Tooltip>
</Box>
</Box>
}
<Box display="flex" alignItems="center">
<Button variant="outlined" component="label">
<span style={{color: colors.lightBlue[500]}}>Choose file to upload</span>

View File

@ -41,6 +41,7 @@ import QDynamicForm from "qqq/components/forms/DynamicForm";
import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
import MDTypography from "qqq/components/legacy/MDTypography";
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
import HtmlUtils from "qqq/utils/HtmlUtils";
import Client from "qqq/utils/qqq/Client";
import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
@ -82,7 +83,6 @@ function EntityForm(props: Props): JSX.Element
const [warningContent, setWarningContent] = useState("");
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
const [formValues, setFormValues] = useState({} as { [key: string]: string });
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
const [record, setRecord] = useState(null as QRecord);
const [tableSections, setTableSections] = useState(null as QTableSection[]);
@ -139,7 +139,7 @@ function EntityForm(props: Props): JSX.Element
{
return <div>Loading...</div>;
}
return <QDynamicForm formData={formData} />;
return <QDynamicForm formData={formData} record={record} />;
}
if (!asyncLoadInited)
@ -185,8 +185,6 @@ function EntityForm(props: Props): JSX.Element
initialValues[key] = record.values.get(key);
});
//? safe to delete? setFormValues(formValues);
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
{
setNotAllowedError("Records may not be edited in this table");
@ -378,17 +376,18 @@ function EntityForm(props: Props): JSX.Element
actions.setSubmitting(true);
await (async () =>
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// (1) convert date-time fields from user's time-zone into UTC //
// (2) if there's an initial value which matches the value (e.g., from the form), then remove that field //
// from the set of values that we'll submit to the backend. This is to deal with the fact that our //
// date-times in the UI (e.g., the form field) only go to the minute - so they kinda always end up //
// changing from, say, 12:15:30 to just 12:15:00... this seems to get around that, for cases when the //
// user didn't change the value in the field (but if the user did change the value, then we will submit it) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
for(let fieldName of tableMetaData.fields.keys())
{
const fieldMetaData = tableMetaData.fields.get(fieldName);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// (1) convert date-time fields from user's time-zone into UTC //
// (2) if there's an initial value which matches the value (e.g., from the form), then remove that field //
// from the set of values that we'll submit to the backend. This is to deal with the fact that our //
// date-times in the UI (e.g., the form field) only go to the minute - so they kinda always end up //
// changing from, say, 12:15:30 to just 12:15:00... this seems to get around that, for cases when the //
// user didn't change the value in the field (but if the user did change the value, then we will submit it) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(fieldMetaData.type === QFieldType.DATE_TIME && values[fieldName])
{
console.log(`DateTime ${fieldName}: Initial value: [${initialValues[fieldName]}] -> [${values[fieldName]}]`)
@ -402,6 +401,22 @@ function EntityForm(props: Props): JSX.Element
values[fieldName] = ValueUtils.frontendLocalZoneDateTimeStringToUTCStringForBackend(values[fieldName]);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// for BLOB fields, there are 3 possible cases: //
// 1) they are a File object - in which case, cool, send them through to the backend to have bytes stored. //
// 2) they are null - in which case, cool, send them through to the backend to be set to null. //
// 3) they are a String, which is their URL path to download them... in that case, don't submit them to //
// the backend at all, so they'll stay what they were. do that by deleting them from the values object here. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(fieldMetaData.type === QFieldType.BLOB)
{
if(typeof values[fieldName] === "string")
{
console.log(`${fieldName} value was a string, so, we're deleting it from the values array, to not submit it to the backend, to not change it.`);
delete(values[fieldName]);
}
}
}
if (props.id !== null)
@ -416,8 +431,8 @@ function EntityForm(props: Props): JSX.Element
}
else
{
const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true`;
navigate(path);
const path = location.pathname.replace(/\/edit$/, "");
navigate(path, {state: {updateSuccess: true}});
}
})
.catch((error) =>
@ -427,12 +442,13 @@ function EntityForm(props: Props): JSX.Element
if(error.message.toLowerCase().startsWith("warning"))
{
const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true&warning=${encodeURIComponent(error.message)}`;
navigate(path);
const path = location.pathname.replace(/\/edit$/, "");
navigate(path, {state: {updateSuccess: true, warning: error.message}});
}
else
{
setAlertContent(error.message);
HtmlUtils.autoScroll(0);
}
});
}
@ -448,20 +464,22 @@ function EntityForm(props: Props): JSX.Element
}
else
{
const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true`;
navigate(path);
const path = location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField));
navigate(path, {state: {createSuccess: true}});
}
})
.catch((error) =>
{
if(error.message.toLowerCase().startsWith("warning"))
{
const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true&warning=${encodeURIComponent(error.message)}`;
const path = location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField));
navigate(path);
navigate(path, {state: {createSuccess: true, warning: error.message}});
}
else
{
setAlertContent(error.message);
HtmlUtils.autoScroll(0);
}
});
}
@ -499,12 +517,12 @@ function EntityForm(props: Props): JSX.Element
<Grid item xs={12}>
{alertContent ? (
<Box mb={3}>
<Alert severity="error">{alertContent}</Alert>
<Alert severity="error" onClose={() => setAlertContent(null)}>{alertContent}</Alert>
</Box>
) : ("")}
{warningContent ? (
<Box mb={3}>
<Alert severity="warning">{warningContent}</Alert>
<Alert severity="warning" onClose={() => setWarningContent(null)}>{warningContent}</Alert>
</Box>
) : ("")}
</Grid>