Compare commits

..

14 Commits

Author SHA1 Message Date
bcade32ed1 CE-1107: style updates to icon 2024-04-15 21:37:46 -05:00
8071c54ccd CE-1107: updates to date picker styles 2024-04-15 09:01:31 -05:00
334871988b CE-1107: added alert widget, fixed axios problems 2024-04-12 14:47:37 -05:00
ddb055bc81 Merge pull request #53 from Kingsrook/feature/CE-1072-update-how-we-display-imported
CE-1072 return displayValue for DATE_TIME fields (if they're differen…
2024-04-05 11:30:07 -05:00
66ddf4cb57 CE-1072 return displayValue for DATE_TIME fields (if they're different from the raw value) 2024-04-04 20:06:00 -05:00
7e15f4601d Merged feature/quartz-scheduler into main 2024-03-29 18:35:41 -05:00
cdb61695d1 Merge pull request #50 from Kingsrook/feature/CE-1120-bug-order-statuses-not
CE-1120: updated to handle errors on join tables (specifically was ha…
2024-03-28 15:20:10 -05:00
5e3991d9ae CE-1120: updated to handle errors on join tables (specifically was happening with deposco customer orders) 2024-03-28 15:09:56 -05:00
f1826c81a9 Merge pull request #48 from Kingsrook/bugfix/null-field-names-in-criteria
Strip away null field names in criteria (e.g., from incomplete advanc…
2024-03-27 20:14:01 -05:00
230aaeef8c Strip away null field names in criteria (e.g., from incomplete advanced filters) when storing in local storage, in saved views, and any time we load a view. 2024-03-21 16:41:09 -05:00
c08696b3a1 Remove todo no longer needed 2024-03-20 10:34:37 -05:00
84e627467f CE-936 - Update to receive warnings within the response QRecord and display them (this fixes inserts that warn) 2024-03-19 11:13:58 -05:00
6c524c4eca CE-936 - Fix editing child records; fix warning icon on view screen 2024-03-19 10:41:03 -05:00
edab918763 CE-969: fixed flex wrapping on advanced queries, recursive calls to 'clean values' and 'prep subquery for backend' 2024-03-18 19:39:19 -05:00
26 changed files with 1680 additions and 1209 deletions

2200
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,14 @@
"@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.89", "@kingsrook/qqq-frontend-core": "1.0.94",
"@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",
"@mui/system": "5.11.1", "@mui/system": "5.11.1",
"@mui/x-data-grid": "5.17.23", "@mui/x-data-grid": "5.17.23",
"@mui/x-data-grid-pro": "5.17.23", "@mui/x-data-grid-pro": "5.17.23",
"@mui/x-date-pickers": "7.1.1",
"@mui/x-license-pro": "5.12.3", "@mui/x-license-pro": "5.12.3",
"@react-jvectormap/core": "1.0.1", "@react-jvectormap/core": "1.0.1",
"@react-jvectormap/unitedstates": "1.0.1", "@react-jvectormap/unitedstates": "1.0.1",
@ -26,6 +27,7 @@
"chroma-js": "2.4.2", "chroma-js": "2.4.2",
"cmdk": "0.2.0", "cmdk": "0.2.0",
"datejs": "1.0.0-rc3", "datejs": "1.0.0-rc3",
"dayjs": "1.11.10",
"downshift": "3.2.10", "downshift": "3.2.10",
"faker": "5.5.3", "faker": "5.5.3",
"form-data": "4.0.0", "form-data": "4.0.0",

View File

@ -79,7 +79,7 @@ export default function App()
Client.setUnauthorizedCallback(() => Client.setUnauthorizedCallback(() =>
{ {
logout(); logout();
}) });
const shouldStoreNewToken = (newToken: string, oldToken: string): boolean => const shouldStoreNewToken = (newToken: string, oldToken: string): boolean =>
{ {
@ -104,7 +104,7 @@ export default function App()
// if the old (local storage) token is expired, then we need to store the new one // // if the old (local storage) token is expired, then we need to store the new one //
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
const oldExp = oldJSON["exp"]; const oldExp = oldJSON["exp"];
if(oldExp * 1000 < (new Date().getTime())) if (oldExp * 1000 < (new Date().getTime()))
{ {
console.log("Access token in local storage was expired - so we should store a new one."); console.log("Access token in local storage was expired - so we should store a new one.");
return (true); return (true);
@ -114,21 +114,21 @@ export default function App()
// remove the exp & iat values from what we compare - as they are always different from auth0 // // remove the exp & iat values from what we compare - as they are always different from auth0 //
// note, this is only deleting them from what we compare, not from what we'd store. // // note, this is only deleting them from what we compare, not from what we'd store. //
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
delete newJSON["exp"] delete newJSON["exp"];
delete newJSON["iat"] delete newJSON["iat"];
delete oldJSON["exp"] delete oldJSON["exp"];
delete oldJSON["iat"] delete oldJSON["iat"];
const different = JSON.stringify(newJSON) !== JSON.stringify(oldJSON); const different = JSON.stringify(newJSON) !== JSON.stringify(oldJSON);
if(different) if (different)
{ {
console.log("Latest access token from auth0 has changed vs localStorage - so we should store a new one."); console.log("Latest access token from auth0 has changed vs localStorage - so we should store a new one.");
} }
return (different); return (different);
} }
catch(e) catch (e)
{ {
console.log("Caught in shouldStoreNewToken: " + e) console.log("Caught in shouldStoreNewToken: " + e);
} }
return (true); return (true);
@ -185,7 +185,7 @@ export default function App()
{ {
console.log(`Error loading token: ${JSON.stringify(e)}`); console.log(`Error loading token: ${JSON.stringify(e)}`);
qController.clearAuthenticationMetaDataLocalStorage(); qController.clearAuthenticationMetaDataLocalStorage();
localStorage.removeItem("accessToken") localStorage.removeItem("accessToken");
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"}); removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
logout(); logout();
return; return;
@ -550,7 +550,7 @@ export default function App()
}); });
} }
const pathToLabelMap: {[path: string]: string} = {} const pathToLabelMap: { [path: string]: string } = {};
for (let i = 0; i < appRoutesList.length; i++) for (let i = 0; i < appRoutesList.length; i++)
{ {
const route = appRoutesList[i]; const route = appRoutesList[i];
@ -575,11 +575,11 @@ export default function App()
console.error(e); console.error(e);
if (e instanceof QException) if (e instanceof QException)
{ {
if ((e as QException).status === "401") if ((e as QException).status === 401)
{ {
console.log("Exception is a QException with status = 401. Clearing some of localStorage & cookies"); console.log("Exception is a QException with status = 401. Clearing some of localStorage & cookies");
qController.clearAuthenticationMetaDataLocalStorage(); qController.clearAuthenticationMetaDataLocalStorage();
localStorage.removeItem("accessToken") localStorage.removeItem("accessToken");
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"}); removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
@ -656,7 +656,7 @@ export default function App()
const [pageHeader, setPageHeader] = useState("" as string | JSX.Element); const [pageHeader, setPageHeader] = useState("" as string | JSX.Element);
const [accentColor, setAccentColor] = useState("#0062FF"); const [accentColor, setAccentColor] = useState("#0062FF");
const [accentColorLight, setAccentColorLight] = useState("#C0D6F7") const [accentColorLight, setAccentColorLight] = useState("#C0D6F7");
const [tableMetaData, setTableMetaData] = useState(null); const [tableMetaData, setTableMetaData] = useState(null);
const [tableProcesses, setTableProcesses] = useState(null); const [tableProcesses, setTableProcesses] = useState(null);
const [dotMenuOpen, setDotMenuOpen] = useState(false); const [dotMenuOpen, setDotMenuOpen] = useState(false);

View File

@ -34,10 +34,10 @@ import ToggleButton from "@mui/material/ToggleButton";
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import React, {useContext, useEffect, useState} from "react";
import QContext from "QContext"; import QContext from "QContext";
import Client from "qqq/utils/qqq/Client"; import Client from "qqq/utils/qqq/Client";
import ValueUtils from "qqq/utils/qqq/ValueUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils";
import React, {useContext, useEffect, useState} from "react";
interface Props interface Props
{ {
@ -217,7 +217,7 @@ function AuditBody({tableMetaData, recordId, record}: Props): JSX.Element
{ {
if (e instanceof QException) if (e instanceof QException)
{ {
if ((e as QException).status === "403") if ((e as QException).status === 403)
{ {
setStatusString("You do not have permission to view audits"); setStatusString("You do not have permission to view audits");
return; return;

View File

@ -243,10 +243,10 @@ function EntityForm(props: Props): JSX.Element
switch(action) switch(action)
{ {
case "insert": case "insert":
newChildListWidgetData[widgetName].queryOutput.records.push(new QRecord({values: values})) newChildListWidgetData[widgetName].queryOutput.records.push({values: values})
break; break;
case "edit": case "edit":
newChildListWidgetData[widgetName].queryOutput.records[rowIndex] = new QRecord({values: values}); newChildListWidgetData[widgetName].queryOutput.records[rowIndex] = {values: values};
break; break;
case "delete": case "delete":
newChildListWidgetData[widgetName].queryOutput.records.splice(rowIndex, 1); newChildListWidgetData[widgetName].queryOutput.records.splice(rowIndex, 1);
@ -734,8 +734,6 @@ function EntityForm(props: Props): JSX.Element
} }
} }
// todo - associations + copy might be a "bad time"
const associationsToPost: any = {} const associationsToPost: any = {}
let haveAssociationsToPost = false; let haveAssociationsToPost = false;
for (let name of Object.keys(childListWidgetData)) for (let name of Object.keys(childListWidgetData))
@ -747,7 +745,7 @@ function EntityForm(props: Props): JSX.Element
} }
associationsToPost[manageAssociationName] = []; associationsToPost[manageAssociationName] = [];
haveAssociationsToPost = true; haveAssociationsToPost = true;
for(let i=0; i<childListWidgetData[name].queryOutput.records.length; i++) for(let i=0; i<childListWidgetData[name].queryOutput?.records?.length; i++)
{ {
associationsToPost[manageAssociationName].push(childListWidgetData[name].queryOutput.records[i].values); associationsToPost[manageAssociationName].push(childListWidgetData[name].queryOutput.records[i].values);
} }
@ -759,7 +757,9 @@ function EntityForm(props: Props): JSX.Element
if (props.id !== null && !props.isCopy) if (props.id !== null && !props.isCopy)
{ {
// todo - audit that it's a dupe ///////////////////////
// perform an update //
///////////////////////
await qController await qController
.update(tableName, props.id, valuesToPost) .update(tableName, props.id, valuesToPost)
.then((record) => .then((record) =>
@ -770,8 +770,14 @@ function EntityForm(props: Props): JSX.Element
} }
else else
{ {
let warningMessage = null;
if(record.warnings && record.warnings.length && record.warnings.length > 0)
{
warningMessage = record.warnings[0];
}
const path = location.pathname.replace(/\/edit$/, ""); const path = location.pathname.replace(/\/edit$/, "");
navigate(path, {state: {updateSuccess: true}}); navigate(path, {state: {updateSuccess: true, warning: warningMessage}});
} }
}) })
.catch((error) => .catch((error) =>
@ -793,6 +799,10 @@ function EntityForm(props: Props): JSX.Element
} }
else else
{ {
/////////////////////////////////
// perform an insert //
// todo - audit if it's a dupe //
/////////////////////////////////
await qController await qController
.create(tableName, valuesToPost) .create(tableName, valuesToPost)
.then((record) => .then((record) =>
@ -803,10 +813,16 @@ function EntityForm(props: Props): JSX.Element
} }
else else
{ {
let warningMessage = null;
if(record.warnings && record.warnings.length && record.warnings.length > 0)
{
warningMessage = record.warnings[0];
}
const path = props.isCopy ? const path = props.isCopy ?
location.pathname.replace(new RegExp(`/${props.id}/copy$`), "/" + record.values.get(tableMetaData.primaryKeyField)) location.pathname.replace(new RegExp(`/${props.id}/copy$`), "/" + record.values.get(tableMetaData.primaryKeyField))
: location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField)); : location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField));
navigate(path, {state: {createSuccess: true}}); navigate(path, {state: {createSuccess: true, warning: warningMessage}});
} }
}) })
.catch((error) => .catch((error) =>

View File

@ -25,7 +25,7 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete"; import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError"; import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Alert, Button, Link} from "@mui/material"; import {Alert, Button} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Dialog from "@mui/material/Dialog"; import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions"; import DialogActions from "@mui/material/DialogActions";
@ -40,14 +40,14 @@ import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import {TooltipProps} from "@mui/material/Tooltip/Tooltip"; import {TooltipProps} from "@mui/material/Tooltip/Tooltip";
import FormData from "form-data"; import FormData from "form-data";
import React, {useContext, useEffect, useRef, useState} from "react";
import {useLocation, useNavigate} from "react-router-dom";
import QContext from "QContext"; import QContext from "QContext";
import colors from "qqq/assets/theme/base/colors"; import colors from "qqq/assets/theme/base/colors";
import {QCancelButton, QDeleteButton, QSaveButton} from "qqq/components/buttons/DefaultButtons"; import {QCancelButton, QDeleteButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
import RecordQueryView from "qqq/models/query/RecordQueryView"; import RecordQueryView from "qqq/models/query/RecordQueryView";
import FilterUtils from "qqq/utils/qqq/FilterUtils"; import FilterUtils from "qqq/utils/qqq/FilterUtils";
import {SavedViewUtils} from "qqq/utils/qqq/SavedViewUtils"; import {SavedViewUtils} from "qqq/utils/qqq/SavedViewUtils";
import React, {useContext, useEffect, useRef, useState} from "react";
import {useLocation, useNavigate} from "react-router-dom";
interface Props interface Props
{ {
@ -227,6 +227,12 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
///////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////
const viewObject = JSON.parse(JSON.stringify(view)); const viewObject = JSON.parse(JSON.stringify(view));
viewObject.queryFilter = JSON.parse(JSON.stringify(FilterUtils.convertFilterPossibleValuesToIds(viewObject.queryFilter))); viewObject.queryFilter = JSON.parse(JSON.stringify(FilterUtils.convertFilterPossibleValuesToIds(viewObject.queryFilter)));
////////////////////////////////////////////////////////////////////////////
// strip away incomplete filters too, just for cleaner saved view filters //
////////////////////////////////////////////////////////////////////////////
FilterUtils.stripAwayIncompleteCriteria(viewObject.queryFilter)
formData.append("viewJson", JSON.stringify(viewObject)); formData.append("viewJson", JSON.stringify(viewObject));
if (isSaveFilterAs || isRenameFilter || currentSavedView == null) if (isSaveFilterAs || isRenameFilter || currentSavedView == null)

View File

@ -410,7 +410,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
let counter = 0; let counter = 0;
return ( return (
<Box display="flex" flexWrap="wrap" fontSize="0.875rem"> <React.Fragment>
{thisQueryFilter.criteria?.map((criteria, i) => {thisQueryFilter.criteria?.map((criteria, i) =>
{ {
const {criteriaIsValid} = validateCriteria(criteria, null); const {criteriaIsValid} = validateCriteria(criteria, null);
@ -446,7 +446,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
</React.Fragment> </React.Fragment>
); );
}))} }))}
</Box> </React.Fragment>
); );
}; };
@ -821,7 +821,9 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
pb={"0.125rem"} pb={"0.125rem"}
boxShadow={"inset 0px 0px 4px 2px #EFEFED"} boxShadow={"inset 0px 0px 4px 2px #EFEFED"}
> >
{queryToAdvancedString(queryFilter)} <Box display="flex" flexWrap="wrap" fontSize="0.875rem">
{queryToAdvancedString(queryFilter)}
</Box>
</Box> </Box>
} }
</Box> </Box>

View File

@ -19,13 +19,12 @@
*/ */
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {Skeleton} from "@mui/material"; import {Alert, Skeleton} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Tab from "@mui/material/Tab"; import Tab from "@mui/material/Tab";
import Tabs from "@mui/material/Tabs"; import Tabs from "@mui/material/Tabs";
import parse from "html-react-parser"; import parse from "html-react-parser";
import React, {useContext, useEffect, useReducer, useState} from "react";
import QContext from "QContext"; import QContext from "QContext";
import MDTypography from "qqq/components/legacy/MDTypography"; import MDTypography from "qqq/components/legacy/MDTypography";
import TabPanel from "qqq/components/misc/TabPanel"; import TabPanel from "qqq/components/misc/TabPanel";
@ -47,10 +46,11 @@ import USMapWidget from "qqq/components/widgets/misc/USMapWidget";
import ParentWidget from "qqq/components/widgets/ParentWidget"; import ParentWidget from "qqq/components/widgets/ParentWidget";
import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard"; import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard";
import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard"; import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard";
import Widget, {HeaderIcon, WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT, LabelComponent} from "qqq/components/widgets/Widget"; import Widget, {HeaderIcon, LabelComponent, WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT} from "qqq/components/widgets/Widget";
import WidgetBlock from "qqq/components/widgets/WidgetBlock"; import WidgetBlock from "qqq/components/widgets/WidgetBlock";
import ProcessRun from "qqq/pages/processes/ProcessRun"; import ProcessRun from "qqq/pages/processes/ProcessRun";
import Client from "qqq/utils/qqq/Client"; import Client from "qqq/utils/qqq/Client";
import React, {useContext, useEffect, useReducer, useState} from "react";
import TableWidget from "./tables/TableWidget"; import TableWidget from "./tables/TableWidget";
@ -91,9 +91,9 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
let initialSelectedTab = 0; let initialSelectedTab = 0;
let selectedTabKey: string = null; let selectedTabKey: string = null;
if(parentWidgetMetaData && wrapWidgetsInTabPanels) if (parentWidgetMetaData && wrapWidgetsInTabPanels)
{ {
selectedTabKey = `qqq.widgets.selectedTabs.${parentWidgetMetaData.name}` selectedTabKey = `qqq.widgets.selectedTabs.${parentWidgetMetaData.name}`;
if (localStorage.getItem(selectedTabKey)) if (localStorage.getItem(selectedTabKey))
{ {
initialSelectedTab = Number(localStorage.getItem(selectedTabKey)); initialSelectedTab = Number(localStorage.getItem(selectedTabKey));
@ -191,7 +191,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
const metaDataToUse = (thisWidgetHasDropdowns) ? widgetMetaData : parentWidgetMetaData; const metaDataToUse = (thisWidgetHasDropdowns) ? widgetMetaData : parentWidgetMetaData;
for (let i = 0; i < metaDataToUse.dropdowns.length; i++) for (let i = 0; i < metaDataToUse.dropdowns.length; i++)
{ {
const dropdownName = metaDataToUse.dropdowns[i].possibleValueSourceName; const dropdownName = metaDataToUse.dropdowns[i].possibleValueSourceName ?? metaDataToUse.dropdowns[i].name;
const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${metaDataToUse.name}.${dropdownName}`; const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${metaDataToUse.name}.${dropdownName}`;
const json = JSON.parse(localStorage.getItem(localStorageKey)); const json = JSON.parse(localStorage.getItem(localStorageKey));
if (json) if (json)
@ -286,6 +286,21 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
/> />
) )
} }
{
widgetMetaData.type === "alert" && widgetData[i]?.html && (
<Widget
omitPadding={true}
widgetMetaData={widgetMetaData}
widgetData={widgetData[i]}
reloadWidgetCallback={(data) => reloadWidget(i, data)}
isChild={areChildren}
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
labelAdditionalComponentsLeft={labelAdditionalComponentsLeft}
>
<Alert severity={widgetData[i]?.alertType?.toLowerCase()}>{parse(widgetData[i]?.html)}</Alert>
</Widget>
)
}
{ {
widgetMetaData.type === "usaMap" && ( widgetMetaData.type === "usaMap" && (
<USMapWidget <USMapWidget
@ -550,7 +565,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
); );
}; };
if(wrapWidgetsInTabPanels) if (wrapWidgetsInTabPanels)
{ {
omitWrappingGridContainer = true; omitWrappingGridContainer = true;
} }
@ -582,7 +597,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
</TabPanel>); </TabPanel>);
} }
return (<React.Fragment key={`${widgetMetaData.name}-${i}`}>{renderedWidget}</React.Fragment>) return (<React.Fragment key={`${widgetMetaData.name}-${i}`}>{renderedWidget}</React.Fragment>);
}) })
} }
</> </>
@ -590,7 +605,8 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
const tabs = widgetMetaDataList && wrapWidgetsInTabPanels ? const tabs = widgetMetaDataList && wrapWidgetsInTabPanels ?
<Tabs <Tabs
sx={{m: 0, mb: 1.5, ml: -2, mr: -2, mt: -3, sx={{
m: 0, mb: 1.5, ml: -2, mr: -2, mt: -3,
"& .MuiTabs-scroller": { "& .MuiTabs-scroller": {
ml: 0 ml: 0
} }
@ -603,7 +619,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
<Tab key={widgetMetaData.name} label={widgetMetaData.label} /> <Tab key={widgetMetaData.name} label={widgetMetaData.label} />
))} ))}
</Tabs> </Tabs>
: <></> : <></>;
return ( return (
widgetCount > 0 ? ( widgetCount > 0 ? (

View File

@ -28,14 +28,14 @@ import Icon from "@mui/material/Icon";
import Tooltip from "@mui/material/Tooltip/Tooltip"; import Tooltip from "@mui/material/Tooltip/Tooltip";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import parse from "html-react-parser"; import parse from "html-react-parser";
import React, {useContext, useEffect, useState} from "react";
import {NavigateFunction, useNavigate} from "react-router-dom";
import QContext from "QContext"; import QContext from "QContext";
import colors from "qqq/assets/theme/base/colors"; import colors from "qqq/assets/theme/base/colors";
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent"; import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
import WidgetDropdownMenu, {DropdownOption} from "qqq/components/widgets/components/WidgetDropdownMenu"; import WidgetDropdownMenu, {DropdownOption} from "qqq/components/widgets/components/WidgetDropdownMenu";
import {WidgetUtils} from "qqq/components/widgets/WidgetUtils"; import {WidgetUtils} from "qqq/components/widgets/WidgetUtils";
import HtmlUtils from "qqq/utils/HtmlUtils"; import HtmlUtils from "qqq/utils/HtmlUtils";
import React, {useContext, useEffect, useState} from "react";
import {NavigateFunction, useNavigate} from "react-router-dom";
export interface WidgetData export interface WidgetData
{ {
@ -240,12 +240,20 @@ export class Dropdown extends LabelComponent
if (localStorageOption) if (localStorageOption)
{ {
const id = localStorageOption.id; const id = localStorageOption.id;
for (let i = 0; i < this.options.length; i++)
if (this.dropdownMetaData.type == "DATE_PICKER")
{ {
if (this.options[i].id == id) defaultValue = id;
}
else
{
for (let i = 0; i < this.options.length; i++)
{ {
defaultValue = this.options[i]; if (this.options[i].id == id)
args.dropdownData[args.componentIndex] = defaultValue?.id; {
defaultValue = this.options[i];
args.dropdownData[args.componentIndex] = defaultValue?.id;
}
} }
} }
} }
@ -299,6 +307,7 @@ export class Dropdown extends LabelComponent
<Box mb={2} sx={{float: "right"}}> <Box mb={2} sx={{float: "right"}}>
<WidgetDropdownMenu <WidgetDropdownMenu
name={this.dropdownName} name={this.dropdownName}
type={this.dropdownMetaData.type}
defaultValue={defaultValue} defaultValue={defaultValue}
sx={{marginLeft: "1rem"}} sx={{marginLeft: "1rem"}}
label={label} label={label}
@ -622,11 +631,11 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
setUsingLabelAsTitle(props.widgetData.isLabelPageTitle); setUsingLabelAsTitle(props.widgetData.isLabelPageTitle);
} }
const helpRoles = ["ALL_SCREENS"] const helpRoles = ["ALL_SCREENS"];
const slotName = "label"; const slotName = "label";
const showHelp = helpHelpActive || hasHelpContent(props.widgetMetaData?.helpContent?.get(slotName), helpRoles); const showHelp = helpHelpActive || hasHelpContent(props.widgetMetaData?.helpContent?.get(slotName), helpRoles);
if(showHelp) if (showHelp)
{ {
const formattedHelpContent = <HelpContent helpContents={props.widgetMetaData?.helpContent?.get(slotName)} roles={helpRoles} helpContentKey={`widget:${props.widgetMetaData?.name};slot:${slotName}`} />; const formattedHelpContent = <HelpContent helpContents={props.widgetMetaData?.helpContent?.get(slotName)} roles={helpRoles} helpContentKey={`widget:${props.widgetMetaData?.name};slot:${slotName}`} />;
labelElement = <Tooltip title={formattedHelpContent} arrow={true} placement="bottom-start">{labelElement}</Tooltip>; labelElement = <Tooltip title={formattedHelpContent} arrow={true} placement="bottom-start">{labelElement}</Tooltip>;

View File

@ -31,7 +31,7 @@ export default function TextBlock({widgetMetaData, data}: StandardBlockComponent
{ {
return ( return (
<BlockElementWrapper metaData={widgetMetaData} data={data} slot=""> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
<span>{data.values.text}</span> <span style={{fontSize: "1.000rem"}}>{data.values.text}</span>
</BlockElementWrapper> </BlockElementWrapper>
); );
} }

View File

@ -19,18 +19,23 @@
* 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 {Collapse, Theme, InputAdornment} from "@mui/material"; import {CalendarTodayOutlined} from "@mui/icons-material";
import {Collapse, InputAdornment, Theme} from "@mui/material";
import Autocomplete from "@mui/material/Autocomplete"; import Autocomplete from "@mui/material/Autocomplete";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import {SxProps} from "@mui/system"; import {SxProps} from "@mui/system";
import {DatePicker, DateValidationError, LocalizationProvider, PickerChangeHandlerContext, PickerValidDate} from "@mui/x-date-pickers";
import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs";
import dayjs from "dayjs";
import {Field, Form, Formik} from "formik"; import {Field, Form, Formik} from "formik";
import React, {useState} from "react"; import QContext from "QContext";
import colors from "qqq/assets/theme/base/colors"; import colors from "qqq/assets/theme/base/colors";
import MDInput from "qqq/components/legacy/MDInput"; import MDInput from "qqq/components/legacy/MDInput";
import ValueUtils from "qqq/utils/qqq/ValueUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils";
import React, {useContext, useEffect, useState} from "react";
export interface DropdownOption export interface DropdownOption
@ -45,6 +50,7 @@ export interface DropdownOption
interface Props interface Props
{ {
name: string; name: string;
type?: string;
defaultValue?: any; defaultValue?: any;
label?: string; label?: string;
startIcon?: string; startIcon?: string;
@ -96,7 +102,7 @@ function makeBackendValuesFromFrontendValues(frontendDefaultValues: StartAndEndD
return (backendTimeValues); return (backendTimeValues);
} }
function WidgetDropdownMenu({name, defaultValue, label, startIcon, width, disableClearable, allowBackAndForth, backAndForthInverted, dropdownOptions, onChangeCallback, sx}: Props): JSX.Element function WidgetDropdownMenu({name, type, defaultValue, label, startIcon, width, disableClearable, allowBackAndForth, backAndForthInverted, dropdownOptions, onChangeCallback, sx}: Props): JSX.Element
{ {
const [customTimesVisible, setCustomTimesVisible] = useState(defaultValue && defaultValue.id && defaultValue.id.startsWith("custom,")); const [customTimesVisible, setCustomTimesVisible] = useState(defaultValue && defaultValue.id && defaultValue.id.startsWith("custom,"));
const [customTimeValuesFrontend, setCustomTimeValuesFrontend] = useState(parseCustomTimeValuesFromDefaultValue(defaultValue) as StartAndEndDate); const [customTimeValuesFrontend, setCustomTimeValuesFrontend] = useState(parseCustomTimeValuesFromDefaultValue(defaultValue) as StartAndEndDate);
@ -105,16 +111,27 @@ function WidgetDropdownMenu({name, defaultValue, label, startIcon, width, disabl
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [value, setValue] = useState(defaultValue); const [value, setValue] = useState(defaultValue);
const [dateValue, setDateValue] = useState(defaultValue);
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
const [backDisabled, setBackDisabled] = useState(false); const [backDisabled, setBackDisabled] = useState(false);
const [forthDisabled, setForthDisabled] = useState(false); const [forthDisabled, setForthDisabled] = useState(false);
const {accentColor} = useContext(QContext);
const doForceOpen = (event: React.MouseEvent<HTMLDivElement>) => const doForceOpen = (event: React.MouseEvent<HTMLDivElement>) =>
{ {
setIsOpen(true); setIsOpen(true);
}; };
useEffect(() =>
{
if (type == "DATE_PICKER")
{
handleOnChange(null, defaultValue, null);
}
}, [defaultValue]);
function getSelectedIndex(value: DropdownOption) function getSelectedIndex(value: DropdownOption)
{ {
let currentIndex = null; let currentIndex = null;
@ -129,9 +146,19 @@ function WidgetDropdownMenu({name, defaultValue, label, startIcon, width, disabl
return currentIndex; return currentIndex;
} }
const navigateBackAndForth = (event: React.MouseEvent, direction: -1 | 1) =>
const navigateBackAndForth = (event: React.MouseEvent, direction: -1 | 1, type: string) =>
{ {
event.stopPropagation(); event.stopPropagation();
if (type == "DATE_PICKER")
{
let currentDate = new Date(dateValue);
currentDate.setDate(currentDate.getDate() + direction);
handleOnChange(null, currentDate, null);
return;
}
let currentIndex = getSelectedIndex(value); let currentIndex = getSelectedIndex(value);
if (currentIndex == null) if (currentIndex == null)
@ -156,9 +183,26 @@ function WidgetDropdownMenu({name, defaultValue, label, startIcon, width, disabl
}; };
const handleDatePickerOnChange = (value: PickerValidDate, context: PickerChangeHandlerContext<DateValidationError>) =>
{
if (value.isValid())
{
handleOnChange(null, value.toDate(), null);
}
};
const handleOnChange = (event: any, newValue: any, reason: string) => const handleOnChange = (event: any, newValue: any, reason: string) =>
{ {
setValue(newValue); if (type == "DATE_PICKER")
{
setDateValue(newValue);
newValue = {"id": new Date(newValue).toLocaleDateString()};
}
else
{
setValue(newValue);
}
const isTimeframeCustom = name == "timeframe" && newValue && newValue.id == "custom"; const isTimeframeCustom = name == "timeframe" && newValue && newValue.id == "custom";
setCustomTimesVisible(isTimeframeCustom); setCustomTimesVisible(isTimeframeCustom);
@ -250,86 +294,123 @@ function WidgetDropdownMenu({name, defaultValue, label, startIcon, width, disabl
const fontSize = "1rem"; const fontSize = "1rem";
let optionPaddingLeftRems = 0.75; let optionPaddingLeftRems = 0.75;
if(startIcon) if (startIcon)
{ {
optionPaddingLeftRems += allowBackAndForth ? 1.5 : 1.75 optionPaddingLeftRems += allowBackAndForth ? 1.5 : 1.75;
} }
if(allowBackAndForth) if (allowBackAndForth)
{ {
optionPaddingLeftRems += 2.5; optionPaddingLeftRems += 2.5;
} }
return ( if (type == "DATE_PICKER")
dropdownOptions ? ( {
<Box sx={{whiteSpace: "nowrap", display: "flex", return (
"& .MuiPopperUnstyled-root": { <Box sx={{
border: `1px solid ${colors.grayLines.main}`, ...sx,
borderTop: "none", background: "white",
borderRadius: "0 0 0.75rem 0.75rem", width: "250px",
padding: 0, borderRadius: "0.75rem !important",
}, "& .MuiPaper-rounded": { border: `1px solid ${colors.grayLines.main}`,
borderRadius: "0 0 0.75rem 0.75rem", "& *": {cursor: "pointer"}
} }} display="flex" alignItems="center" onClick={(event) => doForceOpen(event)}>
}} className="dashboardDropdownMenu"> {allowBackAndForth && <IconButton sx={{padding: 0, margin: "8px"}} onClick={(event) => navigateBackAndForth(event, backAndForthInverted ? 1 : -1, type)} disabled={backDisabled}><Icon>navigate_before</Icon></IconButton>}
<Autocomplete <LocalizationProvider dateAdapter={AdapterDayjs}>
id={`${label}-combo-box`} <DatePicker
sx={{paddingRight: "8px"}}
defaultValue={defaultValue} defaultValue={dayjs(defaultValue)}
value={value} name={name}
onChange={handleOnChange} value={dayjs(dateValue)}
inputValue={inputValue} onChange={handleDatePickerOnChange}
onInputChange={handleOnInputChange} slots={{
openPickerIcon: CalendarTodayOutlined
isOptionEqualToValue={(option, value) => option.id === value.id} }}
slotProps={{
open={isOpen} openPickerIcon: {sx: {fontSize: "1.25rem !important", color: "#757575"}},
onOpen={() => setIsOpen(true)} actionBar: {actions: ["today"]},
onClose={() => setIsOpen(false)} textField: {variant: "standard", InputProps: {sx: {fontSize: "16px", color: "#495057"}, disableUnderline: true}}
}}
size="small" />
disablePortal </LocalizationProvider>
disableClearable={disableClearable} {allowBackAndForth && <IconButton onClick={(event) => navigateBackAndForth(event, backAndForthInverted ? -1 : 1, type)} disabled={forthDisabled}><Icon>navigate_next</Icon></IconButton>}
options={dropdownOptions}
sx={{
...sx,
cursor: "pointer",
display: "inline-block",
"& .MuiOutlinedInput-notchedOutline": {
border: "none"
},
}}
renderInput={(params: any) =>
<>
<Box sx={{width: `${width}px`, background: "white", borderRadius: isOpen ? "0.75rem 0.75rem 0 0" : "0.75rem", border: `1px solid ${colors.grayLines.main}`, "& *": {cursor: "pointer"}}} display="flex" alignItems="center" onClick={(event) => doForceOpen(event)}>
{allowBackAndForth && <IconButton onClick={(event) => navigateBackAndForth(event, backAndForthInverted ? 1 : -1)} disabled={backDisabled}><Icon>navigate_before</Icon></IconButton>}
<TextField {...params} placeholder={label} sx={{
"& .MuiInputBase-input": {
fontSize: fontSize
}
}} InputProps={{...params.InputProps, startAdornment: startAdornment/*, endAdornment: endAdornment*/}}
/>
{allowBackAndForth && <IconButton onClick={(event) => navigateBackAndForth(event, backAndForthInverted ? -1 : 1)} disabled={forthDisabled}><Icon>navigate_next</Icon></IconButton>}
</Box>
</>
}
renderOption={(props, option: DropdownOption) => (
<li {...props} style={{whiteSpace: "normal", fontSize: fontSize, paddingLeft: `${optionPaddingLeftRems}rem`}}>{option.label}</li>
)}
noOptionsText={<Box fontSize={fontSize}>No options found</Box>}
slotProps={{
popper: {
sx: {
width: `${width}px!important`
}
}
}}
/>
{customTimes}
</Box> </Box>
) : null );
); }
else
{
return (
dropdownOptions ? (
<Box sx={{
whiteSpace: "nowrap", display: "flex",
"& .MuiPopperUnstyled-root": {
border: `1px solid ${colors.grayLines.main}`,
borderTop: "none",
borderRadius: "0 0 0.75rem 0.75rem",
padding: 0,
}, "& .MuiPaper-rounded": {
borderRadius: "0 0 0.75rem 0.75rem",
}
}} className="dashboardDropdownMenu">
<Autocomplete
id={`${label}-combo-box`}
defaultValue={defaultValue}
value={value}
onChange={handleOnChange}
inputValue={inputValue}
onInputChange={handleOnInputChange}
isOptionEqualToValue={(option, value) => option.id === value.id}
open={isOpen}
onOpen={() => setIsOpen(true)}
onClose={() => setIsOpen(false)}
size="small"
disablePortal
disableClearable={disableClearable}
options={dropdownOptions}
sx={{
...sx,
cursor: "pointer",
display: "inline-block",
"& .MuiOutlinedInput-notchedOutline": {
border: "none"
},
}}
renderInput={(params: any) =>
<>
<Box sx={{width: `${width}px`, background: "white", borderRadius: isOpen ? "0.75rem 0.75rem 0 0" : "0.75rem", border: `1px solid ${colors.grayLines.main}`, "& *": {cursor: "pointer"}}} display="flex" alignItems="center" onClick={(event) => doForceOpen(event)}>
{allowBackAndForth && <IconButton onClick={(event) => navigateBackAndForth(event, backAndForthInverted ? 1 : -1, type)} disabled={backDisabled}><Icon>navigate_before</Icon></IconButton>}
<TextField {...params} placeholder={label} sx={{
"& .MuiInputBase-input": {
fontSize: fontSize
}
}} InputProps={{...params.InputProps, startAdornment: startAdornment/*, endAdornment: endAdornment*/}}
/>
{allowBackAndForth && <IconButton onClick={(event) => navigateBackAndForth(event, backAndForthInverted ? -1 : 1, type)} disabled={forthDisabled}><Icon>navigate_next</Icon></IconButton>}
</Box>
</>
}
renderOption={(props, option: DropdownOption) => (
<li {...props} style={{whiteSpace: "normal", fontSize: fontSize, paddingLeft: `${optionPaddingLeftRems}rem`}}>{option.label}</li>
)}
noOptionsText={<Box fontSize={fontSize}>No options found</Box>}
slotProps={{
popper: {
sx: {
width: `${width}px!important`
}
}
}}
/>
{customTimes}
</Box>
) : null
);
}
} }
export default WidgetDropdownMenu; export default WidgetDropdownMenu;

View File

@ -119,7 +119,7 @@ export default function DataBagViewer({dataBagId}: Props): JSX.Element
{ {
if (e instanceof QException) if (e instanceof QException)
{ {
if ((e as QException).status === "404") if ((e as QException).status === 404)
{ {
setNotFoundMessage("Data bag data could not be found."); setNotFoundMessage("Data bag data could not be found.");
return; return;

View File

@ -40,7 +40,7 @@ import {Link, useNavigate} from "react-router-dom";
export interface ChildRecordListData extends WidgetData export interface ChildRecordListData extends WidgetData
{ {
title: string; title: string;
queryOutput: {records: QRecord[]} queryOutput: {records: {values: any}[]}
childTableMetaData: QTableMetaData; childTableMetaData: QTableMetaData;
tablePath: string; tablePath: string;
viewAllLink: string; viewAllLink: string;

View File

@ -169,7 +169,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
{ {
if (e instanceof QException) if (e instanceof QException)
{ {
if ((e as QException).status === "404") if ((e as QException).status === 404)
{ {
setNotFoundMessage("Script code could not be found."); setNotFoundMessage("Script code could not be found.");
return; return;

View File

@ -21,8 +21,8 @@
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import {Theme} from "@mui/material/styles"; import {Theme} from "@mui/material/styles";
import {ReactNode} from "react";
import colors from "qqq/assets/theme/base/colors"; import colors from "qqq/assets/theme/base/colors";
import {ReactNode} from "react";
// Declaring prop types for DataTableBodyCell // Declaring prop types for DataTableBodyCell
interface Props interface Props
@ -49,7 +49,7 @@ function DataTableBodyCell({noBorder, align, children}: Props): JSX.Element
"@media (max-width: 1440px)": { "@media (max-width: 1440px)": {
fontSize: "0.875rem" fontSize: "0.875rem"
}, },
"&:nth-child(1)": { "&:nth-of-type(1)": {
paddingLeft: "1rem" paddingLeft: "1rem"
}, },
"&:last-child": { "&:last-child": {

View File

@ -23,9 +23,9 @@ import Box from "@mui/material/Box";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import {Theme} from "@mui/material/styles"; import {Theme} from "@mui/material/styles";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import {ReactNode} from "react";
import colors from "qqq/assets/theme/base/colors"; import colors from "qqq/assets/theme/base/colors";
import {useMaterialUIController} from "qqq/context"; import {useMaterialUIController} from "qqq/context";
import {ReactNode} from "react";
// Declaring props types for DataTableHeadCell // Declaring props types for DataTableHeadCell
interface Props interface Props
@ -50,7 +50,7 @@ function DataTableHeadCell({width, children, sorted, align, tooltip, ...rest}: P
px={1.5} px={1.5}
sx={({palette: {light}, borders: {borderWidth}}: Theme) => ({ sx={({palette: {light}, borders: {borderWidth}}: Theme) => ({
borderBottom: `${borderWidth[1]} solid ${colors.grayLines.main}`, borderBottom: `${borderWidth[1]} solid ${colors.grayLines.main}`,
"&:nth-child(1)": { "&:nth-of-type(1)": {
paddingLeft: "1rem" paddingLeft: "1rem"
}, },
"&:last-child": { "&:last-child": {

View File

@ -63,6 +63,8 @@ export default class RecordQueryView
view.queryFilter = json.queryFilter as QQueryFilter; view.queryFilter = json.queryFilter as QQueryFilter;
FilterUtils.stripAwayIncompleteCriteria(view.queryFilter)
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// it's important that some criteria values exist as expression objects - so - do that. // // it's important that some criteria values exist as expression objects - so - do that. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////

View File

@ -47,9 +47,6 @@ import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro";
import FormData from "form-data"; import FormData from "form-data";
import {Form, Formik} from "formik"; import {Form, Formik} from "formik";
import parse from "html-react-parser"; import parse from "html-react-parser";
import React, {useContext, useEffect, useState} from "react";
import {useLocation, useNavigate, useParams} from "react-router-dom";
import * as Yup from "yup";
import QContext from "QContext"; import QContext from "QContext";
import {QCancelButton, QSubmitButton} from "qqq/components/buttons/DefaultButtons"; import {QCancelButton, QSubmitButton} from "qqq/components/buttons/DefaultButtons";
import QDynamicForm from "qqq/components/forms/DynamicForm"; import QDynamicForm from "qqq/components/forms/DynamicForm";
@ -66,6 +63,9 @@ import {TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT} from "qqq/pages/records/query/Reco
import Client from "qqq/utils/qqq/Client"; import Client from "qqq/utils/qqq/Client";
import TableUtils from "qqq/utils/qqq/TableUtils"; import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils";
import React, {useContext, useEffect, useState} from "react";
import {useLocation, useNavigate, useParams} from "react-router-dom";
import * as Yup from "yup";
interface Props interface Props
@ -91,7 +91,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const processNameParam = useParams().processName; const processNameParam = useParams().processName;
const processName = process === null ? processNameParam : process.name; const processName = process === null ? processNameParam : process.name;
let tableVariantLocalStorageKey: string | null = null; let tableVariantLocalStorageKey: string | null = null;
if(table) if (table)
{ {
tableVariantLocalStorageKey = `${TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT}.${table.name}`; tableVariantLocalStorageKey = `${TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT}.${table.name}`;
} }
@ -416,10 +416,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
////////////////////////////////////////////////// //////////////////////////////////////////////////
step.components && (step.components.map((component: QFrontendComponent, index: number) => step.components && (step.components.map((component: QFrontendComponent, index: number) =>
{ {
let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"] let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"];
if(component.type == QComponentType.BULK_EDIT_FORM) if (component.type == QComponentType.BULK_EDIT_FORM)
{ {
helpRoles = ["EDIT_SCREEN", "WRITE_SCREENS", "ALL_SCREENS"] helpRoles = ["EDIT_SCREEN", "WRITE_SCREENS", "ALL_SCREENS"];
} }
return ( return (
@ -1068,7 +1068,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const handlePermissionDenied = (e: any): boolean => const handlePermissionDenied = (e: any): boolean =>
{ {
if ((e as QException).status === "403") if ((e as QException).status === 403)
{ {
setProcessError(`You do not have permission to run this ${isReport ? "report" : "process"}.`, true); setProcessError(`You do not have permission to run this ${isReport ? "report" : "process"}.`, true);
return (true); return (true);

View File

@ -447,9 +447,19 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
} }
} }
} }
/////////////////////////////////////////
// recursively prep subfilters as well //
/////////////////////////////////////////
let subFilters = [] as QQueryFilter[];
for (let j = 0; j < sourceFilter?.subFilters?.length; j++)
{
subFilters.push(prepQueryFilterForBackend(sourceFilter.subFilters[j]));
}
filterForBackend.subFilters = subFilters;
filterForBackend.skip = pageNumber * rowsPerPage; filterForBackend.skip = pageNumber * rowsPerPage;
filterForBackend.limit = rowsPerPage; filterForBackend.limit = rowsPerPage;
return filterForBackend; return filterForBackend;
}; };
@ -701,8 +711,27 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
const doSetView = (view: RecordQueryView): void => const doSetView = (view: RecordQueryView): void =>
{ {
setView(view); setView(view);
setViewAsJson(JSON.stringify(view)); const viewAsJSON = JSON.stringify(view);
localStorage.setItem(viewLocalStorageKey, JSON.stringify(view)); setViewAsJson(viewAsJSON);
try
{
////////////////////////////////////////////////////////////////////////////////////
// in case there's an incomplete criteria in the view (e.g., w/o a fieldName), //
// don't store that in local storage - we don't want that, it's messy, and it //
// has caused fails in the past. So, clone the view, and strip away such things. //
////////////////////////////////////////////////////////////////////////////////////
const viewForLocalStorage: RecordQueryView = JSON.parse(viewAsJSON);
if (viewForLocalStorage?.queryFilter?.criteria?.length > 0)
{
FilterUtils.stripAwayIncompleteCriteria(viewForLocalStorage.queryFilter)
}
localStorage.setItem(viewLocalStorageKey, JSON.stringify(viewForLocalStorage));
}
catch(e)
{
console.log("Error storing view in local storage: " + e)
}
}; };

View File

@ -28,9 +28,6 @@ import Button from "@mui/material/Button";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Snackbar from "@mui/material/Snackbar"; import Snackbar from "@mui/material/Snackbar";
import React, {useContext, useReducer, useState} from "react";
import AceEditor from "react-ace";
import {useParams} from "react-router-dom";
import QContext from "QContext"; import QContext from "QContext";
import ScriptViewer from "qqq/components/widgets/misc/ScriptViewer"; import ScriptViewer from "qqq/components/widgets/misc/ScriptViewer";
import BaseLayout from "qqq/layouts/BaseLayout"; import BaseLayout from "qqq/layouts/BaseLayout";
@ -41,6 +38,9 @@ import "ace-builds/src-noconflict/mode-java";
import "ace-builds/src-noconflict/mode-javascript"; import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/mode-json"; import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-github"; import "ace-builds/src-noconflict/theme-github";
import React, {useContext, useReducer, useState} from "react";
import AceEditor from "react-ace";
import {useParams} from "react-router-dom";
import "ace-builds/src-noconflict/ext-language_tools"; import "ace-builds/src-noconflict/ext-language_tools";
const qController = Client.getInstance(); const qController = Client.getInstance();
@ -121,7 +121,7 @@ function RecordDeveloperView({table}: Props): JSX.Element
{ {
if (e instanceof QException) if (e instanceof QException)
{ {
if ((e as QException).status === "404") if ((e as QException).status === 404)
{ {
setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`); setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`);
return; return;

View File

@ -46,8 +46,6 @@ import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import Modal from "@mui/material/Modal"; import Modal from "@mui/material/Modal";
import Tooltip from "@mui/material/Tooltip/Tooltip"; import Tooltip from "@mui/material/Tooltip/Tooltip";
import React, {useContext, useEffect, useState} from "react";
import {useLocation, useNavigate, useParams} from "react-router-dom";
import QContext from "QContext"; import QContext from "QContext";
import colors from "qqq/assets/theme/base/colors"; import colors from "qqq/assets/theme/base/colors";
import AuditBody from "qqq/components/audits/AuditBody"; import AuditBody from "qqq/components/audits/AuditBody";
@ -65,6 +63,8 @@ import Client from "qqq/utils/qqq/Client";
import ProcessUtils from "qqq/utils/qqq/ProcessUtils"; import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
import TableUtils from "qqq/utils/qqq/TableUtils"; import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils";
import React, {useContext, useEffect, useState} from "react";
import {useLocation, useNavigate, useParams} from "react-router-dom";
const qController = Client.getInstance(); const qController = Client.getInstance();
@ -148,9 +148,9 @@ function RecordView({table, launchProcess}: Props): JSX.Element
/////////////////////// ///////////////////////
useEffect(() => useEffect(() =>
{ {
if(tableMetaData == null) if (tableMetaData == null)
{ {
(async() => (async () =>
{ {
const tableMetaData = await qController.loadTableMetaData(tableName); const tableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData); setTableMetaData(tableMetaData);
@ -162,54 +162,54 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const type = (e.target as any).type; const type = (e.target as any).type;
const validType = (type !== "text" && type !== "textarea" && type !== "input" && type !== "search"); const validType = (type !== "text" && type !== "textarea" && type !== "input" && type !== "search");
if(validType && !dotMenuOpen && !keyboardHelpOpen && !showAudit && !showEditChildForm) if (validType && !dotMenuOpen && !keyboardHelpOpen && !showAudit && !showEditChildForm)
{ {
if (! e.metaKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission) if (!e.metaKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
{ {
e.preventDefault() e.preventDefault();
gotoCreate(); gotoCreate();
} }
else if (! e.metaKey && e.key === "e" && table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) else if (!e.metaKey && e.key === "e" && table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission)
{ {
e.preventDefault() e.preventDefault();
navigate("edit"); navigate("edit");
} }
else if (! e.metaKey && e.key === "c" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission) else if (!e.metaKey && e.key === "c" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
{ {
e.preventDefault() e.preventDefault();
navigate("copy"); navigate("copy");
} }
else if (! e.metaKey && e.key === "d" && table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission) else if (!e.metaKey && e.key === "d" && table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)
{ {
e.preventDefault() e.preventDefault();
handleClickDeleteButton(); handleClickDeleteButton();
} }
else if (! e.metaKey && e.key === "a" && metaData && metaData.tables.has("audit")) else if (!e.metaKey && e.key === "a" && metaData && metaData.tables.has("audit"))
{ {
e.preventDefault() e.preventDefault();
navigate("#audit"); navigate("#audit");
} }
} }
} };
document.addEventListener("keydown", down) document.addEventListener("keydown", down);
return () => return () =>
{ {
document.removeEventListener("keydown", down) document.removeEventListener("keydown", down);
} };
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData, location]) }, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData, location]);
const gotoCreate = () => const gotoCreate = () =>
{ {
const path = `${pathParts.slice(0, -1).join("/")}/create`; const path = `${pathParts.slice(0, -1).join("/")}/create`;
navigate(path); navigate(path);
} };
const gotoEdit = () => const gotoEdit = () =>
{ {
const path = `${pathParts.slice(0, -1).join("/")}/${record.values.get(table.primaryKeyField)}/edit`; const path = `${pathParts.slice(0, -1).join("/")}/${record.values.get(table.primaryKeyField)}/edit`;
navigate(path); navigate(path);
} };
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
// monitor location changes - if we've clicked a link from viewing one record to viewing another, // // monitor location changes - if we've clicked a link from viewing one record to viewing another, //
@ -267,7 +267,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
// if our table is in the -4 index, and there's `createChild` in the -2 index, try to open a createChild form // // if our table is in the -4 index, and there's `createChild` in the -2 index, try to open a createChild form //
// e.g., person/42/createChild/address (to create an address under person 42) // // e.g., person/42/createChild/address (to create an address under person 42) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(pathParts[pathParts.length - 4] === tableName && pathParts[pathParts.length - 2] == "createChild") if (pathParts[pathParts.length - 4] === tableName && pathParts[pathParts.length - 2] == "createChild")
{ {
(async () => (async () =>
{ {
@ -298,7 +298,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
} }
} }
if(hashParts[0] == "#audit") if (hashParts[0] == "#audit")
{ {
setShowAudit(true); setShowAudit(true);
return; return;
@ -307,11 +307,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
/////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////
// look for anchor links - e.g., table section names. return w/ no-op if found. // // look for anchor links - e.g., table section names. return w/ no-op if found. //
/////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////
if(tableSections) if (tableSections)
{ {
for (let i = 0; i < tableSections.length; i++) for (let i = 0; i < tableSections.length; i++)
{ {
if("#" + tableSections[i].name === location.hash) if ("#" + tableSections[i].name === location.hash)
{ {
return; return;
} }
@ -345,11 +345,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
section.fieldNames.forEach((fieldName) => section.fieldNames.forEach((fieldName) =>
{ {
const [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName); const [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
if(tableForField && tableForField.name != tableMetaData.name) if (tableForField && tableForField.name != tableMetaData.name)
{ {
visibleJoinTables.add(tableForField.name); visibleJoinTables.add(tableForField.name);
} }
}) });
} }
return (visibleJoinTables); return (visibleJoinTables);
@ -361,15 +361,15 @@ function RecordView({table, launchProcess}: Props): JSX.Element
*******************************************************************************/ *******************************************************************************/
const getSectionHelp = (section: QTableSection) => const getSectionHelp = (section: QTableSection) =>
{ {
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"] const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"];
const formattedHelpContent = <HelpContent helpContents={section.helpContents} roles={helpRoles} helpContentKey={`table:${tableName};section:${section.name}`} />; const formattedHelpContent = <HelpContent helpContents={section.helpContents} roles={helpRoles} helpContentKey={`table:${tableName};section:${section.name}`} />;
return formattedHelpContent && ( return formattedHelpContent && (
<Box px={"1.5rem"} fontSize={"0.875rem"} color={colors.blueGray.main}> <Box px={"1.5rem"} fontSize={"0.875rem"} color={colors.blueGray.main}>
{formattedHelpContent} {formattedHelpContent}
</Box> </Box>
) );
} };
if (!asyncLoadInited) if (!asyncLoadInited)
@ -401,11 +401,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// load processes that the routing needs to respect // // load processes that the routing needs to respect //
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
const allTableProcesses = ProcessUtils.getProcessesForTable(metaData, tableName, true) // these include hidden ones (e.g., to find the bulks) const allTableProcesses = ProcessUtils.getProcessesForTable(metaData, tableName, true); // these include hidden ones (e.g., to find the bulks)
const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); const runRecordScriptProcess = metaData?.processes.get("runRecordScript");
if (runRecordScriptProcess) if (runRecordScriptProcess)
{ {
allTableProcesses.unshift(runRecordScriptProcess) allTableProcesses.unshift(runRecordScriptProcess);
} }
setAllTableProcesses(allTableProcesses); setAllTableProcesses(allTableProcesses);
@ -417,7 +417,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
let queryJoins: QueryJoin[] = null; let queryJoins: QueryJoin[] = null;
const visibleJoinTables = getVisibleJoinTables(tableMetaData); const visibleJoinTables = getVisibleJoinTables(tableMetaData);
if(visibleJoinTables.size > 0) if (visibleJoinTables.size > 0)
{ {
queryJoins = TableUtils.getQueryJoins(tableMetaData, visibleJoinTables); queryJoins = TableUtils.getQueryJoins(tableMetaData, visibleJoinTables);
} }
@ -439,7 +439,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
{ {
HistoryUtils.ensurePathNotInHistory(location.pathname); HistoryUtils.ensurePathNotInHistory(location.pathname);
} }
catch(e) catch (e)
{ {
console.error("Error pushing history: " + e); console.error("Error pushing history: " + e);
} }
@ -447,13 +447,13 @@ function RecordView({table, launchProcess}: Props): JSX.Element
if (e instanceof QException) if (e instanceof QException)
{ {
if ((e as QException).status === "404") if ((e as QException).status === 404)
{ {
setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`); setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`);
historyPurge(location.pathname); historyPurge(location.pathname);
return; return;
} }
else if ((e as QException).status === "403") else if ((e as QException).status === 403)
{ {
setNotFoundMessage(`You do not have permission to view ${tableMetaData.label} records`); setNotFoundMessage(`You do not have permission to view ${tableMetaData.label} records`);
historyPurge(location.pathname); historyPurge(location.pathname);
@ -464,13 +464,13 @@ function RecordView({table, launchProcess}: Props): JSX.Element
setPageHeader(record.recordLabel); setPageHeader(record.recordLabel);
if(!launchingProcess) if (!launchingProcess)
{ {
try try
{ {
HistoryUtils.push({label: `${tableMetaData?.label}: ${record.recordLabel}`, path: location.pathname, iconName: table.iconName}); HistoryUtils.push({label: `${tableMetaData?.label}: ${record.recordLabel}`, path: location.pathname, iconName: table.iconName});
} }
catch(e) catch (e)
{ {
console.error("Error pushing history: " + e); console.error("Error pushing history: " + e);
} }
@ -522,27 +522,30 @@ function RecordView({table, launchProcess}: Props): JSX.Element
section.fieldNames.map((fieldName: string) => section.fieldNames.map((fieldName: string) =>
{ {
let [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName); let [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
let label = field.label; if (field != null)
{
let label = field.label;
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"] const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"];
const showHelp = helpHelpActive || hasHelpContent(field.helpContents, helpRoles); const showHelp = helpHelpActive || hasHelpContent(field.helpContents, helpRoles);
const formattedHelpContent = <HelpContent helpContents={field.helpContents} roles={helpRoles} heading={label} helpContentKey={`table:${tableName};field:${fieldName}`} />; const formattedHelpContent = <HelpContent helpContents={field.helpContents} roles={helpRoles} heading={label} helpContentKey={`table:${tableName};field:${fieldName}`} />;
const labelElement = <Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)" sx={{cursor: "default"}}>{label}:</Typography> const labelElement = <Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)" sx={{cursor: "default"}}>{label}:</Typography>;
return ( return (
<Box key={fieldName} flexDirection="row" pr={2}> <Box key={fieldName} flexDirection="row" pr={2}>
<> <>
{ {
showHelp && formattedHelpContent ? <Tooltip title={formattedHelpContent}>{labelElement}</Tooltip> : labelElement showHelp && formattedHelpContent ? <Tooltip title={formattedHelpContent}>{labelElement}</Tooltip> : labelElement
} }
<div style={{display: "inline-block", width: 0}}>&nbsp;</div> <div style={{display: "inline-block", width: 0}}>&nbsp;</div>
<Typography variant="button" textTransform="none" fontWeight="regular" color="rgb(123, 128, 154)"> <Typography variant="button" textTransform="none" fontWeight="regular" color="rgb(123, 128, 154)">
{ValueUtils.getDisplayValue(field, record, "view", fieldName)} {ValueUtils.getDisplayValue(field, record, "view", fieldName)}
</Typography> </Typography>
</> </>
</Box> </Box>
) );
}
}) })
} }
</Box> </Box>
@ -590,7 +593,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
setSectionFieldElements(sectionFieldElements); setSectionFieldElements(sectionFieldElements);
setNonT1TableSections(nonT1TableSections); setNonT1TableSections(nonT1TableSections);
if(location.state) if (location.state)
{ {
let state: any = location.state; let state: any = location.state;
if (state["createSuccess"] || state["updateSuccess"]) if (state["createSuccess"] || state["updateSuccess"])
@ -603,9 +606,9 @@ function RecordView({table, launchProcess}: Props): JSX.Element
setWarningMessage(state["warning"]); setWarningMessage(state["warning"]);
} }
delete state["createSuccess"] delete state["createSuccess"];
delete state["updateSuccess"] delete state["updateSuccess"];
delete state["warning"] delete state["warning"];
window.history.replaceState(state, ""); window.history.replaceState(state, "");
} }
@ -640,7 +643,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
console.log("Caught:"); console.log("Caught:");
console.log(error); console.log(error);
if(error.message.toLowerCase().startsWith("warning")) if (error.message.toLowerCase().startsWith("warning"))
{ {
const path = pathParts.slice(0, -1).join("/"); const path = pathParts.slice(0, -1).join("/");
navigate(path, {state: {deleteSuccess: true, warning: error.message}}); navigate(path, {state: {deleteSuccess: true, warning: error.message}});
@ -767,7 +770,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// when closing a modal process, navigate up to the record being viewed // // when closing a modal process, navigate up to the record being viewed //
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
if(location.hash) if (location.hash)
{ {
navigate(location.pathname); navigate(location.pathname);
} }
@ -801,7 +804,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
///////////////////////////////////////////////// /////////////////////////////////////////////////
// navigate back up to the record being viewed // // navigate back up to the record being viewed //
///////////////////////////////////////////////// /////////////////////////////////////////////////
if(location.hash) if (location.hash)
{ {
navigate(location.pathname); navigate(location.pathname);
} }
@ -828,7 +831,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
///////////////////////////////////////////////// /////////////////////////////////////////////////
// navigate back up to the record being viewed // // navigate back up to the record being viewed //
///////////////////////////////////////////////// /////////////////////////////////////////////////
if(location.hash) if (location.hash)
{ {
navigate(location.pathname); navigate(location.pathname);
} }
@ -864,7 +867,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
} }
{ {
warningMessage ? warningMessage ?
<Alert color="warning" sx={{mb: 3}} onClose={() => <Alert color="warning" sx={{mb: 3}} icon={<Icon>warning</Icon>} onClose={() =>
{ {
setWarningMessage(null); setWarningMessage(null);
}}> }}>

View File

@ -158,6 +158,7 @@ but we've turned off the click-to-sort function, so remove hand cursor */
white-space: normal; white-space: normal;
height: auto; height: auto;
} }
.MuiDataGrid-filterForm .MuiDataGrid-filterForm
{ {
align-items: flex-end; align-items: flex-end;
@ -173,10 +174,12 @@ but we've turned off the click-to-sort function, so remove hand cursor */
{ {
width: 200px; width: 200px;
} }
.MuiDataGrid-filterForm .MuiDataGrid-filterFormValueInput .MuiDataGrid-filterForm .MuiDataGrid-filterFormValueInput
{ {
width: 300px; width: 300px;
} }
.MuiDataGrid-filterForm .MuiDataGrid-filterFormOperatorInput .MuiDataGrid-filterForm .MuiDataGrid-filterFormOperatorInput
{ {
width: 150px; width: 150px;
@ -187,13 +190,14 @@ but we've turned off the click-to-sort function, so remove hand cursor */
{ {
padding-top: 4px; padding-top: 4px;
} }
.MuiDataGrid-filterForm .MuiDataGrid-filterFormValueInput .MuiAutocomplete-root .MuiAutocomplete-endAdornment svg .MuiDataGrid-filterForm .MuiDataGrid-filterFormValueInput .MuiAutocomplete-root .MuiAutocomplete-endAdornment svg
{ {
height: 0.625em; height: 0.625em;
} }
/* fix strange size bug on filter autocompletes */ /* fix strange size bug on filter autocompletes */
.MuiDataGrid-filterForm .MuiDataGrid-filterFormValueInput>.MuiBox-root>.MuiBox-root:has(>.MuiAutocomplete-root) .MuiDataGrid-filterForm .MuiDataGrid-filterFormValueInput > .MuiBox-root > .MuiBox-root:has(>.MuiAutocomplete-root)
{ {
margin-bottom: 0; margin-bottom: 0;
width: 100%; width: 100%;
@ -208,16 +212,31 @@ but we've turned off the click-to-sort function, so remove hand cursor */
} }
/* clears the X from Internet Explorer */ /* clears the X from Internet Explorer */
input[type=search]::-ms-clear { display: none; width : 0; height: 0; } input[type=search]::-ms-clear
input[type=search]::-ms-reveal { display: none; width : 0; height: 0; } {
display: none;
width: 0;
height: 0;
}
input[type=search]::-ms-reveal
{
display: none;
width: 0;
height: 0;
}
/* clears the X from Chrome */ /* clears the X from Chrome */
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-results-button, input[type="search"]::-webkit-search-results-button,
input[type="search"]::-webkit-search-results-decoration { display: none; } input[type="search"]::-webkit-search-results-decoration
{
display: none;
}
/* Shrink the big margin-bottom on modal processes */ /* Shrink the big margin-bottom on modal processes */
.modalProcess>.MuiBox-root>.MuiBox-root .modalProcess > .MuiBox-root > .MuiBox-root
{ {
margin-bottom: 24px; margin-bottom: 24px;
} }
@ -270,6 +289,7 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
color: initial !important; color: initial !important;
border: 1px solid rgb(206, 212, 218); border: 1px solid rgb(206, 212, 218);
} }
.MuiDataGrid-filterForm .MuiAutocomplete-tag .MuiSvgIcon-root .MuiDataGrid-filterForm .MuiAutocomplete-tag .MuiSvgIcon-root
{ {
color: initial !important; color: initial !important;
@ -287,7 +307,7 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
right: 0.125rem; right: 0.125rem;
} }
.devDocumentation ul>li .devDocumentation ul > li
{ {
margin-left: 30px; margin-left: 30px;
} }
@ -640,6 +660,7 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
border: 1px solid #BDBDBD; border: 1px solid #BDBDBD;
border-radius: 0.5rem !important; border-radius: 0.5rem !important;
} }
.MuiToggleButtonGroup-root .MuiButtonBase-root .MuiToggleButtonGroup-root .MuiButtonBase-root
{ {
text-transform: none; text-transform: none;
@ -650,11 +671,19 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
border: none; border: none;
flex: 1 1 0px; flex: 1 1 0px;
} }
.MuiToggleButtonGroup-root .MuiButtonBase-root.Mui-selected .MuiToggleButtonGroup-root .MuiButtonBase-root.Mui-selected
{ {
background: rgba(117, 117, 117, 0.20); background: rgba(117, 117, 117, 0.20);
} }
.MuiToggleButtonGroup-root .MuiButtonBase-root.Mui-disabled .MuiToggleButtonGroup-root .MuiButtonBase-root.Mui-disabled
{ {
border: none; border: none;
} }
.MuiPickersDay-root.Mui-selected, .MuiPickersDay-root.MuiPickersDay-dayWithMargin:hover
{
color: white;
background-color: #0062FF !important;
}

View File

@ -35,7 +35,7 @@ class Client
{ {
console.log(`Caught Exception: ${JSON.stringify(exception)}`); console.log(`Caught Exception: ${JSON.stringify(exception)}`);
if(exception && exception.status == "401" && Client.unauthorizedCallback) if (exception && exception.status == 401 && Client.unauthorizedCallback)
{ {
console.log("This is a 401 - calling the unauthorized callback."); console.log("This is a 401 - calling the unauthorized callback.");
Client.unauthorizedCallback(); Client.unauthorizedCallback();

View File

@ -159,6 +159,14 @@ class FilterUtils
criteria.values = values; criteria.values = values;
} }
////////////////////////////////////////////////
// recursively clean values in any subfilters //
////////////////////////////////////////////////
for (let j = 0; j < queryFilter?.subFilters?.length; j++)
{
await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, queryFilter.subFilters[j]);
}
} }
@ -581,6 +589,29 @@ class FilterUtils
} }
} }
/*******************************************************************************
** after go-live of redesigin in march 2024, we had bugs where we could get a
** filter with a criteria w/ a null field name (e.g., by having an incomplete
** criteria in the Advanced filter builder - and that would sometimes break
** the screen! So, strip those away when storing or loading filters, via
** this function.
*******************************************************************************/
public static stripAwayIncompleteCriteria(filter: QQueryFilter)
{
if (filter?.criteria?.length > 0)
{
for (let i = 0; i < filter.criteria.length; i++)
{
if (!filter.criteria[i].fieldName)
{
filter.criteria.splice(i, 1);
i--;
}
}
}
}
} }
export default FilterUtils; export default FilterUtils;

View File

@ -150,7 +150,7 @@ class TableUtils
return ([tableMetaData.fields.get(fieldName), tableMetaData]); return ([tableMetaData.fields.get(fieldName), tableMetaData]);
} }
return (null); return [null, null];
} }
@ -173,7 +173,7 @@ class TableUtils
catch (e) catch (e)
{ {
console.log(`Error getting full field label for ${fieldName} in table ${tableMetaData?.name}: ${e}`); console.log(`Error getting full field label for ${fieldName} in table ${tableMetaData?.name}: ${e}`);
return fieldName return fieldName;
} }
} }

View File

@ -219,6 +219,16 @@ class ValueUtils
if (field.type === QFieldType.DATE_TIME) if (field.type === QFieldType.DATE_TIME)
{ {
if(displayValue && displayValue != rawValue)
{
//////////////////////////////////////////////////////////////////////////////
// if the date-time actually has a displayValue set, and it isn't just the //
// raw-value being copied into the display value by whoever called us, then //
// return the display value. //
//////////////////////////////////////////////////////////////////////////////
return displayValue;
}
if (!rawValue) if (!rawValue)
{ {
return (""); return ("");
@ -270,6 +280,7 @@ class ValueUtils
{ {
date = new Date(date); date = new Date(date);
} }
// @ts-ignore // @ts-ignore
return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`); return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`);
} }