mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
Merge pull request #626 from gibsonliketheguitar/feat-147
Feat-147 & Feat-166
This commit is contained in:
68
src/components/fields/Status/ConditionList.tsx
Normal file
68
src/components/fields/Status/ConditionList.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import Subheading from "@src/components/Table/ColumnMenu/Subheading";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Divider from "@mui/material/Divider";
|
||||
|
||||
import { IConditionModal } from "./Settings";
|
||||
import { createValueLabel } from "./utils/conditionListHelper";
|
||||
|
||||
interface I_ConditionList {
|
||||
config: Record<string, any>;
|
||||
setModal: React.Dispatch<React.SetStateAction<IConditionModal>>;
|
||||
}
|
||||
|
||||
export default function ConditionList({ config, setModal }: I_ConditionList) {
|
||||
const conditions = config?.conditions ?? [];
|
||||
const noConditions = Boolean(conditions?.length < 1); // Double check this
|
||||
|
||||
if (noConditions) {
|
||||
return (
|
||||
<>
|
||||
No conditions set yet
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Subheading>Conditions</Subheading>
|
||||
{conditions.map((condition, index) => {
|
||||
return (
|
||||
<>
|
||||
<Grid
|
||||
container
|
||||
justifyContent="space-between"
|
||||
alignItems={"center"}
|
||||
>
|
||||
<GridItem
|
||||
index={index}
|
||||
condition={condition}
|
||||
setModal={setModal}
|
||||
/>
|
||||
</Grid>
|
||||
<Divider />
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const GridItem = ({ condition, setModal, index }: any) => {
|
||||
const noCondition = Boolean(!condition);
|
||||
if (noCondition) return <></>;
|
||||
return (
|
||||
<>
|
||||
{condition?.label}
|
||||
<Grid item>
|
||||
{createValueLabel(condition)}
|
||||
<IconButton
|
||||
onClick={() => setModal({ isOpen: true, condition, index })}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
113
src/components/fields/Status/ConditionModal.tsx
Normal file
113
src/components/fields/Status/ConditionModal.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { useEffect } from "react";
|
||||
import _find from "lodash/find";
|
||||
import Modal from "@src/components/Modal";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import { default as Content } from "./ConditionModalContent";
|
||||
import { EMPTY_STATE } from "./Settings";
|
||||
import { isElement, isEmpty } from "lodash";
|
||||
|
||||
export default function ConditionModal({
|
||||
modal,
|
||||
setModal,
|
||||
conditions,
|
||||
setConditions,
|
||||
}) {
|
||||
const handleClose = () => setModal(EMPTY_STATE);
|
||||
const handleSave = () => {
|
||||
let _conditions = [...conditions];
|
||||
_conditions[modal.index] = modal.condition;
|
||||
setConditions(_conditions);
|
||||
setModal(EMPTY_STATE);
|
||||
};
|
||||
const handleAdd = () => {
|
||||
const labelIsEmpty = Boolean(modal.condition.label.length < 4);
|
||||
const stringValueIsEmpty = Boolean(
|
||||
modal.condition.type === "string" && modal.condition.value.length === 0
|
||||
);
|
||||
const hasDuplicate = Boolean(_find(conditions, modal.condition));
|
||||
const validation = Boolean(
|
||||
labelIsEmpty || stringValueIsEmpty || hasDuplicate
|
||||
);
|
||||
if (validation) return;
|
||||
function setConditionHack(type, condition) {
|
||||
let rCondition = condition;
|
||||
if (type === "undefined") rCondition = { ...condition, value: undefined };
|
||||
if (type === "boolean" && typeof condition.value === "object")
|
||||
rCondition = { ...condition, value: false }; //Again 'rowy's multiselect does not accept default value'
|
||||
return rCondition;
|
||||
}
|
||||
const modalCondition = setConditionHack(
|
||||
modal.condition.type,
|
||||
modal.condition
|
||||
);
|
||||
const noConditions = Boolean(conditions?.length === 0 || !conditions);
|
||||
const arr = noConditions
|
||||
? [modalCondition]
|
||||
: [...conditions, modalCondition];
|
||||
setConditions(arr);
|
||||
setModal(EMPTY_STATE);
|
||||
};
|
||||
const handleRemove = () => {
|
||||
const _newConditions = conditions.filter(
|
||||
(c, index) => index !== modal.index
|
||||
);
|
||||
setConditions(_newConditions);
|
||||
setModal(EMPTY_STATE);
|
||||
};
|
||||
const handleUpdate = (key: string) => (value) => {
|
||||
const newState = {
|
||||
...modal,
|
||||
condition: { ...modal.condition, [key]: value },
|
||||
};
|
||||
setModal(newState);
|
||||
};
|
||||
const primaryAction = (index) => {
|
||||
return index === null
|
||||
? {
|
||||
children: "Add condition",
|
||||
onClick: () => handleAdd(),
|
||||
disabled: false,
|
||||
}
|
||||
: {
|
||||
children: "Save changes",
|
||||
onClick: () => handleSave(),
|
||||
disabled: false,
|
||||
};
|
||||
};
|
||||
const secondaryAction = (index) => {
|
||||
return index === null
|
||||
? {
|
||||
children: "Cancel",
|
||||
onClick: () => setModal(EMPTY_STATE),
|
||||
}
|
||||
: {
|
||||
startIcon: <DeleteIcon />,
|
||||
children: "Remove condition",
|
||||
onClick: () => handleRemove(),
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleUpdate("operator")(modal.condition.operator ?? "==");
|
||||
}, [modal.condition.type]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={modal.isOpen}
|
||||
title={`${modal.index ? "Edit" : "Add"} condition`}
|
||||
maxWidth={"xs"}
|
||||
onClose={handleClose}
|
||||
actions={{
|
||||
primary: primaryAction(modal.index),
|
||||
secondary: secondaryAction(modal.index),
|
||||
}}
|
||||
children={
|
||||
<Content
|
||||
condition={modal.condition}
|
||||
conditions={conditions}
|
||||
handleUpdate={handleUpdate}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
105
src/components/fields/Status/ConditionModalContent.tsx
Normal file
105
src/components/fields/Status/ConditionModalContent.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import _find from "lodash/find";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
|
||||
interface I_ConditionModalContent {
|
||||
handleUpdate: () => void;
|
||||
modal: any;
|
||||
}
|
||||
|
||||
const multiSelectOption = [
|
||||
{ label: "Boolean", value: "boolean" },
|
||||
{ label: "Number", value: "number" },
|
||||
{ label: "String", value: "string" },
|
||||
{ label: "Undefined", value: "undefined" },
|
||||
{ label: "Null", value: "null" },
|
||||
];
|
||||
|
||||
const booleanOptions = [
|
||||
{ label: "True", value: "true" },
|
||||
{ label: "False", value: "false" },
|
||||
];
|
||||
|
||||
const operatorOptions = [
|
||||
{ label: "Less than", value: "<" },
|
||||
{ label: "Less than or equal", value: "<=" },
|
||||
{ label: "Equal", value: "==" },
|
||||
{ label: "Equal or more than", value: ">=" },
|
||||
{ label: "More than", value: ">" },
|
||||
];
|
||||
|
||||
export default function ConditionModalContent({
|
||||
condition,
|
||||
conditions,
|
||||
handleUpdate,
|
||||
}: any) {
|
||||
const { label, operator, type, value } = condition;
|
||||
const duplicateCond = Boolean(_find(conditions, condition));
|
||||
const labelReqLen = Boolean(condition.label.length < 4);
|
||||
return (
|
||||
<>
|
||||
<Typography variant="overline">DATA TYPE (input)</Typography>
|
||||
<MultiSelect
|
||||
options={multiSelectOption}
|
||||
onChange={(v) => handleUpdate("type")(v)}
|
||||
value={type}
|
||||
multiple={false}
|
||||
label="Select data type"
|
||||
/>
|
||||
{/** This is the issue where false is causing a problem */}
|
||||
{/** To add defaultValue into MultiSelect?*/}
|
||||
{type === "boolean" && (
|
||||
<MultiSelect
|
||||
options={booleanOptions}
|
||||
onChange={(v) => handleUpdate("value")(v === "true")}
|
||||
value={value ? "true" : "false"}
|
||||
multiple={false}
|
||||
label="Select condition value"
|
||||
/>
|
||||
)}
|
||||
{type === "number" && (
|
||||
<Grid container direction="row" justifyContent="space-between">
|
||||
<div style={{ width: "45%" }}>
|
||||
{console.log(operatorOptions)}
|
||||
<MultiSelect
|
||||
options={operatorOptions}
|
||||
onChange={(v) => handleUpdate("operator")(v)}
|
||||
value={operator}
|
||||
multiple={false}
|
||||
label="Select operator"
|
||||
/>
|
||||
</div>
|
||||
<TextField
|
||||
error={duplicateCond}
|
||||
type="number"
|
||||
label="Value"
|
||||
value={value}
|
||||
onChange={(e) => handleUpdate("value")(Number(e.target.value))}
|
||||
helperText={
|
||||
duplicateCond ? "Numeric Conditional already exists" : ""
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{type === "string" && (
|
||||
<TextField
|
||||
error={duplicateCond}
|
||||
fullWidth
|
||||
label="Value"
|
||||
value={value}
|
||||
onChange={(e) => handleUpdate("value")(e.target.value)}
|
||||
helperText={duplicateCond ? "string value already exists" : ""}
|
||||
/>
|
||||
)}
|
||||
<TextField
|
||||
error={labelReqLen}
|
||||
value={label}
|
||||
label="Label"
|
||||
fullWidth
|
||||
onChange={(e) => handleUpdate("label")(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
12
src/components/fields/Status/Filter.tsx
Normal file
12
src/components/fields/Status/Filter.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { IFilterOperator } from "../types";
|
||||
|
||||
export const filterOperators: IFilterOperator[] = [
|
||||
{
|
||||
label: "equals",
|
||||
value: "==",
|
||||
},
|
||||
{
|
||||
label: "not equals",
|
||||
value: "!=",
|
||||
},
|
||||
];
|
||||
71
src/components/fields/Status/InlineCell.tsx
Normal file
71
src/components/fields/Status/InlineCell.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { forwardRef, useMemo } from "react";
|
||||
import { IPopoverInlineCellProps } from "../types";
|
||||
|
||||
import { ButtonBase } from "@mui/material";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
import _find from "lodash/find";
|
||||
import getLabel from "./utils/getLabelHelper";
|
||||
import { LowPriority } from "@mui/icons-material";
|
||||
|
||||
export const StatusSingleSelect = forwardRef(function StatusSingleSelect(
|
||||
{ column, value, showPopoverCell, disabled }: IPopoverInlineCellProps,
|
||||
ref: React.Ref<any>
|
||||
) {
|
||||
const conditions = column.config?.conditions ?? [];
|
||||
const lowPriorityOperator = ["<", "<=", ">=", ">"];
|
||||
const otherOperator = conditions.filter(
|
||||
(c) => !lowPriorityOperator.includes(c.operator)
|
||||
);
|
||||
|
||||
/**Revisit this */
|
||||
const sortLowPriorityList = conditions
|
||||
.filter((c) => {
|
||||
return lowPriorityOperator.includes(c.operator);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aDistFromValue = Math.abs(value - a.value);
|
||||
const bDistFromValue = Math.abs(value - b.value);
|
||||
//return the smallest distance
|
||||
return aDistFromValue - bDistFromValue;
|
||||
});
|
||||
const sortedConditions = [...otherOperator, ...sortLowPriorityList];
|
||||
const label = useMemo(
|
||||
() => getLabel(value, sortedConditions),
|
||||
[value, sortedConditions]
|
||||
);
|
||||
return (
|
||||
<ButtonBase
|
||||
onClick={() => showPopoverCell(true)}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
className="cell-collapse-padding"
|
||||
style={{
|
||||
padding: "var(--cell-padding)",
|
||||
paddingRight: 0,
|
||||
height: "100%",
|
||||
font: "inherit",
|
||||
color: "inherit !important",
|
||||
letterSpacing: "inherit",
|
||||
textAlign: "inherit",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<div style={{ flexGrow: 1, overflow: "hidden" }}>{label}</div>
|
||||
|
||||
{!disabled && (
|
||||
<ArrowDropDownIcon
|
||||
className="row-hover-iconButton"
|
||||
sx={{
|
||||
flexShrink: 0,
|
||||
mr: 0.5,
|
||||
borderRadius: 1,
|
||||
p: (32 - 24) / 2 / 8,
|
||||
boxSizing: "content-box",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ButtonBase>
|
||||
);
|
||||
});
|
||||
|
||||
export default StatusSingleSelect;
|
||||
48
src/components/fields/Status/PopoverCell.tsx
Normal file
48
src/components/fields/Status/PopoverCell.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import _find from "lodash/find";
|
||||
import { IPopoverCellProps } from "../types";
|
||||
import MultiSelect_ from "@rowy/multiselect";
|
||||
|
||||
export default function StatusSingleSelect({
|
||||
value,
|
||||
onSubmit,
|
||||
column,
|
||||
parentRef,
|
||||
showPopoverCell,
|
||||
disabled,
|
||||
}: IPopoverCellProps) {
|
||||
const config = column.config ?? {};
|
||||
const conditions = config.conditions ?? [];
|
||||
/**Revisit eventually, can we abstract or use a helper function to clean this? */
|
||||
const reMappedConditions = conditions.map((c) => {
|
||||
let rValue = { ...c };
|
||||
if (c.type === "number") {
|
||||
if (c.operator === "<") rValue = { ...c, value: c.value - 1 };
|
||||
if (c.operator === ">") rValue = { ...c, value: c.value + 1 };
|
||||
}
|
||||
return rValue;
|
||||
});
|
||||
return (
|
||||
<MultiSelect_
|
||||
value={value}
|
||||
onChange={(v) => onSubmit(v)}
|
||||
options={conditions.length >= 1 ? reMappedConditions : []} // this handles when conditions are deleted
|
||||
multiple={false}
|
||||
freeText={config.freeText}
|
||||
disabled={disabled}
|
||||
label={column.name as string}
|
||||
labelPlural={column.name as string}
|
||||
TextFieldProps={{
|
||||
style: { display: "none" },
|
||||
SelectProps: {
|
||||
open: true,
|
||||
MenuProps: {
|
||||
anchorEl: parentRef,
|
||||
anchorOrigin: { vertical: "bottom", horizontal: "left" },
|
||||
transformOrigin: { vertical: "top", horizontal: "left" },
|
||||
},
|
||||
},
|
||||
}}
|
||||
onClose={() => showPopoverCell(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,12 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { ISettingsProps } from "../types";
|
||||
|
||||
import Subheading from "@src/components/Table/ColumnMenu/Subheading";
|
||||
import Button from "@mui/material/Button";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Divider from "@mui/material/Divider";
|
||||
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import Modal from "@src/components/Modal";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
import Button from "@mui/material/Button";
|
||||
import ConditionModal from "./ConditionModal";
|
||||
import ConditionList from "./ConditionList";
|
||||
|
||||
const EMPTY_STATE: {
|
||||
export interface IConditionModal {
|
||||
isOpen: boolean;
|
||||
index: number | null;
|
||||
condition: {
|
||||
@@ -24,7 +15,9 @@ const EMPTY_STATE: {
|
||||
label: string;
|
||||
operator: string | undefined;
|
||||
};
|
||||
} = {
|
||||
}
|
||||
|
||||
export const EMPTY_STATE: IConditionModal = {
|
||||
index: null,
|
||||
isOpen: false,
|
||||
condition: {
|
||||
@@ -34,190 +27,12 @@ const EMPTY_STATE: {
|
||||
operator: "==",
|
||||
},
|
||||
};
|
||||
const ConditionModal = ({ modal, setModal, conditions, setConditions }) => {
|
||||
const handleClose = () => {
|
||||
setModal(EMPTY_STATE);
|
||||
};
|
||||
const handleSave = () => {
|
||||
let _conditions = [...conditions];
|
||||
_conditions[modal.index] = modal.condition;
|
||||
setConditions(_conditions);
|
||||
setModal(EMPTY_STATE);
|
||||
};
|
||||
const handleAdd = () => {
|
||||
setConditions(
|
||||
conditions ? [...conditions, modal.condition] : [modal.condition]
|
||||
);
|
||||
setModal(EMPTY_STATE);
|
||||
};
|
||||
const handleRemove = () => {
|
||||
let _conditions = [...conditions];
|
||||
delete _conditions[modal.index];
|
||||
setConditions(_conditions);
|
||||
setModal(EMPTY_STATE);
|
||||
};
|
||||
const handleUpdate = (key: string) => (value) => {
|
||||
setModal({ ...modal, condition: { ...modal.condition, [key]: value } });
|
||||
};
|
||||
useEffect(() => {
|
||||
handleUpdate("operator")(modal.condition.operator ?? "==");
|
||||
}, [modal.condition.type]);
|
||||
return (
|
||||
<Modal
|
||||
open={modal.isOpen}
|
||||
title={`${modal.index ? "Edit" : "Add"} condition`}
|
||||
maxWidth={"xs"}
|
||||
onClose={handleClose}
|
||||
actions={{
|
||||
primary:
|
||||
modal.index === null
|
||||
? {
|
||||
children: "Add condition",
|
||||
onClick: handleAdd,
|
||||
disabled: false,
|
||||
}
|
||||
: {
|
||||
children: "Save changes",
|
||||
onClick: handleSave,
|
||||
disabled: false,
|
||||
},
|
||||
secondary:
|
||||
modal.index === null
|
||||
? {
|
||||
children: "Cancel",
|
||||
onClick: () => {
|
||||
setModal(EMPTY_STATE);
|
||||
},
|
||||
}
|
||||
: {
|
||||
startIcon: <DeleteIcon />,
|
||||
children: "Remove condition",
|
||||
onClick: handleRemove,
|
||||
},
|
||||
}}
|
||||
children={
|
||||
<>
|
||||
<Typography variant="overline">DATA TYPE (input)</Typography>
|
||||
<MultiSelect
|
||||
options={[
|
||||
{ label: "Boolean", value: "boolean" },
|
||||
{ label: "Number", value: "number" },
|
||||
{ label: "String", value: "string" },
|
||||
{ label: "Undefined", value: "undefined" },
|
||||
{ label: "Null", value: "null" },
|
||||
]}
|
||||
onChange={handleUpdate("type")}
|
||||
value={modal.condition.type}
|
||||
multiple={false}
|
||||
label="Select data type"
|
||||
/>
|
||||
<Typography variant="overline">Condition </Typography>
|
||||
{modal.condition.type === "boolean" && (
|
||||
<MultiSelect
|
||||
options={[
|
||||
{ label: "True", value: "true" },
|
||||
{ label: "False", value: "false" },
|
||||
]}
|
||||
onChange={(v) => handleUpdate("value")(v === "true")}
|
||||
value={modal.condition.value ? "true" : "false"}
|
||||
multiple={false}
|
||||
label="Select condition value"
|
||||
/>
|
||||
)}
|
||||
|
||||
{modal.condition.type === "number" && (
|
||||
<Grid container direction="row" justifyContent="space-between">
|
||||
<div style={{ width: "45%" }}>
|
||||
<MultiSelect
|
||||
options={[
|
||||
{ label: "Less than", value: "<" },
|
||||
{ label: "Less than or equal", value: "<=" },
|
||||
{ label: "Equal", value: "==" },
|
||||
{ label: "Equal or more than", value: ">=" },
|
||||
{ label: "More than", value: ">" },
|
||||
]}
|
||||
onChange={handleUpdate("operator")}
|
||||
value={modal.condition.operator}
|
||||
multiple={false}
|
||||
label="Select operator"
|
||||
/>
|
||||
</div>
|
||||
<TextField
|
||||
type="number"
|
||||
label="Value"
|
||||
value={modal.condition.value}
|
||||
onChange={(e) => handleUpdate("value")(e.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{modal.condition.type === "string" && (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Value"
|
||||
value={modal.condition.value}
|
||||
onChange={(e) => handleUpdate("value")(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Typography variant="overline">Assigned label (output)</Typography>
|
||||
<TextField
|
||||
value={modal.condition.label}
|
||||
label="Type the cell output"
|
||||
fullWidth
|
||||
onChange={(e) => handleUpdate("label")(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Settings({ onChange, config }: ISettingsProps) {
|
||||
const [modal, setModal] = useState(EMPTY_STATE);
|
||||
const { conditions } = config;
|
||||
return (
|
||||
<>
|
||||
<Subheading>Conditions</Subheading>
|
||||
{conditions ? (
|
||||
conditions.map((condition, index) => {
|
||||
return (
|
||||
<>
|
||||
<Grid
|
||||
container
|
||||
justifyContent="space-between"
|
||||
alignItems={"center"}
|
||||
>
|
||||
{condition.label}
|
||||
<Grid item>
|
||||
{["undefined", "null"].includes(condition.type)
|
||||
? condition.type
|
||||
: `${condition.type}:${
|
||||
condition.type === "number" ? condition.operator : ""
|
||||
}${
|
||||
condition.type === "boolean"
|
||||
? JSON.stringify(condition.value)
|
||||
: condition.value
|
||||
}`}
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setModal({ isOpen: true, condition, index });
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider />
|
||||
</>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<>
|
||||
No conditions set yet
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
|
||||
<ConditionList config={config} setModal={setModal} />
|
||||
<Button
|
||||
onClick={() => setModal({ ...EMPTY_STATE, isOpen: true })}
|
||||
startIcon={<AddIcon />}
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
import { Controller } from "react-hook-form";
|
||||
import { ISideDrawerFieldProps } from "../types";
|
||||
|
||||
import { Grid } from "@mui/material";
|
||||
|
||||
import "@mui/lab";
|
||||
|
||||
import { useFieldStyles } from "@src/components/SideDrawer/Form/utils";
|
||||
import { useStatusStyles } from "./styles";
|
||||
|
||||
export default function Rating({ control, column }: ISideDrawerFieldProps) {
|
||||
const fieldClasses = useFieldStyles();
|
||||
const ratingClasses = useStatusStyles();
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
import getLabel from "./utils/getLabelHelper";
|
||||
|
||||
export default function Status({
|
||||
control,
|
||||
column,
|
||||
disabled,
|
||||
}: ISideDrawerFieldProps) {
|
||||
const config = column.config ?? {};
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={column.key}
|
||||
render={({ field: { value } }) => (
|
||||
<Grid container alignItems="center" className={fieldClasses.root}>
|
||||
<>{value}</>
|
||||
</Grid>
|
||||
render={({ field: { onChange, onBlur, value } }) => (
|
||||
<>
|
||||
<MultiSelect
|
||||
value={getLabel(value, config?.conditions)}
|
||||
onChange={onChange}
|
||||
options={config?.conditions ?? []}
|
||||
multiple={false}
|
||||
freeText={config?.freeText}
|
||||
disabled={disabled}
|
||||
TextFieldProps={{
|
||||
label: "",
|
||||
hiddenLabel: true,
|
||||
onBlur,
|
||||
id: `sidedrawer-field-${column.key}`,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { useMemo } from "react";
|
||||
import { IHeavyCellProps } from "../types";
|
||||
|
||||
import { useStatusStyles } from "./styles";
|
||||
import _find from "lodash/find";
|
||||
|
||||
export default function Status({ column, value }: IHeavyCellProps) {
|
||||
const statusClasses = useStatusStyles();
|
||||
|
||||
const conditions = column.config?.conditions ?? [];
|
||||
const label = useMemo(() => {
|
||||
if (["null", "undefined"].includes(typeof value)) {
|
||||
const condition = _find(conditions, (c) => c.type === typeof value);
|
||||
return condition?.label;
|
||||
} else if (typeof value === "number") {
|
||||
const numberConditions = conditions.filter((c) => c.type === "number");
|
||||
for (let i = 0; i < numberConditions.length; i++) {
|
||||
const condition = numberConditions[i];
|
||||
switch (condition.operator) {
|
||||
case "<":
|
||||
if (value < condition.value) return condition.label;
|
||||
break;
|
||||
case "<=":
|
||||
if (value <= condition.value) return condition.label;
|
||||
break;
|
||||
case ">=":
|
||||
if (value >= condition.value) return condition.label;
|
||||
break;
|
||||
case ">":
|
||||
if (value > condition.value) return condition.label;
|
||||
break;
|
||||
case "==":
|
||||
default:
|
||||
if (value == condition.value) return condition.label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < conditions.length; i++) {
|
||||
const condition = conditions[i];
|
||||
if (value == condition.value) return condition.label;
|
||||
}
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}, [value, conditions]);
|
||||
|
||||
return <>{label}</>;
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withHeavyCell from "../_withTableCell/withHeavyCell";
|
||||
|
||||
import StatusIcon from "@src/assets/icons/Status";
|
||||
import BasicCell from "../_BasicCell/BasicCellNull";
|
||||
import NullEditor from "@src/components/Table/editors/NullEditor";
|
||||
|
||||
const TableCell = lazy(
|
||||
() => import("./TableCell" /* webpackChunkName: "TableCell-Status" */)
|
||||
);
|
||||
import { filterOperators } from "./Filter";
|
||||
import BasicCell from "../_BasicCell/BasicCellNull";
|
||||
import PopoverCell from "./PopoverCell";
|
||||
import InlineCell from "./InlineCell";
|
||||
import withPopoverCell from "../_withTableCell/withPopoverCell";
|
||||
|
||||
const SideDrawerField = lazy(
|
||||
() =>
|
||||
import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Status" */)
|
||||
@@ -26,10 +26,16 @@ export const config: IFieldConfig = {
|
||||
initializable: true,
|
||||
icon: <StatusIcon />,
|
||||
description: "Displays field value as custom status text. Read-only. ",
|
||||
TableCell: withHeavyCell(BasicCell, TableCell),
|
||||
TableCell: withPopoverCell(BasicCell, InlineCell, PopoverCell, {
|
||||
anchorOrigin: { horizontal: "left", vertical: "bottom" },
|
||||
transparent: true,
|
||||
}),
|
||||
TableEditor: NullEditor as any,
|
||||
settings: Settings,
|
||||
SideDrawerField,
|
||||
requireConfiguration: true,
|
||||
filter: {
|
||||
operators: filterOperators,
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
|
||||
12
src/components/fields/Status/utils/conditionListHelper.ts
Normal file
12
src/components/fields/Status/utils/conditionListHelper.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export function createValueLabel(condition) {
|
||||
const { operator, type, value } = condition || {};
|
||||
const typeLabelMap = new Map([
|
||||
["undefined", `${type}`],
|
||||
["null", `${type}`],
|
||||
["number", ` ${type}:${operator}${value}`],
|
||||
["boolean", `${type}:${value}`],
|
||||
]);
|
||||
const string = typeLabelMap.get(type);
|
||||
const validString = Boolean(typeof string === "string");
|
||||
return validString ? string : JSON.stringify(value);
|
||||
}
|
||||
89
src/components/fields/Status/utils/getLabelHelper.ts
Normal file
89
src/components/fields/Status/utils/getLabelHelper.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import _find from "lodash/find";
|
||||
|
||||
type value = number | "string" | undefined | null;
|
||||
|
||||
interface condition {
|
||||
type: string;
|
||||
operator: string;
|
||||
label: string;
|
||||
value: value;
|
||||
}
|
||||
|
||||
//TODO ADD TYPES
|
||||
const getFalseyLabelFrom = (arr: condition[], value: string) => {
|
||||
const falseyType = (value) =>
|
||||
typeof value === "object" ? "null" : "undefined";
|
||||
const conditions = _find(arr, (c) => c.type === falseyType(value));
|
||||
return conditions?.label;
|
||||
};
|
||||
|
||||
const getBooleanLabelFrom = (arr: condition[], value: string) => {
|
||||
const boolConditions = arr.filter((c) => c.type === "boolean");
|
||||
for (let c of boolConditions) {
|
||||
if (value === c.value) return c.label;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param arr conditional array
|
||||
* @param value if value is not detected, conditional value becomes the default value
|
||||
* @returns conditional's label || undefined
|
||||
*/
|
||||
const getNumericLabelFrom = (arr: condition[], value: number) => {
|
||||
const numLabelFind = (v, c) => {
|
||||
const val = Number(v); // need to handle when value is default seted
|
||||
const condVal: number = Number(c.value);
|
||||
|
||||
const handleLessThan = () => {
|
||||
if (val === condVal) return true;
|
||||
if (val < condVal) return true;
|
||||
else return false;
|
||||
};
|
||||
|
||||
const hanldeGreaterThan = () => {
|
||||
if (val === condVal) return true;
|
||||
if (val > condVal) return true;
|
||||
else return false;
|
||||
};
|
||||
const operatorMap = new Map([
|
||||
["<", handleLessThan()],
|
||||
[">", hanldeGreaterThan()],
|
||||
["<=", val <= condVal ? true : false],
|
||||
[">=", val >= condVal ? true : false],
|
||||
["==", val === condVal ? true : false],
|
||||
]);
|
||||
return operatorMap.get(c.operator) ? c.label : undefined;
|
||||
};
|
||||
|
||||
const numConditions = arr.filter((c) => c?.type === "number");
|
||||
for (let c of numConditions) {
|
||||
const label = numLabelFind(value, c);
|
||||
if (typeof label === "string") return label;
|
||||
}
|
||||
};
|
||||
|
||||
const getLabelFrom = (arr, value) => {
|
||||
const invalidVal = Boolean(value);
|
||||
if (invalidVal) return;
|
||||
for (let c of arr) {
|
||||
if (value === c.value) return c.label;
|
||||
}
|
||||
};
|
||||
|
||||
const finalLabel = (label: string | undefined, value) => {
|
||||
return typeof label === "string" ? label : value;
|
||||
};
|
||||
|
||||
export default function getLabel(value, conditions) {
|
||||
let _label: any = undefined;
|
||||
const isBoolean = Boolean(typeof value === "boolean");
|
||||
const notBoolean = Boolean(typeof value !== "boolean");
|
||||
const isNullOrUndefined = Boolean(!value && notBoolean);
|
||||
const isNumeric = Boolean(typeof value === "number");
|
||||
|
||||
if (isNullOrUndefined) _label = getFalseyLabelFrom(conditions, value);
|
||||
else if (isBoolean) _label = getBooleanLabelFrom(conditions, value);
|
||||
else if (isNumeric) _label = getNumericLabelFrom(conditions, value);
|
||||
else _label = getLabelFrom(conditions, value);
|
||||
return finalLabel(_label, value);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
IPopoverCellProps,
|
||||
} from "../types";
|
||||
|
||||
import _find from "lodash/find";
|
||||
import { makeStyles, createStyles } from "@mui/styles";
|
||||
import { Popover, PopoverProps } from "@mui/material";
|
||||
|
||||
@@ -50,7 +51,7 @@ export default function withPopoverCell(
|
||||
return function PopoverCell(props: FormatterProps<any>) {
|
||||
const classes = useStyles();
|
||||
const { transparent, ...popoverProps } = options ?? {};
|
||||
const { updateCell } = useProjectContext();
|
||||
const { deleteCell, updateCell, tableState } = useProjectContext();
|
||||
|
||||
const { validationRegex, required } = (props.column as any).config;
|
||||
|
||||
@@ -98,8 +99,18 @@ export default function withPopoverCell(
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
//This is where we update the documents
|
||||
const handleSubmit = (value: any) => {
|
||||
if (updateCell && !options?.readOnly) {
|
||||
const targetRow = _find(tableState?.rows, { id: props.row.ref.id });
|
||||
const targetCell = targetRow?.[props.column.key];
|
||||
const canDelete = Boolean(
|
||||
typeof value === "undefined" && targetCell !== value
|
||||
);
|
||||
|
||||
if (deleteCell && !options?.readOnly && canDelete) {
|
||||
deleteCell(props.row.ref, props.column.key);
|
||||
setLocalValue(value);
|
||||
} else if (updateCell && !options?.readOnly) {
|
||||
updateCell(props.row.ref, props.column.key, value);
|
||||
setLocalValue(value);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ export interface IProjectContext {
|
||||
ignoreRequiredFields?: boolean
|
||||
) => void;
|
||||
deleteRow: (rowId) => void;
|
||||
deleteCell: (
|
||||
rowRef: firebase.firestore.DocumentReference,
|
||||
fieldValue: string
|
||||
) => void;
|
||||
updateCell: (
|
||||
ref: firebase.firestore.DocumentReference,
|
||||
fieldName: string,
|
||||
@@ -290,6 +294,17 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
return;
|
||||
};
|
||||
|
||||
const deleteCell: IProjectContext["deleteCell"] = (rowRef, fieldValue) => {
|
||||
rowRef
|
||||
.update({
|
||||
[fieldValue]: firebase.firestore.FieldValue.delete(),
|
||||
})
|
||||
.then(
|
||||
() => console.log("Field Value deleted"),
|
||||
(error) => console.error("Failed to delete", error)
|
||||
);
|
||||
};
|
||||
|
||||
const updateCell: IProjectContext["updateCell"] = (
|
||||
ref,
|
||||
fieldName,
|
||||
@@ -297,9 +312,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
onSuccess
|
||||
) => {
|
||||
if (value === undefined) return;
|
||||
|
||||
const update = { [fieldName]: value };
|
||||
|
||||
if (table?.audit !== false) {
|
||||
update[table?.auditFieldUpdatedBy || "_updatedBy"] = rowyUser(
|
||||
currentUser!,
|
||||
@@ -395,6 +408,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
tableActions,
|
||||
addRow,
|
||||
addRows,
|
||||
deleteCell,
|
||||
updateCell,
|
||||
deleteRow,
|
||||
settingsActions,
|
||||
|
||||
Reference in New Issue
Block a user