mirror of
https://github.com/rowyio/rowy.git
synced 2026-07-13 13:59:05 +02:00
migrate remaining fields to react-hook-form
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { FieldProps } from "formik";
|
||||
import { Control, Controller, useWatch } from "react-hook-form";
|
||||
|
||||
import {
|
||||
createStyles,
|
||||
@@ -40,87 +40,109 @@ const useStyles = makeStyles(theme =>
|
||||
})
|
||||
);
|
||||
|
||||
function Action({
|
||||
field,
|
||||
form,
|
||||
editable,
|
||||
config,
|
||||
}: FieldProps<any> & { config: { callableName: string }; editable?: boolean }) {
|
||||
export interface IActionProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
docRef: firebase.firestore.DocumentReference;
|
||||
config: { callableName: string };
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
function Action({ control, name, docRef, editable, config }: IActionProps) {
|
||||
const classes = useStyles();
|
||||
const { ref, ...docData } = form.values;
|
||||
const docData = useWatch({ control });
|
||||
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
const snack = useContext(SnackContext);
|
||||
const handleRun = () => {
|
||||
setIsRunning(true);
|
||||
console.log("RUN");
|
||||
|
||||
cloudFunction(
|
||||
config.callableName,
|
||||
{
|
||||
ref: { path: ref.path, id: ref.id },
|
||||
row: sanitiseRowData(Object.assign({}, docData)),
|
||||
},
|
||||
response => {
|
||||
const { message, cellValue } = response.data;
|
||||
setIsRunning(false);
|
||||
snack.open({ message, severity: "success" });
|
||||
if (cellValue) form.setFieldValue(field.name, cellValue);
|
||||
},
|
||||
error => {
|
||||
console.error("ERROR", config.callableName, error);
|
||||
setIsRunning(false);
|
||||
snack.open({ message: JSON.stringify(error), severity: "error" });
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const hasRan = field.value && field.value.status;
|
||||
const disabled = editable === false;
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
alignItems="center"
|
||||
wrap="nowrap"
|
||||
className={classes.root}
|
||||
spacing={2}
|
||||
>
|
||||
<Grid item xs className={classes.labelGridItem}>
|
||||
<Grid container alignItems="center" className={classes.labelContainer}>
|
||||
<Typography variant="body1" className={classes.label}>
|
||||
{hasRan && isUrl(field.value.status) ? (
|
||||
<a
|
||||
href={field.value.status}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{field.value.status}
|
||||
</a>
|
||||
) : hasRan ? (
|
||||
field.value.status
|
||||
) : (
|
||||
sanitiseCallableName(config.callableName)
|
||||
)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<Fab
|
||||
onClick={handleRun}
|
||||
disabled={isRunning || !!(hasRan && !field.value.redo) || disabled}
|
||||
>
|
||||
{isRunning ? (
|
||||
<CircularProgress color="secondary" size={24} thickness={4.6} />
|
||||
) : hasRan ? (
|
||||
<RefreshIcon />
|
||||
) : (
|
||||
<PlayIcon />
|
||||
)}
|
||||
</Fab>
|
||||
</Grid>
|
||||
</Grid>
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
const handleRun = () => {
|
||||
setIsRunning(true);
|
||||
console.log("RUN");
|
||||
|
||||
cloudFunction(
|
||||
config.callableName,
|
||||
{
|
||||
ref: { path: docRef.path, id: docRef.id },
|
||||
row: sanitiseRowData(Object.assign({}, docData)),
|
||||
},
|
||||
response => {
|
||||
const { message, cellValue } = response.data;
|
||||
setIsRunning(false);
|
||||
snack.open({ message, severity: "success" });
|
||||
if (cellValue) onChange(cellValue);
|
||||
},
|
||||
error => {
|
||||
console.error("ERROR", config.callableName, error);
|
||||
setIsRunning(false);
|
||||
snack.open({ message: JSON.stringify(error), severity: "error" });
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const hasRan = value && value.status;
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
alignItems="center"
|
||||
wrap="nowrap"
|
||||
className={classes.root}
|
||||
spacing={2}
|
||||
>
|
||||
<Grid item xs className={classes.labelGridItem}>
|
||||
<Grid
|
||||
container
|
||||
alignItems="center"
|
||||
className={classes.labelContainer}
|
||||
>
|
||||
<Typography variant="body1" className={classes.label}>
|
||||
{hasRan && isUrl(value.status) ? (
|
||||
<a
|
||||
href={value.status}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{value.status}
|
||||
</a>
|
||||
) : hasRan ? (
|
||||
value.status
|
||||
) : (
|
||||
sanitiseCallableName(config.callableName)
|
||||
)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<Fab
|
||||
onClick={handleRun}
|
||||
disabled={isRunning || !!(hasRan && !value.redo) || disabled}
|
||||
>
|
||||
{isRunning ? (
|
||||
<CircularProgress
|
||||
color="secondary"
|
||||
size={24}
|
||||
thickness={4.6}
|
||||
/>
|
||||
) : hasRan ? (
|
||||
<RefreshIcon />
|
||||
) : (
|
||||
<PlayIcon />
|
||||
)}
|
||||
</Fab>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,38 +55,36 @@ export default function Checkbox({
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonBase className={classes.buttonBase}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
const handleChange = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
onChange(event.target.checked);
|
||||
};
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(event.target.checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch
|
||||
color="secondary"
|
||||
{...props}
|
||||
checked={value}
|
||||
onChange={handleChange}
|
||||
onBlur={onBlur}
|
||||
disabled={editable === false}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
const handleClick = () => onChange(!value);
|
||||
|
||||
return (
|
||||
<ButtonBase className={classes.buttonBase} onClick={handleClick}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
color="secondary"
|
||||
{...props}
|
||||
checked={value}
|
||||
onChange={handleChange}
|
||||
onBlur={onBlur}
|
||||
disabled={editable === false}
|
||||
/>
|
||||
}
|
||||
label={label}
|
||||
labelPlacement="start"
|
||||
classes={{ root: classes.formControlLabel, label: classes.label }}
|
||||
/>
|
||||
}
|
||||
label={label}
|
||||
labelPlacement="start"
|
||||
classes={{ root: classes.formControlLabel, label: classes.label }}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</>
|
||||
</ButtonBase>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { FieldProps } from "formik";
|
||||
import { Controller, Control } from "react-hook-form";
|
||||
|
||||
import AceEditor from "react-ace";
|
||||
import "ace-builds/src-noconflict/mode-javascript";
|
||||
import "ace-builds/src-noconflict/theme-monokai";
|
||||
import "ace-builds/src-noconflict/theme-github";
|
||||
|
||||
import { makeStyles, createStyles, Button } from "@material-ui/core";
|
||||
import ErrorMessage from "../ErrorMessage";
|
||||
import CornerResizeIcon from "assets/icons/CornerResize";
|
||||
|
||||
const useStyles = makeStyles(theme =>
|
||||
@@ -35,17 +34,23 @@ const useStyles = makeStyles(theme =>
|
||||
})
|
||||
);
|
||||
|
||||
export default function Code({ form, field }: FieldProps) {
|
||||
export interface IControlledCodeProps {
|
||||
onChange: (...event: any[]) => void;
|
||||
onBlur: () => void;
|
||||
value: any;
|
||||
}
|
||||
|
||||
function ControlledCode({ onChange, onBlur, value }: IControlledCodeProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [localValue, setLocalValue] = useState(field.value);
|
||||
const [localValue, setLocalValue] = useState(value);
|
||||
useEffect(() => {
|
||||
if (field.value !== localValue) setLocalValue(field.value);
|
||||
}, [field.value]);
|
||||
if (value !== localValue) setLocalValue(value);
|
||||
}, [value]);
|
||||
|
||||
const autoSave = false;
|
||||
const handleChange = autoSave
|
||||
? value => form.setFieldValue(field.name, value)
|
||||
? value => onChange(value)
|
||||
: value => setLocalValue(value);
|
||||
|
||||
const editor = useRef<AceEditor>(null);
|
||||
@@ -58,11 +63,9 @@ export default function Code({ form, field }: FieldProps) {
|
||||
<>
|
||||
<div className={classes.editorWrapper} onMouseUp={handleResize}>
|
||||
<AceEditor
|
||||
key={`${form.initialValues.id}-${field.name}`}
|
||||
placeholder="Type code here…"
|
||||
mode="javascript"
|
||||
theme="github"
|
||||
name={field.name}
|
||||
//onLoad={this.onLoad}
|
||||
onChange={handleChange}
|
||||
fontSize={13}
|
||||
@@ -71,7 +74,7 @@ export default function Code({ form, field }: FieldProps) {
|
||||
showGutter
|
||||
highlightActiveLine
|
||||
showPrintMargin
|
||||
value={autoSave ? field.value : localValue}
|
||||
value={autoSave ? value : localValue}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: false,
|
||||
enableLiveAutocompletion: false,
|
||||
@@ -85,19 +88,30 @@ export default function Code({ form, field }: FieldProps) {
|
||||
<CornerResizeIcon className={classes.resizeIcon} />
|
||||
</div>
|
||||
|
||||
{!autoSave && field.value !== localValue && (
|
||||
{!autoSave && value !== localValue && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
form.setFieldValue(field.name, localValue);
|
||||
}}
|
||||
onClick={() => onChange(localValue)}
|
||||
className={classes.saveButton}
|
||||
variant="contained"
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<ErrorMessage name={field.name} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ICodeProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default function Code({ control, name, ...props }: ICodeProps) {
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={renderProps => <ControlledCode {...props} {...renderProps} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { FieldProps } from "formik";
|
||||
import { Controller, Control } from "react-hook-form";
|
||||
import { ChromePicker } from "react-color";
|
||||
|
||||
import {
|
||||
@@ -18,7 +18,16 @@ const useStyles = makeStyles(theme =>
|
||||
cursor: "pointer",
|
||||
textAlign: "left",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
|
||||
backgroundColor:
|
||||
theme.palette.type === "light"
|
||||
? "rgba(0, 0, 0, 0.09)"
|
||||
: "rgba(255, 255, 255, 0.09)",
|
||||
margin: 0,
|
||||
width: "100%",
|
||||
padding: theme.spacing(0, 0.75),
|
||||
},
|
||||
|
||||
colorIndicator: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
@@ -30,51 +39,57 @@ const useStyles = makeStyles(theme =>
|
||||
})
|
||||
);
|
||||
|
||||
export interface IColorProps extends FieldProps {}
|
||||
export interface IColorProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default function Color({ field, form }: IColorProps) {
|
||||
export default function Color({ control, name }: IColorProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
const toggleOpen = () => setShowPicker(s => !s);
|
||||
|
||||
const handleChangeComplete = color => {
|
||||
form.setFieldValue(field.name, color);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid
|
||||
container
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
className={classes.root}
|
||||
onClick={toggleOpen}
|
||||
component={ButtonBase}
|
||||
>
|
||||
<Grid item>
|
||||
<div
|
||||
className={classes.colorIndicator}
|
||||
style={{ backgroundColor: field.value.hex }}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color={field.value.hex ? "textPrimary" : "textSecondary"}
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => (
|
||||
<>
|
||||
<Grid
|
||||
container
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
className={classes.root}
|
||||
onClick={() => {
|
||||
toggleOpen();
|
||||
onBlur();
|
||||
}}
|
||||
component={ButtonBase}
|
||||
focusRipple
|
||||
>
|
||||
{field.value.hex ?? "Choose a color…"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<div
|
||||
className={classes.colorIndicator}
|
||||
style={{ backgroundColor: value?.hex }}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Collapse in={showPicker}>
|
||||
<ChromePicker
|
||||
color={field.value.rgb}
|
||||
onChangeComplete={handleChangeComplete}
|
||||
/>
|
||||
</Collapse>
|
||||
</>
|
||||
<Grid item xs>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color={value?.hex ? "textPrimary" : "textSecondary"}
|
||||
>
|
||||
{value?.hex ?? "Choose a color…"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Collapse in={showPicker}>
|
||||
<ChromePicker color={value?.rgb} onChangeComplete={onChange} />
|
||||
</Collapse>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,63 +1,75 @@
|
||||
import React from "react";
|
||||
import { FieldProps } from "formik";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { useTheme, Grid, Chip } from "@material-ui/core";
|
||||
|
||||
import ConnectTableSelect, {
|
||||
ConnectTableValue,
|
||||
IConnectTableSelectProps,
|
||||
} from "components/ConnectTableSelect";
|
||||
|
||||
export interface IConnectTableProps extends Partial<IConnectTableSelectProps> {
|
||||
control: Control;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default function ConnectTable({
|
||||
field,
|
||||
form,
|
||||
control,
|
||||
name,
|
||||
editable,
|
||||
...props
|
||||
}: FieldProps<ConnectTableValue[]> & IConnectTableSelectProps) {
|
||||
}: IConnectTableProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const handleDelete = (hit: any) => () => {
|
||||
// if (multiple)
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
field.value.filter(v => v.snapshot.objectID !== hit.objectID)
|
||||
);
|
||||
// else form.setFieldValue(field.name, []);
|
||||
};
|
||||
const disabled = editable === false;
|
||||
return (
|
||||
<>
|
||||
{!disabled && (
|
||||
<ConnectTableSelect
|
||||
{...props}
|
||||
value={field.value}
|
||||
onChange={value => form.setFieldValue(field.name, value)}
|
||||
TextFieldProps={{
|
||||
fullWidth: true,
|
||||
error: !!(form.touched[field.name] && form.errors[field.name]),
|
||||
helperText:
|
||||
(form.touched[field.name] && form.errors[field.name]) || "",
|
||||
onBlur: () => form.setFieldTouched(field.name),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{Array.isArray(field.value) && (
|
||||
<Grid container spacing={1} style={{ marginTop: theme.spacing(1) }}>
|
||||
{field.value.map(({ snapshot }) => (
|
||||
<Grid item key={snapshot.objectID}>
|
||||
<Chip
|
||||
component="li"
|
||||
size="medium"
|
||||
label={props.config?.primaryKeys
|
||||
?.map((key: string) => snapshot[key])
|
||||
.join(" ")}
|
||||
onDelete={disabled ? undefined : handleDelete(snapshot)}
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
const handleDelete = (hit: any) => () => {
|
||||
// if (multiple)
|
||||
onChange(value.filter(v => v.snapshot.objectID !== hit.objectID));
|
||||
// else form.setFieldValue(field.name, []);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disabled && (
|
||||
<ConnectTableSelect
|
||||
{...(props as any)}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
TextFieldProps={{
|
||||
fullWidth: true,
|
||||
onBlur,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{Array.isArray(value) && (
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
style={{ marginTop: theme.spacing(1) }}
|
||||
>
|
||||
{value.map(({ snapshot }) => (
|
||||
<Grid item key={snapshot.objectID}>
|
||||
<Chip
|
||||
component="li"
|
||||
size="medium"
|
||||
label={props.config?.primaryKeys
|
||||
?.map((key: string) => snapshot[key])
|
||||
.join(" ")}
|
||||
onDelete={disabled ? undefined : handleDelete(snapshot)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import {
|
||||
} from "@material-ui/pickers";
|
||||
import { DATE_FORMAT } from "constants/dates";
|
||||
|
||||
import { MuiPickersUtilsProvider } from "@material-ui/pickers";
|
||||
import DateFnsUtils from "@date-io/date-fns";
|
||||
|
||||
export interface IDatePickerProps
|
||||
extends Omit<KeyboardDatePickerProps, "onChange" | "value"> {
|
||||
control: Control;
|
||||
@@ -22,38 +25,42 @@ export default function DatePicker({
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
let transformedValue = null;
|
||||
if (value && "toDate" in value) transformedValue = value.toDate();
|
||||
else if (value !== undefined) transformedValue = value;
|
||||
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
let transformedValue = null;
|
||||
if (value && "toDate" in value) transformedValue = value.toDate();
|
||||
else if (value !== undefined) transformedValue = value;
|
||||
|
||||
const handleChange = (date: Date | null) => {
|
||||
if (isNaN(date?.valueOf() ?? 0)) return;
|
||||
onChange(date);
|
||||
};
|
||||
const handleChange = (date: Date | null) => {
|
||||
if (isNaN(date?.valueOf() ?? 0)) return;
|
||||
onChange(date);
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardDatePicker
|
||||
variant="inline"
|
||||
inputVariant="filled"
|
||||
fullWidth
|
||||
margin="none"
|
||||
format={DATE_FORMAT}
|
||||
placeholder={DATE_FORMAT}
|
||||
InputAdornmentProps={{ style: { marginRight: theme.spacing(-1) } }}
|
||||
{...props}
|
||||
value={transformedValue}
|
||||
onChange={handleChange}
|
||||
onBlur={onBlur}
|
||||
label=""
|
||||
hiddenLabel
|
||||
id={`sidedrawer-field-${name}`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<KeyboardDatePicker
|
||||
variant="inline"
|
||||
inputVariant="filled"
|
||||
fullWidth
|
||||
margin="none"
|
||||
format={DATE_FORMAT}
|
||||
placeholder={DATE_FORMAT}
|
||||
InputAdornmentProps={{
|
||||
style: { marginRight: theme.spacing(-1) },
|
||||
}}
|
||||
{...props}
|
||||
value={transformedValue}
|
||||
onChange={handleChange}
|
||||
onBlur={onBlur}
|
||||
label=""
|
||||
hiddenLabel
|
||||
id={`sidedrawer-field-${name}`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ import { DATE_TIME_FORMAT } from "constants/dates";
|
||||
|
||||
import AccessTimeIcon from "@material-ui/icons/AccessTime";
|
||||
|
||||
import { MuiPickersUtilsProvider } from "@material-ui/pickers";
|
||||
import DateFnsUtils from "@date-io/date-fns";
|
||||
|
||||
export interface IDateTimePickerProps
|
||||
extends Omit<KeyboardDateTimePickerProps, "onChange" | "value"> {
|
||||
control: Control;
|
||||
@@ -24,39 +27,43 @@ export default function DateTimePicker({
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
let transformedValue = null;
|
||||
if (value && "toDate" in value) transformedValue = value.toDate();
|
||||
else if (value !== undefined) transformedValue = value;
|
||||
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
let transformedValue = null;
|
||||
if (value && "toDate" in value) transformedValue = value.toDate();
|
||||
else if (value !== undefined) transformedValue = value;
|
||||
|
||||
const handleChange = (date: Date | null) => {
|
||||
if (isNaN(date?.valueOf() ?? 0)) return;
|
||||
onChange(date);
|
||||
};
|
||||
const handleChange = (date: Date | null) => {
|
||||
if (isNaN(date?.valueOf() ?? 0)) return;
|
||||
onChange(date);
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardDateTimePicker
|
||||
variant="inline"
|
||||
inputVariant="filled"
|
||||
fullWidth
|
||||
margin="none"
|
||||
format={DATE_TIME_FORMAT}
|
||||
placeholder={DATE_TIME_FORMAT}
|
||||
InputAdornmentProps={{ style: { marginRight: theme.spacing(-1) } }}
|
||||
keyboardIcon={<AccessTimeIcon />}
|
||||
{...props}
|
||||
value={transformedValue}
|
||||
onChange={handleChange}
|
||||
onBlur={onBlur}
|
||||
label=""
|
||||
hiddenLabel
|
||||
id={`sidedrawer-field-${name}`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<KeyboardDateTimePicker
|
||||
variant="inline"
|
||||
inputVariant="filled"
|
||||
fullWidth
|
||||
margin="none"
|
||||
format={DATE_TIME_FORMAT}
|
||||
placeholder={DATE_TIME_FORMAT}
|
||||
InputAdornmentProps={{
|
||||
style: { marginRight: theme.spacing(-1) },
|
||||
}}
|
||||
keyboardIcon={<AccessTimeIcon />}
|
||||
{...props}
|
||||
value={transformedValue}
|
||||
onChange={handleChange}
|
||||
onBlur={onBlur}
|
||||
label=""
|
||||
hiddenLabel
|
||||
id={`sidedrawer-field-${name}`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import { FieldProps } from "formik";
|
||||
import { Controller, Control } from "react-hook-form";
|
||||
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import useUploader, { FileValue } from "hooks/useFiretable/useUploader";
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import UploadIcon from "assets/icons/Upload";
|
||||
import { FileIcon } from "constants/fields";
|
||||
|
||||
import ErrorMessage from "../ErrorMessage";
|
||||
import Confirmation from "components/Confirmation";
|
||||
|
||||
const useStyles = makeStyles(theme =>
|
||||
@@ -55,17 +54,21 @@ const useStyles = makeStyles(theme =>
|
||||
})
|
||||
);
|
||||
|
||||
export interface IFileUploaderProps extends FieldProps {
|
||||
editable?: boolean;
|
||||
docRef?: firebase.firestore.DocumentReference;
|
||||
export interface IControlledFileUploaderProps
|
||||
extends Pick<IFileUploaderProps, "editable" | "docRef" | "name"> {
|
||||
onChange: (...event: any[]) => void;
|
||||
onBlur: () => void;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export default function FileUploader({
|
||||
form,
|
||||
field,
|
||||
export function ControlledFileUploader({
|
||||
onChange,
|
||||
onBlur,
|
||||
value,
|
||||
name,
|
||||
docRef,
|
||||
editable,
|
||||
}: IFileUploaderProps) {
|
||||
}: IControlledFileUploaderProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const { uploaderState, upload, deleteUpload } = useUploader();
|
||||
@@ -81,25 +84,25 @@ export default function FileUploader({
|
||||
if (docRef && file) {
|
||||
upload({
|
||||
docRef,
|
||||
fieldName: field.name,
|
||||
fieldName: name,
|
||||
files: [file],
|
||||
previousValue: field.value ?? [],
|
||||
previousValue: value ?? [],
|
||||
onComplete: newValue => {
|
||||
form.setFieldValue(field.name, newValue);
|
||||
onChange(newValue);
|
||||
setLocalFile("");
|
||||
},
|
||||
});
|
||||
setLocalFile(file.name);
|
||||
}
|
||||
},
|
||||
[docRef, field.value]
|
||||
[docRef, value]
|
||||
);
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
const newValue = [...field.value];
|
||||
const newValue = [...value];
|
||||
const toBeDeleted = newValue.splice(index, 1);
|
||||
toBeDeleted.length && deleteUpload(toBeDeleted[0]);
|
||||
form.setFieldValue(field.name, newValue);
|
||||
onChange(newValue);
|
||||
};
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
@@ -117,7 +120,7 @@ export default function FileUploader({
|
||||
)}
|
||||
{...getRootProps()}
|
||||
>
|
||||
<input id={`sidedrawer-field-${field.name}`} {...getInputProps()} />
|
||||
<input id={`sidedrawer-field-${name}`} {...getInputProps()} />
|
||||
<UploadIcon />
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
Upload file
|
||||
@@ -126,8 +129,8 @@ export default function FileUploader({
|
||||
)}
|
||||
|
||||
<Grid container spacing={1} className={classes.chipList}>
|
||||
{Array.isArray(field.value) &&
|
||||
field.value.map((file: FileValue, i) => (
|
||||
{Array.isArray(value) &&
|
||||
value.map((file: FileValue, i) => (
|
||||
<Grid item key={file.name} className={classes.chipGridItem}>
|
||||
<Confirmation
|
||||
message={{
|
||||
@@ -166,8 +169,30 @@ export default function FileUploader({
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<ErrorMessage name={field.name} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export interface IFileUploaderProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
|
||||
editable?: boolean;
|
||||
docRef?: firebase.firestore.DocumentReference;
|
||||
}
|
||||
|
||||
export default function FileUploader({
|
||||
control,
|
||||
name,
|
||||
...props
|
||||
}: IFileUploaderProps) {
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={renderProps => (
|
||||
<ControlledFileUploader {...props} name={name} {...renderProps} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import { FieldProps } from "formik";
|
||||
import { Controller, Control } from "react-hook-form";
|
||||
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import useUploader from "hooks/useFiretable/useUploader";
|
||||
@@ -91,17 +91,21 @@ const useStyles = makeStyles(theme =>
|
||||
})
|
||||
);
|
||||
|
||||
export interface IImageUploaderProps extends FieldProps {
|
||||
docRef?: firebase.firestore.DocumentReference;
|
||||
editable?: boolean;
|
||||
export interface IControlledImageUploaderProps
|
||||
extends Pick<IImageUploaderProps, "docRef" | "editable" | "name"> {
|
||||
onChange: (...event: any[]) => void;
|
||||
onBlur: () => void;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export default function ImageUploader({
|
||||
form,
|
||||
field,
|
||||
export function ControlledImageUploader({
|
||||
onChange,
|
||||
onBlur,
|
||||
value,
|
||||
name,
|
||||
editable,
|
||||
docRef,
|
||||
}: IImageUploaderProps) {
|
||||
}: IControlledImageUploaderProps) {
|
||||
const disabled = editable === false;
|
||||
const classes = useStyles();
|
||||
|
||||
@@ -118,25 +122,25 @@ export default function ImageUploader({
|
||||
if (docRef && imageFile) {
|
||||
upload({
|
||||
docRef,
|
||||
fieldName: field.name,
|
||||
fieldName: name,
|
||||
files: [imageFile],
|
||||
previousValue: field.value ?? [],
|
||||
previousValue: value ?? [],
|
||||
onComplete: newValue => {
|
||||
form.setFieldValue(field.name, newValue);
|
||||
onChange(newValue);
|
||||
setLocalImage("");
|
||||
},
|
||||
});
|
||||
setLocalImage(URL.createObjectURL(imageFile));
|
||||
}
|
||||
},
|
||||
[docRef, field.value]
|
||||
[docRef, value]
|
||||
);
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
const newValue = [...field.value];
|
||||
const newValue = [...value];
|
||||
const toBeDeleted = newValue.splice(index, 1);
|
||||
toBeDeleted.length && deleteUpload(toBeDeleted[0]);
|
||||
form.setFieldValue(field.name, newValue);
|
||||
onChange(newValue);
|
||||
};
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
@@ -155,7 +159,7 @@ export default function ImageUploader({
|
||||
)}
|
||||
{...getRootProps()}
|
||||
>
|
||||
<input id={`sidedrawer-field-${field.name}`} {...getInputProps()} />
|
||||
<input id={`sidedrawer-field-${name}`} {...getInputProps()} />
|
||||
<AddIcon />
|
||||
<Typography variant="body1" color="inherit">
|
||||
{isDragActive ? "Drop your image here" : "Upload image"}
|
||||
@@ -164,8 +168,8 @@ export default function ImageUploader({
|
||||
)}
|
||||
|
||||
<Grid container spacing={1} className={classes.imagesContainer}>
|
||||
{Array.isArray(field.value) &&
|
||||
field.value.map((image, i) => (
|
||||
{Array.isArray(value) &&
|
||||
value.map((image, i) => (
|
||||
<Grid item key={image.downloadURL}>
|
||||
{disabled ? (
|
||||
<Tooltip title="Click to open">
|
||||
@@ -246,8 +250,29 @@ export default function ImageUploader({
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<ErrorMessage name={field.name} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
export interface IImageUploaderProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
|
||||
docRef?: firebase.firestore.DocumentReference;
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
export default function ImageUploader({
|
||||
control,
|
||||
name,
|
||||
...props
|
||||
}: IImageUploaderProps) {
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={renderProps => (
|
||||
<ControlledImageUploader {...props} name={name} {...renderProps} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { FieldProps } from "formik";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
import ReactJson from "react-json-view";
|
||||
|
||||
import { makeStyles, createStyles, useTheme } from "@material-ui/core";
|
||||
@@ -32,45 +32,59 @@ const isValidJson = (val: any) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export default function JsonEditor({ form, field }: FieldProps) {
|
||||
export interface IJsonEditorProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default function JsonEditor({ control, name }: IJsonEditorProps) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
const handleEdit = edit => {
|
||||
form.setFieldValue(field.name, edit.updated_src);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<ReactJson
|
||||
src={isValidJson(field.value) ? field.value : {}}
|
||||
onEdit={handleEdit}
|
||||
onAdd={handleEdit}
|
||||
onDelete={handleEdit}
|
||||
theme={{
|
||||
base00: "rgba(0, 0, 0, 0)",
|
||||
base01: theme.palette.background.default,
|
||||
base02: theme.palette.divider,
|
||||
base03: "#93a1a1",
|
||||
base04: theme.palette.text.disabled,
|
||||
base05: theme.palette.text.secondary,
|
||||
base06: "#073642",
|
||||
base07: theme.palette.text.primary,
|
||||
base08: "#d33682",
|
||||
base09: "#cb4b16",
|
||||
base0A: "#dc322f",
|
||||
base0B: "#859900",
|
||||
base0C: "#6c71c4",
|
||||
base0D: theme.palette.text.secondary,
|
||||
base0E: "#2aa198",
|
||||
base0F: "#268bd2",
|
||||
}}
|
||||
iconStyle="triangle"
|
||||
style={{
|
||||
fontFamily: "SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace",
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
const handleEdit = edit => {
|
||||
onChange(edit.updated_src);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<ReactJson
|
||||
src={isValidJson(value) ? value : {}}
|
||||
onEdit={handleEdit}
|
||||
onAdd={handleEdit}
|
||||
onDelete={handleEdit}
|
||||
theme={{
|
||||
base00: "rgba(0, 0, 0, 0)",
|
||||
base01: theme.palette.background.default,
|
||||
base02: theme.palette.divider,
|
||||
base03: "#93a1a1",
|
||||
base04: theme.palette.text.disabled,
|
||||
base05: theme.palette.text.secondary,
|
||||
base06: "#073642",
|
||||
base07: theme.palette.text.primary,
|
||||
base08: "#d33682",
|
||||
base09: "#cb4b16",
|
||||
base0A: "#dc322f",
|
||||
base0B: "#859900",
|
||||
base0C: "#6c71c4",
|
||||
base0D: theme.palette.text.secondary,
|
||||
base0E: "#2aa198",
|
||||
base0F: "#268bd2",
|
||||
}}
|
||||
iconStyle="triangle"
|
||||
style={{
|
||||
fontFamily:
|
||||
"SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace",
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
makeStyles,
|
||||
createStyles,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
Radio as MuiRadio,
|
||||
Divider,
|
||||
} from "@material-ui/core";
|
||||
import { RadioGroup, RadioGroupProps } from "formik-material-ui";
|
||||
|
||||
import Label from "../Label";
|
||||
import ErrorMessage from "../ErrorMessage";
|
||||
|
||||
const useStyles = makeStyles(theme =>
|
||||
createStyles({
|
||||
root: { display: "flex" },
|
||||
|
||||
formControlLabel: {
|
||||
padding: theme.spacing(1.25, 0),
|
||||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
|
||||
divider: { marginLeft: theme.spacing(5) },
|
||||
})
|
||||
);
|
||||
|
||||
export interface IRadioProps extends RadioGroupProps {
|
||||
options: (string | { value: string; label: React.ReactNode })[];
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Radio({ options, label, ...props }: IRadioProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<FormControl component="fieldset" className={classes.root}>
|
||||
<Label
|
||||
error={
|
||||
!!(
|
||||
props.form.errors[props.field.name] &&
|
||||
props.form.touched[props.field.name]
|
||||
)
|
||||
}
|
||||
>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<RadioGroup {...props}>
|
||||
{options.map(item => {
|
||||
let option: { label: React.ReactNode; value: string } = {
|
||||
label: "",
|
||||
value: "",
|
||||
};
|
||||
if (typeof item === "object") option = item;
|
||||
if (typeof item === "string") option = { label: item, value: item };
|
||||
|
||||
return (
|
||||
<React.Fragment key={option.value}>
|
||||
<FormControlLabel
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
label={option.label}
|
||||
control={
|
||||
<MuiRadio
|
||||
//disabled={isSubmitting}
|
||||
/>
|
||||
}
|
||||
classes={{ label: classes.formControlLabel }}
|
||||
//disabled={isSubmitting}
|
||||
/>
|
||||
<Divider className={classes.divider} />
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</RadioGroup>
|
||||
|
||||
<ErrorMessage name={props.field.name} />
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
import React from "react";
|
||||
import { FieldProps } from "formik";
|
||||
import { Controller, Control } from "react-hook-form";
|
||||
|
||||
import { makeStyles, createStyles, Grid } from "@material-ui/core";
|
||||
import { Rating as MuiRating } from "@material-ui/lab";
|
||||
import StarBorderIcon from "@material-ui/icons/StarBorder";
|
||||
|
||||
import ErrorMessage from "../ErrorMessage";
|
||||
|
||||
const useStyles = makeStyles(theme =>
|
||||
createStyles({
|
||||
root: {
|
||||
@@ -27,32 +25,36 @@ const useStyles = makeStyles(theme =>
|
||||
})
|
||||
);
|
||||
|
||||
export interface IRatingProps extends FieldProps {
|
||||
export interface IRatingProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
export default function Rating(props: IRatingProps) {
|
||||
export default function Rating({ control, name, editable }: IRatingProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container alignItems="center" className={classes.root}>
|
||||
<MuiRating
|
||||
disabled={props.editable === false}
|
||||
name={props.field.name}
|
||||
id={`sidedrawer-field-${props.field.name}`}
|
||||
value={typeof props.field.value === "number" ? props.field.value : 0}
|
||||
onChange={(event, newValue) => {
|
||||
props.form.setFieldValue(props.field.name, newValue);
|
||||
}}
|
||||
emptyIcon={<StarBorderIcon fontSize="inherit" />}
|
||||
classes={{ root: classes.rating, iconEmpty: classes.iconEmpty }}
|
||||
// TODO: Make this customisable in column settings
|
||||
max={4}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<ErrorMessage name={props.field.name} />
|
||||
</>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => (
|
||||
<Grid container alignItems="center" className={classes.root}>
|
||||
<MuiRating
|
||||
disabled={editable === false}
|
||||
name={name}
|
||||
id={`sidedrawer-field-${name}`}
|
||||
value={typeof value === "number" ? value : 0}
|
||||
onChange={(event, newValue) => {
|
||||
onChange(newValue);
|
||||
}}
|
||||
emptyIcon={<StarBorderIcon fontSize="inherit" />}
|
||||
classes={{ root: classes.rating, iconEmpty: classes.iconEmpty }}
|
||||
// TODO: Make this customisable in column settings
|
||||
max={4}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import React from "react";
|
||||
import { FieldProps } from "formik";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import _RichText from "components/RichText";
|
||||
import ErrorMessage from "../ErrorMessage";
|
||||
|
||||
export default function RichText({ form, field }: FieldProps) {
|
||||
const handleChange = value => form.setFieldValue(field.name, value);
|
||||
export interface IRichTextProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default function RichText({ control, name }: IRichTextProps) {
|
||||
return (
|
||||
<>
|
||||
<_RichText value={field.value} onChange={handleChange} />
|
||||
<ErrorMessage name={field.name} />
|
||||
</>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => (
|
||||
<_RichText value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { FieldProps } from "formik";
|
||||
import { Controller, Control } from "react-hook-form";
|
||||
|
||||
import {
|
||||
makeStyles,
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import ErrorMessage from "../ErrorMessage";
|
||||
|
||||
const useStyles = makeStyles(theme =>
|
||||
createStyles({
|
||||
root: { display: "flex" },
|
||||
@@ -45,17 +43,17 @@ const useStyles = makeStyles(theme =>
|
||||
})
|
||||
);
|
||||
|
||||
export interface ISliderProps extends FieldProps, SliderProps {
|
||||
label: React.ReactNode;
|
||||
export interface ISliderProps extends SliderProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
units?: string;
|
||||
minLabel?: React.ReactNode;
|
||||
maxLabel?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Slider({
|
||||
field,
|
||||
form,
|
||||
label,
|
||||
control,
|
||||
name,
|
||||
units,
|
||||
minLabel,
|
||||
maxLabel,
|
||||
@@ -65,54 +63,67 @@ export default function Slider({
|
||||
}: ISliderProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const handleClick = () => form.setFieldTouched(field.name);
|
||||
const handleChange = (event: any, value: number | number[]) => {
|
||||
form.setFieldValue(field.name, value);
|
||||
form.setFieldTouched(field.name);
|
||||
};
|
||||
|
||||
const getAriaValueText = (value: number) =>
|
||||
`${value}${units ? " " + units : ""}`;
|
||||
|
||||
const getValueLabelFormat = (value: number) =>
|
||||
`${value}${units ? " " + units : ""}`;
|
||||
|
||||
return (
|
||||
<FormControl className={classes.root}>
|
||||
<Grid container spacing={2} alignItems="center">
|
||||
<Grid item>
|
||||
<Typography variant="overline" component="span" color="textSecondary">
|
||||
{minLabel ?? `${min}${units ? " " + units : ""}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
const handleChange = (_: any, value: number | number[]) => {
|
||||
onChange(value);
|
||||
onBlur();
|
||||
};
|
||||
|
||||
<Grid item xs>
|
||||
<MuiSlider
|
||||
valueLabelDisplay="auto"
|
||||
min={min}
|
||||
max={max}
|
||||
getAriaValueText={getAriaValueText}
|
||||
valueLabelFormat={getValueLabelFormat}
|
||||
{...props}
|
||||
value={typeof field.value === "number" ? field.value : min}
|
||||
onClick={handleClick}
|
||||
onChange={handleChange}
|
||||
classes={{
|
||||
root: classes.slider,
|
||||
thumb: classes.thumb,
|
||||
valueLabel: classes.valueLabel,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
const getAriaValueText = (value: number) =>
|
||||
`${value}${units ? " " + units : ""}`;
|
||||
|
||||
<Grid item>
|
||||
<Typography variant="overline" component="span" color="textSecondary">
|
||||
{maxLabel ?? `${max}${units ? " " + units : ""}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
const getValueLabelFormat = (value: number) =>
|
||||
`${value}${units ? " " + units : ""}`;
|
||||
|
||||
<ErrorMessage name={field.name} />
|
||||
</FormControl>
|
||||
return (
|
||||
<FormControl className={classes.root}>
|
||||
<Grid container spacing={2} alignItems="center">
|
||||
<Grid item>
|
||||
<Typography
|
||||
variant="overline"
|
||||
component="span"
|
||||
color="textSecondary"
|
||||
>
|
||||
{minLabel ?? `${min}${units ? " " + units : ""}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs>
|
||||
<MuiSlider
|
||||
valueLabelDisplay="auto"
|
||||
min={min}
|
||||
max={max}
|
||||
getAriaValueText={getAriaValueText}
|
||||
valueLabelFormat={getValueLabelFormat}
|
||||
{...props}
|
||||
value={value ?? min}
|
||||
onClick={onBlur}
|
||||
onChange={handleChange}
|
||||
classes={{
|
||||
root: classes.slider,
|
||||
thumb: classes.thumb,
|
||||
valueLabel: classes.valueLabel,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<Typography
|
||||
variant="overline"
|
||||
component="span"
|
||||
color="textSecondary"
|
||||
>
|
||||
{maxLabel ?? `${max}${units ? " " + units : ""}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { FieldProps } from "formik";
|
||||
import { Control, useWatch } from "react-hook-form";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
import queryString from "query-string";
|
||||
@@ -31,30 +31,41 @@ const useStyles = makeStyles(theme =>
|
||||
})
|
||||
);
|
||||
|
||||
export interface ISubTableProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
docRef: firebase.firestore.DocumentReference;
|
||||
config: { parentLabel?: string[] };
|
||||
label: string;
|
||||
}
|
||||
|
||||
export default function SubTable({
|
||||
form,
|
||||
field,
|
||||
control,
|
||||
name,
|
||||
docRef,
|
||||
label,
|
||||
config,
|
||||
}: FieldProps<any> & { config: { parentLabel?: string[] }; label: string }) {
|
||||
}: ISubTableProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const values = useWatch({ control });
|
||||
|
||||
const router = useRouter();
|
||||
const parentLabels = queryString.parse(router.location.search).parentLabel;
|
||||
const _label = config?.parentLabel
|
||||
? config.parentLabel.reduce((acc, curr) => {
|
||||
if (acc !== "") return `${acc} - ${form.values[curr]}`;
|
||||
else return form.values[curr];
|
||||
if (acc !== "") return `${acc} - ${values[curr]}`;
|
||||
else return values[curr];
|
||||
}, "")
|
||||
: "";
|
||||
let subTablePath = "";
|
||||
if (parentLabels)
|
||||
subTablePath =
|
||||
encodeURIComponent(`${form.values.ref.path}/${field.name}`) +
|
||||
encodeURIComponent(`${docRef.path}/${name}`) +
|
||||
`?parentLabel=${parentLabels},${label}`;
|
||||
else
|
||||
subTablePath =
|
||||
encodeURIComponent(`${form.values.ref.path}/${field.name}`) +
|
||||
encodeURIComponent(`${docRef.path}/${name}`) +
|
||||
`?parentLabel=${encodeURIComponent(_label)}`;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { Controller, Control } from "react-hook-form";
|
||||
|
||||
import {
|
||||
makeStyles,
|
||||
@@ -13,12 +14,23 @@ const useStyles = makeStyles(theme =>
|
||||
})
|
||||
);
|
||||
|
||||
export interface ITextProps extends Omit<FilledTextFieldProps, "variant"> {
|
||||
fieldVariant?: "short" | "long" | "email" | "phone" | "number" | "url";
|
||||
export interface IFieldProps {
|
||||
control: Control;
|
||||
name: string;
|
||||
docRef: firebase.firestore.DocumentReference;
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
export interface ITextProps
|
||||
extends IFieldProps,
|
||||
Omit<FilledTextFieldProps, "variant" | "name"> {
|
||||
fieldVariant?: "short" | "long" | "email" | "phone" | "number" | "url";
|
||||
}
|
||||
|
||||
export default function Text({
|
||||
control,
|
||||
name,
|
||||
docRef,
|
||||
fieldVariant = "short",
|
||||
editable,
|
||||
...props
|
||||
@@ -48,7 +60,7 @@ export default function Text({
|
||||
break;
|
||||
|
||||
case "number":
|
||||
variantProps = { type: "number" };
|
||||
variantProps = { inputmode: "numeric", pattern: "[0-9]*" };
|
||||
break;
|
||||
|
||||
case "short":
|
||||
@@ -57,19 +69,33 @@ export default function Text({
|
||||
}
|
||||
|
||||
return (
|
||||
<TextField
|
||||
//key={`${props.form.initialValues.id}-${props.field.name}`}
|
||||
variant="filled"
|
||||
fullWidth
|
||||
margin="none"
|
||||
placeholder={props.label as string}
|
||||
{...variantProps}
|
||||
{...props}
|
||||
id={`sidedrawer-field-${props.name}`}
|
||||
label=""
|
||||
hiddenLabel
|
||||
disabled={editable === false}
|
||||
//InputProps={{ disableUnderline: true }}
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
const handleChange = e => {
|
||||
if (fieldVariant === "number") onChange(parseInt(e.target.value, 10));
|
||||
else onChange(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
variant="filled"
|
||||
fullWidth
|
||||
margin="none"
|
||||
placeholder={props.label as string}
|
||||
{...variantProps}
|
||||
{...props}
|
||||
onChange={handleChange}
|
||||
onBlur={onBlur}
|
||||
value={value}
|
||||
id={`sidedrawer-field-${name}`}
|
||||
label=""
|
||||
hiddenLabel
|
||||
disabled={editable === false}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { FieldProps } from "formik";
|
||||
|
||||
import {
|
||||
makeStyles,
|
||||
createStyles,
|
||||
FormControl,
|
||||
Grid,
|
||||
TextField,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemSecondaryAction,
|
||||
Divider,
|
||||
} from "@material-ui/core";
|
||||
import AddIcon from "@material-ui/icons/Add";
|
||||
import DeleteIcon from "@material-ui/icons/Cancel";
|
||||
|
||||
import Label from "../Label";
|
||||
import ErrorMessage from "../ErrorMessage";
|
||||
|
||||
const useStyles = makeStyles(theme =>
|
||||
createStyles({
|
||||
root: { display: "flex" },
|
||||
list: { marginBottom: theme.spacing(2) },
|
||||
})
|
||||
);
|
||||
|
||||
export interface ITextMultiProps extends FieldProps {
|
||||
label: React.ReactNode;
|
||||
addItemLabel?: string;
|
||||
addItemPlaceholder?: string;
|
||||
}
|
||||
|
||||
export default function TextMulti({
|
||||
form,
|
||||
field,
|
||||
label,
|
||||
addItemLabel,
|
||||
addItemPlaceholder,
|
||||
}: ITextMultiProps) {
|
||||
const classes = useStyles();
|
||||
const [itemToAdd, setItemToAdd] = useState("");
|
||||
|
||||
const handleAddToList = () => {
|
||||
if (Array.isArray(field.value))
|
||||
form.setFieldValue(field.name, [...field.value, itemToAdd]);
|
||||
else form.setFieldValue(field.name, [itemToAdd]);
|
||||
setItemToAdd("");
|
||||
};
|
||||
const handleDeleteFromList = (i: number) => {
|
||||
if (!Array.isArray(field.value)) form.setFieldValue(field.name, []);
|
||||
const newValues = [...field.value];
|
||||
newValues.splice(i, 1);
|
||||
form.setFieldValue(field.name, newValues);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl className={classes.root}>
|
||||
<Label error={!!(form.errors[field.name] && form.touched[field.name])}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<List className={classes.list} disablePadding>
|
||||
{Array.isArray(field.value) &&
|
||||
field.value.map((item: string, i: number) => (
|
||||
<React.Fragment key={i}>
|
||||
<ListItem>
|
||||
<ListItemText primary={item} />
|
||||
<ListItemSecondaryAction>
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="Remove"
|
||||
onClick={() => {
|
||||
handleDeleteFromList(i);
|
||||
form.setFieldTouched(field.name);
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Grid container alignItems="center" spacing={1}>
|
||||
<Grid item>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handleAddToList();
|
||||
form.setFieldTouched(field.name);
|
||||
}}
|
||||
>
|
||||
<AddIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs>
|
||||
<TextField
|
||||
id={`${field.name}-temp`}
|
||||
type="text"
|
||||
onChange={e => setItemToAdd(e.target.value)}
|
||||
variant="filled"
|
||||
fullWidth
|
||||
value={itemToAdd}
|
||||
label={addItemLabel || `Add ${label}`}
|
||||
placeholder={addItemPlaceholder}
|
||||
onKeyPress={e => {
|
||||
if (e.key === "Enter") handleAddToList();
|
||||
}}
|
||||
// NOTE: Field is not automatically touched, has to be set here
|
||||
onBlur={() => form.setFieldTouched(field.name)}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<ErrorMessage name={field.name} />
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export default function Url({ control, name, ...props }: IUrlProps) {
|
||||
{...props}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
value={value}
|
||||
value={value || ""}
|
||||
id={`sidedrawer-field-${name}`}
|
||||
label=""
|
||||
hiddenLabel
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import React, { lazy, useEffect } from "react";
|
||||
import { useForm, useWatch, Control } from "react-hook-form";
|
||||
import { MuiPickersUtilsProvider } from "@material-ui/pickers";
|
||||
import DateFnsUtils from "@date-io/date-fns";
|
||||
import React, { lazy } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import _isFunction from "lodash/isFunction";
|
||||
import _isEmpty from "lodash/isEmpty";
|
||||
|
||||
import { useFiretableContext } from "contexts/firetableContext";
|
||||
// import { useFiretableContext } from "contexts/firetableContext";
|
||||
|
||||
import { Grid } from "@material-ui/core";
|
||||
|
||||
@@ -143,16 +141,13 @@ export interface IFormProps {
|
||||
|
||||
export default function Form({ fields, values }: IFormProps) {
|
||||
const initialValues = getInitialValues(fields);
|
||||
const { ref: firestoreRef, ...rowValues } = values;
|
||||
const { ref: docRef, ...rowValues } = values;
|
||||
const defaultValues = { ...initialValues, ...rowValues };
|
||||
|
||||
const { register, handleSubmit, control, reset } = useForm({
|
||||
const { register, control } = useForm({
|
||||
mode: "onBlur",
|
||||
defaultValues,
|
||||
});
|
||||
const onSubmit = data => {
|
||||
console.log("SUBMIT", data);
|
||||
};
|
||||
|
||||
// Update field values when Firestore document updates
|
||||
// useEffect(() => {
|
||||
@@ -173,181 +168,168 @@ export default function Form({ fields, values }: IFormProps) {
|
||||
// }, [sideDrawerRef?.current]);
|
||||
|
||||
return (
|
||||
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
<form onSubmit={handleSubmit(onSubmit)} key={values.id}>
|
||||
<AutosaveTwo control={control} defaultValues={defaultValues} />
|
||||
<form>
|
||||
<Autosave
|
||||
control={control}
|
||||
defaultValues={defaultValues}
|
||||
docRef={docRef}
|
||||
/>
|
||||
|
||||
<Grid container spacing={4} direction="column" wrap="nowrap">
|
||||
{fields.map((_field, i) => {
|
||||
// Call the field function with values if necessary
|
||||
// Otherwise, just use the field object
|
||||
const field: Field = _isFunction(_field) ? _field(values) : _field;
|
||||
const { type, ...fieldProps } = field;
|
||||
let _type = type;
|
||||
<Grid container spacing={4} direction="column" wrap="nowrap">
|
||||
{fields.map((_field, i) => {
|
||||
// Call the field function with values if necessary
|
||||
// Otherwise, just use the field object
|
||||
const field: Field = _isFunction(_field) ? _field(values) : _field;
|
||||
const { type, ...fieldProps } = field;
|
||||
let _type = type;
|
||||
|
||||
// Derivative field support
|
||||
if (field.config && field.config.renderFieldType) {
|
||||
_type = field.config.renderFieldType;
|
||||
}
|
||||
// Derivative field support
|
||||
if (field.config && field.config.renderFieldType) {
|
||||
_type = field.config.renderFieldType;
|
||||
}
|
||||
|
||||
let renderedField: React.ReactNode = null;
|
||||
let renderedField: React.ReactNode = null;
|
||||
|
||||
switch (_type) {
|
||||
case FieldType.shortText:
|
||||
case FieldType.longText:
|
||||
case FieldType.email:
|
||||
case FieldType.phone:
|
||||
case FieldType.number:
|
||||
renderedField = (
|
||||
<Text {...fieldProps} hiddenLabel inputRef={register} />
|
||||
);
|
||||
break;
|
||||
switch (_type) {
|
||||
case FieldType.shortText:
|
||||
case FieldType.longText:
|
||||
case FieldType.email:
|
||||
case FieldType.phone:
|
||||
case FieldType.number:
|
||||
renderedField = (
|
||||
<Text {...fieldProps} control={control} docRef={docRef} />
|
||||
);
|
||||
break;
|
||||
|
||||
case FieldType.url:
|
||||
renderedField = <Url {...fieldProps} control={control} />;
|
||||
break;
|
||||
case FieldType.url:
|
||||
renderedField = <Url {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
case FieldType.singleSelect:
|
||||
renderedField = (
|
||||
<SingleSelect {...fieldProps} control={control} />
|
||||
);
|
||||
break;
|
||||
case FieldType.singleSelect:
|
||||
renderedField = (
|
||||
<SingleSelect {...fieldProps} control={control} />
|
||||
);
|
||||
break;
|
||||
|
||||
case FieldType.multiSelect:
|
||||
renderedField = (
|
||||
<MultiSelect {...fieldProps} control={control} />
|
||||
);
|
||||
break;
|
||||
case FieldType.multiSelect:
|
||||
renderedField = <MultiSelect {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
case FieldType.date:
|
||||
renderedField = (
|
||||
<DatePicker {...fieldProps} control={control} />
|
||||
);
|
||||
break;
|
||||
case FieldType.date:
|
||||
renderedField = <DatePicker {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
case FieldType.dateTime:
|
||||
renderedField = (
|
||||
<DateTimePicker {...fieldProps} control={control} />
|
||||
);
|
||||
break;
|
||||
case FieldType.dateTime:
|
||||
renderedField = (
|
||||
<DateTimePicker {...fieldProps} control={control} />
|
||||
);
|
||||
break;
|
||||
|
||||
case FieldType.checkbox:
|
||||
renderedField = <Checkbox {...fieldProps} control={control} />;
|
||||
break;
|
||||
case FieldType.checkbox:
|
||||
renderedField = <Checkbox {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
// case FieldType.color:
|
||||
// renderedField = <Field {...fieldProps} component={Color} />;
|
||||
// break;
|
||||
case FieldType.color:
|
||||
renderedField = <Color {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
// case FieldType.slider:
|
||||
// renderedField = (
|
||||
// <Field {...fieldProps} component={Slider} />
|
||||
// );
|
||||
// break;
|
||||
case FieldType.slider:
|
||||
renderedField = <Slider {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
// case FieldType.richText:
|
||||
// renderedField = (
|
||||
// <Field {...fieldProps} component={RichText} />
|
||||
// );
|
||||
// break;
|
||||
case FieldType.richText:
|
||||
renderedField = <RichText {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
// case FieldType.image:
|
||||
// renderedField = (
|
||||
// <Field
|
||||
// {...fieldProps}
|
||||
// component={ImageUploader}
|
||||
// docRef={values.ref}
|
||||
// />
|
||||
// );
|
||||
// break;
|
||||
case FieldType.image:
|
||||
renderedField = (
|
||||
<ImageUploader
|
||||
{...fieldProps}
|
||||
control={control}
|
||||
docRef={values.ref}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
// case FieldType.file:
|
||||
// renderedField = (
|
||||
// <Field
|
||||
// {...fieldProps}
|
||||
// component={FileUploader}
|
||||
// docRef={values.ref}
|
||||
// />
|
||||
// );
|
||||
// break;
|
||||
case FieldType.file:
|
||||
renderedField = (
|
||||
<FileUploader
|
||||
{...fieldProps}
|
||||
control={control}
|
||||
docRef={values.ref}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
// case FieldType.rating:
|
||||
// renderedField = (
|
||||
// <Field {...fieldProps} component={Rating} />
|
||||
// );
|
||||
// break;
|
||||
case FieldType.rating:
|
||||
renderedField = <Rating {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
case FieldType.percentage:
|
||||
renderedField = (
|
||||
<Percentage {...fieldProps} control={control} />
|
||||
);
|
||||
break;
|
||||
case FieldType.percentage:
|
||||
renderedField = <Percentage {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
// case FieldType.connectTable:
|
||||
// renderedField = (
|
||||
// <Field {...fieldProps} component={ConnectTable} />
|
||||
// );
|
||||
// break;
|
||||
case FieldType.connectTable:
|
||||
renderedField = (
|
||||
<ConnectTable {...fieldProps} control={control} />
|
||||
);
|
||||
break;
|
||||
|
||||
// case FieldType.subTable:
|
||||
// renderedField = (
|
||||
// <Field {...fieldProps} component={SubTable} />
|
||||
// );
|
||||
// break;
|
||||
case FieldType.subTable:
|
||||
renderedField = (
|
||||
<SubTable
|
||||
{...(fieldProps as any)}
|
||||
control={control}
|
||||
docRef={docRef}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
// case FieldType.action:
|
||||
// renderedField = (
|
||||
// <Field {...fieldProps} component={Action} />
|
||||
// );
|
||||
// break;
|
||||
case FieldType.action:
|
||||
renderedField = (
|
||||
<Action
|
||||
{...(fieldProps as any)}
|
||||
control={control}
|
||||
docRef={docRef}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
// case FieldType.json:
|
||||
// renderedField = (
|
||||
// <Field {...fieldProps} component={JsonEditor} />
|
||||
// );
|
||||
// break;
|
||||
// case FieldType.code:
|
||||
// renderedField = <Field {...fieldProps} component={Code} />;
|
||||
// break;
|
||||
case undefined:
|
||||
// default:
|
||||
return null;
|
||||
case FieldType.json:
|
||||
renderedField = <JsonEditor {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case FieldType.code:
|
||||
renderedField = <Code {...fieldProps} control={control} />;
|
||||
break;
|
||||
|
||||
return (
|
||||
<FieldWrapper
|
||||
key={fieldProps.name ?? i}
|
||||
type={_type}
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
>
|
||||
{renderedField}
|
||||
</FieldWrapper>
|
||||
);
|
||||
})}
|
||||
case undefined:
|
||||
// default:
|
||||
return null;
|
||||
|
||||
<FieldWrapper
|
||||
type="debug"
|
||||
name="_ft_debug_path"
|
||||
label="Document Path"
|
||||
debugText={values.ref?.path ?? values.id ?? "No ref"}
|
||||
/>
|
||||
</Grid>
|
||||
</form>
|
||||
</MuiPickersUtilsProvider>
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldWrapper
|
||||
key={fieldProps.name ?? i}
|
||||
type={_type}
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
>
|
||||
{renderedField}
|
||||
</FieldWrapper>
|
||||
);
|
||||
})}
|
||||
|
||||
<FieldWrapper
|
||||
type="debug"
|
||||
name="_ft_debug_path"
|
||||
label="Document Path"
|
||||
debugText={values.ref?.path ?? values.id ?? "No ref"}
|
||||
/>
|
||||
</Grid>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function AutosaveTwo({
|
||||
control,
|
||||
}: {
|
||||
control: Control;
|
||||
defaultValues: { [key: string]: any };
|
||||
}) {
|
||||
const watchAll = useWatch({ control });
|
||||
console.log("FORM VALUES", watchAll);
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user