mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-21 22:58:43 +00:00
Compare commits
12 Commits
snapshot-i
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
bcade32ed1 | |||
8071c54ccd | |||
334871988b | |||
ddb055bc81 | |||
66ddf4cb57 | |||
7e15f4601d | |||
cdb61695d1 | |||
5e3991d9ae | |||
f1826c81a9 | |||
230aaeef8c | |||
c08696b3a1 | |||
edab918763 |
2200
package-lock.json
generated
2200
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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.90",
|
"@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",
|
||||||
|
30
src/App.tsx
30
src/App.tsx
@ -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);
|
||||||
@ -707,4 +707,4 @@ export default function App()
|
|||||||
</QContext.Provider>
|
</QContext.Provider>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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 ? (
|
||||||
|
@ -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>;
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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": {
|
||||||
|
@ -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": {
|
||||||
|
@ -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. //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -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);
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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}}> </div>
|
<div style={{display: "inline-block", width: 0}}> </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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()}`);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user