Small updates to widgets; ok-ish version of filter query with relative time expressions; initial version of multi-select query for possible-values

This commit is contained in:
2023-02-13 10:46:59 -06:00
parent 6215e58e23
commit a10cee86a8
7 changed files with 145 additions and 8 deletions

View File

@ -21,7 +21,7 @@ Coded by www.creative-tim.com
<meta name="theme-color" content="#04aaef" />
<link id="appleIcon" rel="apple-touch-icon" sizes="76x76" href="%PUBLIC_URL%/" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>QQQ Material Dashboard</title>
<title></title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet" />
</head>

View File

@ -243,7 +243,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
{
widgetMetaData.type === "html" && (
<Widget widgetMetaData={widgetMetaData}>
<Box px={1} pt={0} pb={2}>
<Box px={3} pt={0} pb={2}>
<MDTypography component="div" variant="button" color="text" fontWeight="light">
{
widgetData && widgetData[i] && widgetData[i].html ? (

View File

@ -142,7 +142,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
{
const link = component as HeaderLink
return (
<Typography variant="body2" p={2} display="inline">
<Typography variant="body2" p={2} display="inline" fontSize=".875rem">
{link.to ? <Link to={link.to}>{link.label}</Link> : null}
</Typography>
);
@ -153,7 +153,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
const addNewRecordButton = component as AddNewRecordButton
return (
<Typography variant="body2" p={2} pr={1} display="inline">
<Button onClick={() => openEditForm(addNewRecordButton.table, null, addNewRecordButton.defaultValues, addNewRecordButton.disabledFields)}>{addNewRecordButton.label}</Button>
<Button sx={{mt: 0.75}} onClick={() => openEditForm(addNewRecordButton.table, null, addNewRecordButton.defaultValues, addNewRecordButton.disabledFields)}>{addNewRecordButton.label}</Button>
</Typography>
);
}

View File

@ -173,7 +173,7 @@ function AppHome({app}: Props): JSX.Element
const widgetCount = widgets ? widgets.length : 0;
// eslint-disable-next-line no-nested-ternary
const tileSizeLg = (widgetCount === 0 ? 3 : widgetCount === 1 ? 4 : 6);
const tileSizeLg = 3;
const handleDropdownOnChange = (value: string, index: number) =>
{
@ -207,7 +207,7 @@ function AppHome({app}: Props): JSX.Element
<Grid container spacing={3}>
{
app.sections ? (
<Grid item xs={12} lg={widgetCount === 0 ? 12 : widgetCount === 1 ? 9 : 6}>
<Grid item xs={12} lg={12}>
{app.sections.map((section) => (
<Box key={section.name} mb={3}>
<Card sx={{overflow: "visible"}}>

View File

@ -731,6 +731,73 @@ function InputPossibleValueSourceSingle(tableName: string, field: QFieldMetaData
}
////////////////////////////////////////////////
// input element for multiple possible values //
////////////////////////////////////////////////
function InputPossibleValueSourceMultiple(tableName: string, field: QFieldMetaData, props: GridFilterInputValueProps)
{
const SUBMIT_FILTER_STROKE_TIME = 500;
const {item, applyValue, focusElementRef = null} = props;
console.log("Item.value? " + item.value);
const filterTimeout = useRef<any>();
const [ selectedPossibleValues, setSelectedPossibleValues ] = useState(item.value as QPossibleValue[]);
const [ applying, setIsApplying ] = useState(false);
useEffect(() =>
{
return () =>
{
clearTimeout(filterTimeout.current);
};
}, []);
useEffect(() =>
{
const itemValue = item.value ?? null;
}, [ item.value ]);
const updateFilterValue = (value: QPossibleValue) =>
{
clearTimeout(filterTimeout.current);
setIsApplying(true);
filterTimeout.current = setTimeout(() =>
{
setIsApplying(false);
applyValue({...item, value: value});
}, SUBMIT_FILTER_STROKE_TIME);
};
const handleChange = (value: QPossibleValue) =>
{
updateFilterValue(value);
};
return (
<Box
sx={{
display: "inline-flex",
flexDirection: "row",
alignItems: "end",
height: 48,
}}
>
<DynamicSelect
tableName={tableName}
fieldName={field.name}
isMultiple={true}
fieldLabel="Value"
initialValues={selectedPossibleValues}
inForm={false}
onChange={handleChange}
/>
</Box>
);
}
//////////////////////////////////
// possible value set operators //
//////////////////////////////////
@ -749,6 +816,18 @@ export const buildQGridPvsOperators = (tableName: string, field: QFieldMetaData)
getApplyFilterFn: () => null,
InputComponent: (props: GridFilterInputValueProps<GridApiCommunity>) => InputPossibleValueSourceSingle(tableName, field, props)
},
{
label: "is any of",
value: "isAnyOf",
getApplyFilterFn: () => null,
InputComponent: (props: GridFilterInputValueProps<GridApiCommunity>) => InputPossibleValueSourceMultiple(tableName, field, props)
},
{
label: "is none of",
value: "isNone",
getApplyFilterFn: () => null,
InputComponent: (props: GridFilterInputValueProps<GridApiCommunity>) => InputPossibleValueSourceMultiple(tableName, field, props)
},
{
label: "is empty",
value: "isEmpty",

View File

@ -344,7 +344,8 @@ class FilterUtils
{
try
{
const qQueryFilter = (filterString !== null) ? JSON.parse(filterString) : JSON.parse(searchParams.get("filter")) as QQueryFilter;
const filterJSON = (filterString !== null) ? JSON.parse(filterString) : JSON.parse(searchParams.get("filter"));
const qQueryFilter = filterJSON as QQueryFilter;
//////////////////////////////////////////////////////////////////
// translate from a qqq-style filter to one that the grid wants //
@ -377,6 +378,53 @@ class FilterUtils
}
}
if (field && field.type == "DATE_TIME" && !values)
{
try
{
const criteria = filterJSON.criteria[i];
if (criteria && criteria.expression)
{
let value = new Date();
let amount = Number(criteria.expression.amount);
switch (criteria.expression.timeUnit)
{
case "MINUTES":
{
amount = amount * 60;
break;
}
case "HOURS":
{
amount = amount * 60 * 60;
break;
}
case "DAYS":
{
amount = amount * 60 * 60 * 24;
break;
}
default:
{
console.log("Unrecognized time unit: " + criteria.expression.timeUnit);
}
}
if (criteria.expression.operator == "MINUS")
{
amount = -amount;
}
value.setTime(value.getTime() + 1000 * amount);
values = [ValueUtils.formatDateTimeISO8601(value)];
}
}
catch (e)
{
console.log(e);
}
}
defaultFilter.items.push({
columnField: criteria.fieldName,
operatorValue: FilterUtils.qqqCriteriaOperatorToGrid(criteria.operator, field, values),

View File

@ -131,7 +131,7 @@ class ValueUtils
if (field.hasAdornment(AdornmentType.RENDER_HTML))
{
return (parse(rawValue));
return (rawValue ? parse(rawValue) : "");
}
if (field.hasAdornment(AdornmentType.CHIP))
@ -253,6 +253,16 @@ class ValueUtils
return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`);
}
public static formatDateTimeISO8601(date: Date)
{
if(!(date instanceof Date))
{
date = new Date(date)
}
// @ts-ignore
return (`${date.toString("yyyy-MM-ddThh:mm:ssZ")}`);
}
public static getFullWeekday(date: Date)
{
if(!(date instanceof Date))