mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 12:50:43 +00:00
CE-798 bug fixes after qa
This commit is contained in:
@ -586,11 +586,11 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, vie
|
||||
{
|
||||
formData.append("tableName", tableMetaData.name);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// clone view via json serialization/deserialization //
|
||||
// then replace the queryFilter in it with a copy that has had its possible values changed to ids //
|
||||
// then stringify that for the backend //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// clone view via json serialization/deserialization //
|
||||
// then replace the viewJson in it with a copy that has had its possible values changed to ids //
|
||||
// then stringify that for the backend //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const viewObject = JSON.parse(JSON.stringify(view));
|
||||
viewObject.queryFilter = JSON.parse(JSON.stringify(FilterUtils.convertFilterPossibleValuesToIds(viewObject.queryFilter)));
|
||||
formData.append("viewJson", JSON.stringify(viewObject));
|
||||
|
@ -132,6 +132,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
const pathParts = location.pathname.replace(/\/+$/, "").split("/");
|
||||
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
const [isFirstRenderAfterChangingTables, setIsFirstRenderAfterChangingTables] = useState(false);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// manage "state" being passed from some screens (like delete) into query screen - by grabbing, and then deleting //
|
||||
@ -180,6 +181,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
// only load things out of local storage on the first render
|
||||
if(firstRender)
|
||||
{
|
||||
console.log("This is firstRender, so reading defaults from local storage...");
|
||||
if (localStorage.getItem(densityLocalStorageKey))
|
||||
{
|
||||
defaultDensity = JSON.parse(localStorage.getItem(densityLocalStorageKey));
|
||||
@ -192,6 +194,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
defaultView = RecordQueryView.buildFromJSON(localStorage.getItem(viewLocalStorageKey));
|
||||
}
|
||||
|
||||
setFirstRender(false);
|
||||
}
|
||||
|
||||
if(defaultView == null)
|
||||
@ -279,7 +283,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
const [tableVariantPromptOpen, setTableVariantPromptOpen] = useState(false);
|
||||
const [alertContent, setAlertContent] = useState("");
|
||||
const [currentSavedView, setCurrentSavedView] = useState(null as QRecord);
|
||||
const [filterIdInLocation, setFilterIdInLocation] = useState(null as number);
|
||||
const [viewIdInLocation, setViewIdInLocation] = useState(null as number);
|
||||
const [loadingSavedView, setLoadingSavedView] = useState(false);
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
@ -350,6 +354,14 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
const [pageLoadingState, _] = useState(new LoadingState(forceUpdate))
|
||||
|
||||
if(isFirstRenderAfterChangingTables)
|
||||
{
|
||||
setIsFirstRenderAfterChangingTables(false);
|
||||
|
||||
console.log("This is the first render after changing tables - so - setting state based on 'defaults' from localStorage");
|
||||
setView(defaultView)
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** utility function to get the names of any join tables which are active,
|
||||
** either as a visible column, or as a query criteria
|
||||
@ -560,16 +572,16 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// the path for a savedView looks like: .../table/savedView/32 //
|
||||
// so if path has '/savedView/' get last parsed string //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// so if path has '/savedView/' get last parsed string //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
let currentSavedViewId = null as number;
|
||||
if (location.pathname.indexOf("/savedView/") != -1)
|
||||
{
|
||||
const parts = location.pathname.split("/");
|
||||
currentSavedViewId = Number.parseInt(parts[parts.length - 1]);
|
||||
setFilterIdInLocation(currentSavedViewId);
|
||||
setViewIdInLocation(currentSavedViewId);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// in case page-state has already advanced to "ready" (e.g., and we're dealing with a user //
|
||||
@ -603,7 +615,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
setActiveModalProcess(null);
|
||||
|
||||
}, [location, tableMetaData]);
|
||||
}, [location]);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -1137,7 +1149,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
///////////////////////////////////////////////////////
|
||||
// propagate filter's orderBy into grid's sort model //
|
||||
///////////////////////////////////////////////////////
|
||||
const gridSort = FilterUtils.getGridSortFromQueryFilter(view.queryFilter);
|
||||
const gridSort = FilterUtils.getGridSortFromQueryFilter(queryFilter);
|
||||
setColumnSortModel(gridSort);
|
||||
|
||||
///////////////////////////////////////////////
|
||||
@ -1404,22 +1416,16 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
** wrapper around setting current saved view (as a QRecord) - which also activates
|
||||
** that view.
|
||||
*******************************************************************************/
|
||||
const doSetCurrentSavedView = (savedView: QRecord) =>
|
||||
const doSetCurrentSavedView = (savedViewRecord: QRecord) =>
|
||||
{
|
||||
setCurrentSavedView(savedView);
|
||||
setCurrentSavedView(savedViewRecord);
|
||||
|
||||
if(savedView)
|
||||
if(savedViewRecord)
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const viewJson = savedView.values.get("viewJson")
|
||||
const viewJson = savedViewRecord.values.get("viewJson")
|
||||
const newView = RecordQueryView.buildFromJSON(viewJson);
|
||||
newView.viewIdentity = "savedView:" + savedView.values.get("id");
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// e.g., translate possible values from ids to objects w/ labels //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, newView.queryFilter);
|
||||
|
||||
activateView(newView);
|
||||
|
||||
@ -1431,7 +1437,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
////////////////////////////////////////////////////////////////
|
||||
// todo can/should/does this move into the view's "identity"? //
|
||||
////////////////////////////////////////////////////////////////
|
||||
localStorage.setItem(currentSavedViewLocalStorageKey, `${savedView.values.get("id")}`);
|
||||
localStorage.setItem(currentSavedViewLocalStorageKey, `${savedViewRecord.values.get("id")}`);
|
||||
})()
|
||||
}
|
||||
else
|
||||
@ -1488,11 +1494,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
/*******************************************************************************
|
||||
** utility function to fetch a saved view from the backend.
|
||||
*******************************************************************************/
|
||||
const fetchSavedView = async (filterId: number): Promise<QRecord> =>
|
||||
const fetchSavedView = async (id: number): Promise<QRecord> =>
|
||||
{
|
||||
let qRecord = null;
|
||||
const formData = new FormData();
|
||||
formData.append("id", filterId);
|
||||
formData.append("id", id);
|
||||
formData.append(QController.STEP_TIMEOUT_MILLIS_PARAM_NAME, 60 * 1000);
|
||||
const processResult = await qController.processInit("querySavedView", formData, qController.defaultMultipartFormDataHeaders());
|
||||
if (processResult instanceof QJobError)
|
||||
@ -1504,12 +1510,73 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
const result = processResult as QJobComplete;
|
||||
qRecord = new QRecord(result.values.savedViewList[0]);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// make the view json a good and healthy object for the UI here. //
|
||||
// such as, making values be what they'd be in the UI (not necessarily //
|
||||
// what they're like in the backend); similarly, set anything that's unset. //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
const viewJson = qRecord.values.get("viewJson")
|
||||
const view = RecordQueryView.buildFromJSON(viewJson);
|
||||
view.viewIdentity = "savedView:" + id;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// e.g., translate possible values from ids to objects w/ labels //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, view.queryFilter);
|
||||
|
||||
///////////////////////////
|
||||
// set columns if absent //
|
||||
///////////////////////////
|
||||
if(!view.queryColumns || !view.queryColumns.columns || view.queryColumns.columns?.length == 0)
|
||||
{
|
||||
view.queryColumns = QQueryColumns.buildDefaultForTable(tableMetaData);
|
||||
}
|
||||
|
||||
qRecord.values.set("viewJson", JSON.stringify(view))
|
||||
}
|
||||
|
||||
return (qRecord);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** event handler for selecting 'filter' action from columns menu in advanced mode.
|
||||
*******************************************************************************/
|
||||
const handleColumnMenuAdvancedFilterSelection = (fieldName: string) =>
|
||||
{
|
||||
const newCriteria = new QFilterCriteria(fieldName, null, []);
|
||||
|
||||
if(!queryFilter.criteria)
|
||||
{
|
||||
queryFilter.criteria = [];
|
||||
}
|
||||
|
||||
const length = queryFilter.criteria.length;
|
||||
if (length > 0 && !queryFilter.criteria[length - 1].fieldName)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// if the last criteria in the filter has no field name (e.g., a default state //
|
||||
// when there's 1 criteria that's all blank - may happen other times too?), //
|
||||
// then replace that criteria with a new one for this field. //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
queryFilter.criteria[length - 1] = newCriteria;
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// else, add a new criteria for this field onto the end of the list //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
queryFilter.criteria.push(newCriteria);
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
// open the filter panel //
|
||||
///////////////////////////
|
||||
gridApiRef.current.showPreferences(GridPreferencePanelsValue.filters)
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** event handler from columns menu - that copies values from that column
|
||||
*******************************************************************************/
|
||||
@ -1550,7 +1617,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
*******************************************************************************/
|
||||
const openColumnStatistics = async (column: GridColDef) =>
|
||||
{
|
||||
setFilterForColumnStats(queryFilter);
|
||||
setFilterForColumnStats(prepQueryFilterForBackend(queryFilter));
|
||||
setColumnStatsFieldName(column.field);
|
||||
|
||||
const [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, column.field);
|
||||
@ -1598,26 +1665,21 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
<GridColumnMenuContainer ref={ref} {...props}>
|
||||
<SortGridMenuItems onClick={hideMenu} column={currentColumn!} />
|
||||
|
||||
<MenuItem onClick={(e) =>
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// in advanced mode, use the default GridFilterMenuItem, which punches into the advanced/filter-builder UI //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
mode == "advanced" && <GridFilterMenuItem onClick={hideMenu} column={currentColumn!} />
|
||||
}
|
||||
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// for basic mode, use our own menu item to turn on this field as a quick-filter //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
mode == "basic" && <MenuItem onClick={(e) =>
|
||||
hideMenu(e);
|
||||
if(mode == "advanced")
|
||||
{
|
||||
handleColumnMenuAdvancedFilterSelection(currentColumn.field);
|
||||
}
|
||||
else
|
||||
{
|
||||
hideMenu(e);
|
||||
// @ts-ignore
|
||||
basicAndAdvancedQueryControlsRef.current.addField(currentColumn.field);
|
||||
}}>
|
||||
Filter
|
||||
</MenuItem>
|
||||
}
|
||||
}
|
||||
}}>
|
||||
Filter
|
||||
</MenuItem>
|
||||
|
||||
<HideGridColMenuItem onClick={hideMenu} column={currentColumn!} />
|
||||
<GridColumnsMenuItem onClick={hideMenu} column={currentColumn!} />
|
||||
@ -1662,29 +1724,32 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
const CustomColumnHeaderFilterIconButton = forwardRef<any, ColumnHeaderFilterIconButtonProps>(
|
||||
function ColumnHeaderFilterIconButton(props: ColumnHeaderFilterIconButtonProps, ref)
|
||||
{
|
||||
if(mode == "basic")
|
||||
let showFilter = false;
|
||||
for (let i = 0; i < queryFilter?.criteria?.length; i++)
|
||||
{
|
||||
let showFilter = false;
|
||||
for (let i = 0; i < queryFilter?.criteria?.length; i++)
|
||||
const criteria = queryFilter.criteria[i];
|
||||
if(criteria.fieldName == props.field && validateCriteria(criteria, null).criteriaIsValid)
|
||||
{
|
||||
const criteria = queryFilter.criteria[i];
|
||||
if(criteria.fieldName == props.field && criteria.operator)
|
||||
{
|
||||
// todo - test values too right?
|
||||
showFilter = true;
|
||||
}
|
||||
showFilter = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(showFilter)
|
||||
if(showFilter)
|
||||
{
|
||||
return (<IconButton size="small" sx={{p: "2px"}} onClick={(event) =>
|
||||
{
|
||||
return (<IconButton size="small" sx={{p: "2px"}} onClick={(event) =>
|
||||
if(mode == "basic")
|
||||
{
|
||||
// @ts-ignore !?
|
||||
basicAndAdvancedQueryControlsRef.current.addField(props.field);
|
||||
}
|
||||
else
|
||||
{
|
||||
gridApiRef.current.showPreferences(GridPreferencePanelsValue.filters)
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
}}><Icon fontSize="small">filter_alt</Icon></IconButton>);
|
||||
}
|
||||
event.stopPropagation();
|
||||
}}><Icon fontSize="small">filter_alt</Icon></IconButton>);
|
||||
}
|
||||
|
||||
return (<></>);
|
||||
@ -1815,7 +1880,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
totalRecords: totalRecords,
|
||||
columnsModel: columnsModel,
|
||||
columnVisibilityModel: columnVisibilityModel,
|
||||
queryFilter: queryFilter
|
||||
queryFilter: prepQueryFilterForBackend(queryFilter)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -2063,9 +2128,9 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
setAlertContent("Error parsing filter from URL");
|
||||
}
|
||||
}
|
||||
else if (filterIdInLocation)
|
||||
else if (viewIdInLocation)
|
||||
{
|
||||
if(view.viewIdentity == `savedView:${filterIdInLocation}`)
|
||||
if(view.viewIdentity == `savedView:${viewIdInLocation}`)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the view id in the location is the same as the view that was most-recently active here, //
|
||||
@ -2073,14 +2138,14 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
// we want to keep their current settings as the active view - thus - use the current 'view' //
|
||||
// state variable (e.g., from local storage) as the view to be activated. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
console.log(`Initializing view to a (potentially dirty) saved view (id=${filterIdInLocation})`);
|
||||
console.log(`Initializing view to a (potentially dirty) saved view (id=${viewIdInLocation})`);
|
||||
activateView(view);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// now fetch that savedView, and set it in state, but don't activate it - because that would overwrite //
|
||||
// anything the user may have changed (e.g., anything in the local-storage/state view). //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const savedViewRecord = await fetchSavedView(filterIdInLocation);
|
||||
const savedViewRecord = await fetchSavedView(viewIdInLocation);
|
||||
setCurrentSavedView(savedViewRecord);
|
||||
}
|
||||
else
|
||||
@ -2088,12 +2153,32 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's a filterId in the location, but it isn't the last one the user had active, then set that as our active view //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
console.log(`Initializing view to a clean saved view (id=${filterIdInLocation})`);
|
||||
await handleSavedViewChange(filterIdInLocation);
|
||||
console.log(`Initializing view to a clean saved view (id=${viewIdInLocation})`);
|
||||
await handleSavedViewChange(viewIdInLocation);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the last time we were on this table, a currentSavedView was written to local storage - //
|
||||
// then navigate back to that view's URL //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (localStorage.getItem(currentSavedViewLocalStorageKey))
|
||||
{
|
||||
const currentSavedViewId = Number.parseInt(localStorage.getItem(currentSavedViewLocalStorageKey));
|
||||
console.log(`returning to previously active saved view ${currentSavedViewId}`);
|
||||
navigate(`${metaData.getTablePathByName(tableName)}/savedView/${currentSavedViewId}`);
|
||||
setViewIdInLocation(currentSavedViewId);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return - without activating any view, and actually, reset the pageState back to loadedMetaData, //
|
||||
// so the useEffect that monitors location will see the change, and will set viewIdInLocation //
|
||||
// so upon a re-render we'll hit this block again. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
setPageState("loadedMetaData")
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// view is ad-hoc - just activate the view that was last active //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
@ -2207,6 +2292,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
setQueryFilter(new QQueryFilter());
|
||||
setQueryColumns(new PreLoadQueryColumns());
|
||||
setRows([]);
|
||||
setIsFirstRenderAfterChangingTables(true);
|
||||
|
||||
return (getLoadingScreen());
|
||||
}
|
||||
@ -2264,15 +2350,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
return (getLoadingScreen());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// for basic mode, set a custom ColumnHeaderFilterIconButton - w/ action to activate basic-mode quick-filter //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
let restOfDataGridProCustomComponents: any = {}
|
||||
if(mode == "basic")
|
||||
{
|
||||
restOfDataGridProCustomComponents.ColumnHeaderFilterIconButton = CustomColumnHeaderFilterIconButton;
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// main screen render //
|
||||
////////////////////////
|
||||
@ -2379,7 +2456,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
ColumnMenu: CustomColumnMenu,
|
||||
ColumnsPanel: CustomColumnsPanel,
|
||||
FilterPanel: CustomFilterPanel,
|
||||
... restOfDataGridProCustomComponents
|
||||
ColumnHeaderFilterIconButton: CustomColumnHeaderFilterIconButton,
|
||||
}}
|
||||
componentsProps={{
|
||||
columnsPanel:
|
||||
|
@ -129,18 +129,28 @@ class FilterUtils
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// replace objects that look like expressions with expression instances //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
if (values && values.length)
|
||||
{
|
||||
for (let i = 0; i < values.length; i++)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// replace objects that look like expressions with expression instances //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
const expression = this.gridCriteriaValueToExpression(values[i]);
|
||||
if (expression)
|
||||
{
|
||||
values[i] = expression;
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// make date-times work for the frontend //
|
||||
///////////////////////////////////////////
|
||||
if (field.type == QFieldType.DATE_TIME)
|
||||
{
|
||||
values[i] = ValueUtils.formatDateTimeValueForForm(values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user