mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 21:30:45 +00:00
Merge branch 'refs/heads/wip/dashboard-custom-timeframe' into dev
This commit is contained in:
@ -6,7 +6,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.55",
|
"@kingsrook/qqq-frontend-core": "1.0.56",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.11.1",
|
"@mui/material": "5.11.1",
|
||||||
"@mui/styles": "5.11.1",
|
"@mui/styles": "5.11.1",
|
||||||
|
@ -130,6 +130,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [dropdownData, setDropdownData] = useState([]);
|
const [dropdownData, setDropdownData] = useState([]);
|
||||||
const [counter, setCounter] = useState(0);
|
const [counter, setCounter] = useState(0);
|
||||||
|
const [fullScreenWidgetClassName, setFullScreenWidgetClassName] = useState("");
|
||||||
|
|
||||||
function openEditForm(table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any)
|
function openEditForm(table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any)
|
||||||
{
|
{
|
||||||
@ -175,6 +176,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
return (
|
return (
|
||||||
<Box my={2} sx={{float: "right"}}>
|
<Box my={2} sx={{float: "right"}}>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
|
name={dropdownName}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
sx={{width: 200, marginLeft: "15px"}}
|
sx={{width: 200, marginLeft: "15px"}}
|
||||||
label={`Select ${dropdown.label}`}
|
label={`Select ${dropdown.label}`}
|
||||||
@ -283,6 +285,18 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
}
|
}
|
||||||
}, [counter]);
|
}, [counter]);
|
||||||
|
|
||||||
|
const toggleFullScreenWidget = () =>
|
||||||
|
{
|
||||||
|
if(fullScreenWidgetClassName)
|
||||||
|
{
|
||||||
|
setFullScreenWidgetClassName("");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setFullScreenWidgetClassName("fullScreenWidget");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const hasPermission = props.widgetData?.hasPermission === undefined || props.widgetData?.hasPermission === true;
|
const hasPermission = props.widgetData?.hasPermission === undefined || props.widgetData?.hasPermission === true;
|
||||||
const widgetContent =
|
const widgetContent =
|
||||||
<Box sx={{width: "100%", height: "100%", minHeight: props.widgetMetaData?.minHeight ? props.widgetMetaData?.minHeight : "initial"}}>
|
<Box sx={{width: "100%", height: "100%", minHeight: props.widgetMetaData?.minHeight ? props.widgetMetaData?.minHeight : "initial"}}>
|
||||||
@ -336,17 +350,22 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
// first look for a label in the widget data, which would override that in the metadata //
|
// first look for a label in the widget data, which would override that in the metadata //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
hasPermission && props.widgetData?.label? (
|
hasPermission && props.widgetData?.label? (
|
||||||
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={2} display="inline">
|
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={2} display="inline-block">
|
||||||
{props.widgetData.label}
|
{props.widgetData.label}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
hasPermission && props.widgetMetaData?.label && (
|
hasPermission && props.widgetMetaData?.label && (
|
||||||
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={3} display="inline">
|
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={3} display="inline-block">
|
||||||
{props.widgetMetaData.label}
|
{props.widgetMetaData.label}
|
||||||
</Typography>
|
</Typography>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{/*
|
||||||
|
<Button onClick={() => toggleFullScreenWidget()}>
|
||||||
|
{fullScreenWidgetClassName ? "-" : "+"}
|
||||||
|
</Button>
|
||||||
|
*/}
|
||||||
{
|
{
|
||||||
hasPermission && (
|
hasPermission && (
|
||||||
props.labelAdditionalComponentsLeft.map((component, i) =>
|
props.labelAdditionalComponentsLeft.map((component, i) =>
|
||||||
@ -384,7 +403,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
}
|
}
|
||||||
</Box>;
|
</Box>;
|
||||||
|
|
||||||
return props.widgetMetaData?.isCard ? <Card sx={{marginTop: props.widgetMetaData?.icon ? 2 : 0, width: "100%"}}>{widgetContent}</Card> : widgetContent;
|
return props.widgetMetaData?.isCard ? <Card sx={{marginTop: props.widgetMetaData?.icon ? 2 : 0, width: "100%"}} className={fullScreenWidgetClassName}>{widgetContent}</Card> : widgetContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Widget;
|
export default Widget;
|
||||||
|
@ -46,6 +46,8 @@ export const options = {
|
|||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
|
grid: {offset: false},
|
||||||
|
ticks: {autoSkip: false, maxRotation: 90}
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
|
@ -19,10 +19,16 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Theme} from "@mui/material";
|
import {Collapse, Theme} from "@mui/material";
|
||||||
import Autocomplete from "@mui/material/Autocomplete";
|
import Autocomplete from "@mui/material/Autocomplete";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import {SxProps} from "@mui/system";
|
import {SxProps} from "@mui/system";
|
||||||
|
import {Field, Form, Formik} from "formik";
|
||||||
|
import React, {useState} from "react";
|
||||||
|
import MDInput from "qqq/components/legacy/MDInput";
|
||||||
|
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
||||||
|
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||||
|
|
||||||
|
|
||||||
export interface DropdownOption
|
export interface DropdownOption
|
||||||
@ -36,6 +42,7 @@ export interface DropdownOption
|
|||||||
/////////////////////////
|
/////////////////////////
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
|
name: string;
|
||||||
defaultValue?: any;
|
defaultValue?: any;
|
||||||
label?: string;
|
label?: string;
|
||||||
dropdownOptions?: DropdownOption[];
|
dropdownOptions?: DropdownOption[];
|
||||||
@ -43,23 +50,119 @@ interface Props
|
|||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenu({defaultValue, label, dropdownOptions, onChangeCallback, sx}: Props): JSX.Element
|
interface StartAndEndDate
|
||||||
{
|
{
|
||||||
|
startDate?: string,
|
||||||
|
endDate?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCustomTimeValuesFromDefaultValue(defaultValue: any): StartAndEndDate
|
||||||
|
{
|
||||||
|
const customTimeValues: StartAndEndDate = {};
|
||||||
|
if(defaultValue && defaultValue.id)
|
||||||
|
{
|
||||||
|
var parts = defaultValue.id.split(",");
|
||||||
|
if(parts.length >= 2)
|
||||||
|
{
|
||||||
|
customTimeValues["startDate"] = ValueUtils.formatDateTimeValueForForm(parts[1]);
|
||||||
|
}
|
||||||
|
if(parts.length >= 3)
|
||||||
|
{
|
||||||
|
customTimeValues["endDate"] = ValueUtils.formatDateTimeValueForForm(parts[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (customTimeValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeBackendValuesFromFrontendValues(frontendDefaultValues: StartAndEndDate): StartAndEndDate
|
||||||
|
{
|
||||||
|
const backendTimeValues: StartAndEndDate = {};
|
||||||
|
if(frontendDefaultValues && frontendDefaultValues.startDate)
|
||||||
|
{
|
||||||
|
backendTimeValues.startDate = FilterUtils.frontendLocalZoneDateTimeStringToUTCStringForBackend(frontendDefaultValues.startDate);
|
||||||
|
}
|
||||||
|
if(frontendDefaultValues && frontendDefaultValues.endDate)
|
||||||
|
{
|
||||||
|
backendTimeValues.endDate = FilterUtils.frontendLocalZoneDateTimeStringToUTCStringForBackend(frontendDefaultValues.endDate);
|
||||||
|
}
|
||||||
|
return (backendTimeValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenu({name, defaultValue, label, dropdownOptions, onChangeCallback, sx}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
const [customTimesVisible, setCustomTimesVisible] = useState(defaultValue && defaultValue.id && defaultValue.id.startsWith("custom,"));
|
||||||
|
const [customTimeValuesFrontend, setCustomTimeValuesFrontend] = useState(parseCustomTimeValuesFromDefaultValue(defaultValue) as StartAndEndDate);
|
||||||
|
const [customTimeValuesBackend, setCustomTimeValuesBackend] = useState(makeBackendValuesFromFrontendValues(customTimeValuesFrontend) as StartAndEndDate);
|
||||||
|
const [debounceTimeout, setDebounceTimeout] = useState(null as any);
|
||||||
|
|
||||||
const handleOnChange = (event: any, newValue: any, reason: string) =>
|
const handleOnChange = (event: any, newValue: any, reason: string) =>
|
||||||
{
|
{
|
||||||
onChangeCallback(label, newValue);
|
const isTimeframeCustom = name == "timeframe" && newValue && newValue.id == "custom"
|
||||||
|
setCustomTimesVisible(isTimeframeCustom);
|
||||||
|
|
||||||
|
if(isTimeframeCustom)
|
||||||
|
{
|
||||||
|
callOnChangeCallbackIfCustomTimeframeHasDateValues();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onChangeCallback(label, newValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const callOnChangeCallbackIfCustomTimeframeHasDateValues = () =>
|
||||||
|
{
|
||||||
|
if(customTimeValuesBackend["startDate"] && customTimeValuesBackend["endDate"])
|
||||||
|
{
|
||||||
|
onChangeCallback(label, {id: `custom,${customTimeValuesBackend["startDate"]},${customTimeValuesBackend["endDate"]}`, label: "Custom"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let customTimes = <></>;
|
||||||
|
if (name == "timeframe")
|
||||||
|
{
|
||||||
|
const handleSubmit = async (values: any, actions: any) =>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateChanged = (fieldName: "startDate" | "endDate", event: any) =>
|
||||||
|
{
|
||||||
|
customTimeValuesFrontend[fieldName] = event.target.value;
|
||||||
|
customTimeValuesBackend[fieldName] = FilterUtils.frontendLocalZoneDateTimeStringToUTCStringForBackend(event.target.value);
|
||||||
|
|
||||||
|
clearTimeout(debounceTimeout);
|
||||||
|
const newDebounceTimeout = setTimeout(() =>
|
||||||
|
{
|
||||||
|
callOnChangeCallbackIfCustomTimeframeHasDateValues();
|
||||||
|
}, 500);
|
||||||
|
setDebounceTimeout(newDebounceTimeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
customTimes = <Box sx={{display: "inline-block", position: "relative", top: "-7px"}}>
|
||||||
|
<Collapse orientation="horizontal" in={customTimesVisible}>
|
||||||
|
<Formik initialValues={customTimeValuesFrontend} onSubmit={handleSubmit}>
|
||||||
|
{({}) => (
|
||||||
|
<Form id="timeframe-form" autoComplete="off">
|
||||||
|
<Field name="startDate" type="datetime-local" as={MDInput} variant="standard" label="Custom Timeframe Start" InputLabelProps={{shrink: true}} InputProps={{size: "small"}} sx={{ml: 2, width: 198}} onChange={(event: any) => dateChanged("startDate", event)} />
|
||||||
|
<Field name="endDate" type="datetime-local" as={MDInput} variant="standard" label="Custom Timeframe End" InputLabelProps={{shrink: true}} InputProps={{size: "small"}} sx={{ml: 2, width: 198}} onChange={(event: any) => dateChanged("endDate", event)} />
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Collapse>
|
||||||
|
</Box>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
dropdownOptions ? (
|
dropdownOptions ? (
|
||||||
<span style={{whiteSpace: "nowrap"}}>
|
<span style={{whiteSpace: "nowrap", display: "flex"}} className="dashboardDropdownMenu">
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
size="small"
|
size="small"
|
||||||
disablePortal
|
disablePortal
|
||||||
id={`${label}-combo-box`}
|
id={`${label}-combo-box`}
|
||||||
options={dropdownOptions}
|
options={dropdownOptions}
|
||||||
sx={{...sx, cursor: "pointer"}}
|
sx={{...sx, cursor: "pointer", display: "inline-block"}}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||||
renderInput={(params: any) => <TextField {...params} label={label} />}
|
renderInput={(params: any) => <TextField {...params} label={label} />}
|
||||||
@ -67,9 +170,10 @@ function DropdownMenu({defaultValue, label, dropdownOptions, onChangeCallback, s
|
|||||||
<li {...props} style={{whiteSpace: "normal"}}>{option.label}</li>
|
<li {...props} style={{whiteSpace: "normal"}}>{option.label}</li>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{customTimes}
|
||||||
</span>
|
</span>
|
||||||
) : null
|
) : null
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DropdownMenu;
|
export default DropdownMenu;
|
||||||
|
@ -289,7 +289,12 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const buildQFilter = (filterModel: GridFilterModel) =>
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// note - important to take tableMetaData as a param, even though it's a state var, as the //
|
||||||
|
// first time we call in here, we may not yet have set it in state (but will have fetched it async) //
|
||||||
|
// so we'll pass in the local version of it! //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const buildQFilter = (tableMetaData: QTableMetaData, filterModel: GridFilterModel) =>
|
||||||
{
|
{
|
||||||
const filter = FilterUtils.buildQFilterFromGridFilter(tableMetaData, filterModel, columnSortModel);
|
const filter = FilterUtils.buildQFilterFromGridFilter(tableMetaData, filterModel, columnSortModel);
|
||||||
setHasValidFilters(filter.criteria && filter.criteria.length > 0);
|
setHasValidFilters(filter.criteria && filter.criteria.length > 0);
|
||||||
@ -337,6 +342,15 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
setTableMetaData(tableMetaData);
|
setTableMetaData(tableMetaData);
|
||||||
setTableLabel(tableMetaData.label);
|
setTableLabel(tableMetaData.label);
|
||||||
|
|
||||||
|
if(columnsModel.length == 0)
|
||||||
|
{
|
||||||
|
let linkBase = metaData.getTablePath(table)
|
||||||
|
linkBase += linkBase.endsWith("/") ? "" : "/";
|
||||||
|
const columns = DataGridUtils.setupGridColumns(tableMetaData, null, linkBase);
|
||||||
|
setColumnsModel(columns);
|
||||||
|
}
|
||||||
|
|
||||||
if (columnSortModel.length === 0)
|
if (columnSortModel.length === 0)
|
||||||
{
|
{
|
||||||
columnSortModel.push({
|
columnSortModel.push({
|
||||||
@ -346,7 +360,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
setColumnSortModel(columnSortModel);
|
setColumnSortModel(columnSortModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const qFilter = buildQFilter(localFilterModel);
|
const qFilter = buildQFilter(tableMetaData, localFilterModel);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// assign a new query id to the query being issued here. then run both the count & query async //
|
// assign a new query id to the query being issued here. then run both the count & query async //
|
||||||
@ -440,14 +454,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
const {rows, columnsToRender} = DataGridUtils.makeRows(results, tableMetaData);
|
const {rows, columnsToRender} = DataGridUtils.makeRows(results, tableMetaData);
|
||||||
|
|
||||||
if(columnsModel.length == 0)
|
|
||||||
{
|
|
||||||
let linkBase = metaData.getTablePath(table)
|
|
||||||
linkBase += linkBase.endsWith("/") ? "" : "/";
|
|
||||||
const columns = DataGridUtils.setupGridColumns(tableMetaData, columnsToRender, linkBase);
|
|
||||||
setColumnsModel(columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
setRows(rows);
|
setRows(rows);
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -616,6 +622,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
|
setTableMetaData(null);
|
||||||
setTableState(tableName);
|
setTableState(tableName);
|
||||||
const metaData = await qController.loadMetaData();
|
const metaData = await qController.loadMetaData();
|
||||||
setMetaData(metaData);
|
setMetaData(metaData);
|
||||||
@ -675,7 +682,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
const d = new Date();
|
const d = new Date();
|
||||||
const dateString = `${d.getFullYear()}-${zp(d.getMonth()+1)}-${zp(d.getDate())} ${zp(d.getHours())}${zp(d.getMinutes())}`;
|
const dateString = `${d.getFullYear()}-${zp(d.getMonth()+1)}-${zp(d.getDate())} ${zp(d.getHours())}${zp(d.getMinutes())}`;
|
||||||
const filename = `${tableMetaData.label} Export ${dateString}.${format}`;
|
const filename = `${tableMetaData.label} Export ${dateString}.${format}`;
|
||||||
const url = `/data/${tableMetaData.name}/export/${filename}?filter=${encodeURIComponent(JSON.stringify(buildQFilter(filterModel)))}&fields=${visibleFields.join(",")}`;
|
const url = `/data/${tableMetaData.name}/export/${filename}?filter=${encodeURIComponent(JSON.stringify(buildQFilter(tableMetaData, filterModel)))}&fields=${visibleFields.join(",")}`;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
// open a window (tab) with a little page that says the file is being generated. //
|
// open a window (tab) with a little page that says the file is being generated. //
|
||||||
@ -742,7 +749,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
if (selectFullFilterState === "filter")
|
if (selectFullFilterState === "filter")
|
||||||
{
|
{
|
||||||
return `?recordsParam=filterJSON&filterJSON=${JSON.stringify(buildQFilter(filterModel))}`;
|
return `?recordsParam=filterJSON&filterJSON=${JSON.stringify(buildQFilter(tableMetaData, filterModel))}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedIds.length > 0)
|
if (selectedIds.length > 0)
|
||||||
@ -757,7 +764,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
if (selectFullFilterState === "filter")
|
if (selectFullFilterState === "filter")
|
||||||
{
|
{
|
||||||
setRecordIdsForProcess(buildQFilter(filterModel));
|
setRecordIdsForProcess(buildQFilter(tableMetaData, filterModel));
|
||||||
}
|
}
|
||||||
else if (selectedIds.length > 0)
|
else if (selectedIds.length > 0)
|
||||||
{
|
{
|
||||||
|
@ -379,3 +379,8 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboardDropdownMenu #timeframe-form label
|
||||||
|
{
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
@ -175,7 +175,10 @@ export default class DataGridUtils
|
|||||||
filterOperators: filterOperators,
|
filterOperators: filterOperators,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (columnsToRender[field.name])
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// looks like, maybe we can just always render all columns, and remove this parameter? //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if (columnsToRender == null || columnsToRender[field.name])
|
||||||
{
|
{
|
||||||
column.renderCell = (cellValues: any) => (
|
column.renderCell = (cellValues: any) => (
|
||||||
(cellValues.value)
|
(cellValues.value)
|
||||||
|
@ -314,11 +314,7 @@ class FilterUtils
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
let localDate = new Date(param[i]);
|
let toPush = this.frontendLocalZoneDateTimeStringToUTCStringForBackend(param[i]);
|
||||||
let month = (1 + localDate.getUTCMonth());
|
|
||||||
let zp = FilterUtils.zeroPad;
|
|
||||||
let toPush = localDate.getUTCFullYear() + "-" + zp(month) + "-" + zp(localDate.getUTCDate()) + "T" + zp(localDate.getUTCHours()) + ":" + zp(localDate.getUTCMinutes()) + ":" + zp(localDate.getUTCSeconds()) + "Z";
|
|
||||||
console.log(`Input date was ${localDate}. Sending to backend as ${toPush}`);
|
|
||||||
rs.push(toPush);
|
rs.push(toPush);
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
@ -336,6 +332,22 @@ class FilterUtils
|
|||||||
return (rs);
|
return (rs);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Take a string date (w/o a timezone) like that our calendar widgets make,
|
||||||
|
** and convert it to UTC, e.g., for submitting to the backend.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static frontendLocalZoneDateTimeStringToUTCStringForBackend(param: string)
|
||||||
|
{
|
||||||
|
let localDate = new Date(param);
|
||||||
|
let month = (1 + localDate.getUTCMonth());
|
||||||
|
let zp = FilterUtils.zeroPad;
|
||||||
|
let toPush = localDate.getUTCFullYear() + "-" + zp(month) + "-" + zp(localDate.getUTCDate()) + "T" + zp(localDate.getUTCHours()) + ":" + zp(localDate.getUTCMinutes()) + ":" + zp(localDate.getUTCSeconds()) + "Z";
|
||||||
|
console.log(`Input date was ${localDate}. Sending to backend as ${toPush}`);
|
||||||
|
return toPush;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Convert a filter field's value from the style that qqq uses, to the style that
|
** Convert a filter field's value from the style that qqq uses, to the style that
|
||||||
** the grid uses.
|
** the grid uses.
|
||||||
|
@ -353,6 +353,21 @@ class ValueUtils
|
|||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
return (value + "T00:00");
|
return (value + "T00:00");
|
||||||
}
|
}
|
||||||
|
else if (value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?Z$/))
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// If the passed in string has a Z on the end (e.g., in UTC) - make a Date object - the browser will //
|
||||||
|
// shift the value into the user's time zone, so it will display correctly for them //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const date = new Date(value);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const formattedDate = `${date.toString("yyyy-MM-ddTHH:mm")}`
|
||||||
|
|
||||||
|
console.log(`Converted UTC date value string [${value}] to local time value for form [${formattedDate}]`)
|
||||||
|
|
||||||
|
return (formattedDate);
|
||||||
|
}
|
||||||
else if (value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}.*/))
|
else if (value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}.*/))
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Reference in New Issue
Block a user