mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 12:50:43 +00:00
Merged feature/CE-882-add-functionality-of-sharing into integration/sprint-41
This commit is contained in:
18466
package-lock.json
generated
18466
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.96",
|
"@kingsrook/qqq-frontend-core": "1.0.97",
|
||||||
"@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",
|
||||||
|
@ -664,10 +664,16 @@ export default function App()
|
|||||||
const [dotMenuOpen, setDotMenuOpen] = useState(false);
|
const [dotMenuOpen, setDotMenuOpen] = useState(false);
|
||||||
const [keyboardHelpOpen, setKeyboardHelpOpen] = useState(false);
|
const [keyboardHelpOpen, setKeyboardHelpOpen] = useState(false);
|
||||||
const [helpHelpActive] = useState(queryParams.has("helpHelp"));
|
const [helpHelpActive] = useState(queryParams.has("helpHelp"));
|
||||||
|
const [userId, setUserId] = useState(user?.email);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
setUserId(user?.email)
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
|
||||||
const [googleAnalyticsUtils] = useState(new GoogleAnalyticsUtils());
|
const [googleAnalyticsUtils] = useState(new GoogleAnalyticsUtils());
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -689,6 +695,7 @@ export default function App()
|
|||||||
dotMenuOpen: dotMenuOpen,
|
dotMenuOpen: dotMenuOpen,
|
||||||
keyboardHelpOpen: keyboardHelpOpen,
|
keyboardHelpOpen: keyboardHelpOpen,
|
||||||
helpHelpActive: helpHelpActive,
|
helpHelpActive: helpHelpActive,
|
||||||
|
userId: userId,
|
||||||
setPageHeader: (header: string | JSX.Element) => setPageHeader(header),
|
setPageHeader: (header: string | JSX.Element) => setPageHeader(header),
|
||||||
setAccentColor: (accentColor: string) => setAccentColor(accentColor),
|
setAccentColor: (accentColor: string) => setAccentColor(accentColor),
|
||||||
setAccentColorLight: (accentColorLight: string) => setAccentColorLight(accentColorLight),
|
setAccentColorLight: (accentColorLight: string) => setAccentColorLight(accentColorLight),
|
||||||
|
@ -59,6 +59,7 @@ interface QContext
|
|||||||
pathToLabelMap?: {[path: string]: string};
|
pathToLabelMap?: {[path: string]: string};
|
||||||
branding?: QBrandingMetaData;
|
branding?: QBrandingMetaData;
|
||||||
helpHelpActive?: boolean;
|
helpHelpActive?: boolean;
|
||||||
|
userId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
|
@ -28,16 +28,17 @@ import Box from "@mui/material/Box";
|
|||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import {ErrorMessage, useFormikContext} from "formik";
|
import {ErrorMessage, useFormikContext} from "formik";
|
||||||
import React, {useEffect, useState} from "react";
|
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
tableName?: string;
|
tableName?: string;
|
||||||
processName?: string;
|
processName?: string;
|
||||||
fieldName: string;
|
fieldName?: string;
|
||||||
|
possibleValueSourceName?: string;
|
||||||
overrideId?: string;
|
overrideId?: string;
|
||||||
fieldLabel: string;
|
fieldLabel: string;
|
||||||
inForm: boolean;
|
inForm: boolean;
|
||||||
@ -57,6 +58,8 @@ interface Props
|
|||||||
DynamicSelect.defaultProps = {
|
DynamicSelect.defaultProps = {
|
||||||
tableName: null,
|
tableName: null,
|
||||||
processName: null,
|
processName: null,
|
||||||
|
fieldName: null,
|
||||||
|
possibleValueSourceName: null,
|
||||||
inForm: true,
|
inForm: true,
|
||||||
initialValue: null,
|
initialValue: null,
|
||||||
initialDisplayValue: null,
|
initialDisplayValue: null,
|
||||||
@ -73,16 +76,78 @@ DynamicSelect.defaultProps = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {inputBorderColor} = colors;
|
||||||
|
|
||||||
|
|
||||||
|
export const getAutocompleteOutlinedStyle = (isDisabled: boolean) =>
|
||||||
|
{
|
||||||
|
return ({
|
||||||
|
"& .MuiOutlinedInput-root": {
|
||||||
|
borderRadius: "0.75rem",
|
||||||
|
},
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
padding: "0.5rem",
|
||||||
|
background: isDisabled ? "#f0f2f5!important" : "initial",
|
||||||
|
},
|
||||||
|
"& .MuiOutlinedInput-root .MuiAutocomplete-input": {
|
||||||
|
padding: "0",
|
||||||
|
fontSize: "1rem"
|
||||||
|
},
|
||||||
|
"& .Mui-disabled .MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderColor: inputBorderColor
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const qController = Client.getInstance();
|
const qController = Client.getInstance();
|
||||||
|
|
||||||
function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabel, inForm, initialValue, initialDisplayValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues, variant, initiallyOpen}: Props)
|
function DynamicSelect({tableName, processName, fieldName, possibleValueSourceName, overrideId, fieldLabel, inForm, initialValue, initialDisplayValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues, variant, initiallyOpen}: Props)
|
||||||
{
|
{
|
||||||
const [open, setOpen] = useState(initiallyOpen);
|
const [open, setOpen] = useState(initiallyOpen);
|
||||||
const [options, setOptions] = useState<readonly QPossibleValue[]>([]);
|
const [options, setOptions] = useState<readonly QPossibleValue[]>([]);
|
||||||
const [searchTerm, setSearchTerm] = useState(null);
|
const [searchTerm, setSearchTerm] = useState(null);
|
||||||
const [firstRender, setFirstRender] = useState(true);
|
const [firstRender, setFirstRender] = useState(true);
|
||||||
const [otherValuesWhenResultsWereLoaded, setOtherValuesWhenResultsWereLoaded] = useState(JSON.stringify(Object.fromEntries((otherValues))))
|
const [otherValuesWhenResultsWereLoaded, setOtherValuesWhenResultsWereLoaded] = useState(JSON.stringify(Object.fromEntries((otherValues))))
|
||||||
const {inputBorderColor} = colors;
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(tableName && processName)
|
||||||
|
{
|
||||||
|
console.log("DynamicSelect - you may not provide both a tableName and a processName")
|
||||||
|
}
|
||||||
|
if(tableName && !fieldName)
|
||||||
|
{
|
||||||
|
console.log("DynamicSelect - if you provide a tableName, you must also provide a fieldName");
|
||||||
|
}
|
||||||
|
if(processName && !fieldName)
|
||||||
|
{
|
||||||
|
console.log("DynamicSelect - if you provide a processName, you must also provide a fieldName");
|
||||||
|
}
|
||||||
|
if(fieldName && possibleValueSourceName)
|
||||||
|
{
|
||||||
|
console.log("DynamicSelect - if you provide a fieldName and a possibleValueSourceName, the possibleValueSourceName will be ignored");
|
||||||
|
}
|
||||||
|
if(!fieldName && !possibleValueSourceName)
|
||||||
|
{
|
||||||
|
console.log("DynamicSelect - you must provide either a fieldName (and a tableName or processName) or a possibleValueSourceName");
|
||||||
|
}
|
||||||
|
if(fieldName)
|
||||||
|
{
|
||||||
|
if(!tableName || !processName)
|
||||||
|
{
|
||||||
|
console.log("DynamicSelect - if you provide a fieldName, you must also provide a tableName or processName");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(possibleValueSourceName)
|
||||||
|
{
|
||||||
|
if(tableName || processName)
|
||||||
|
{
|
||||||
|
console.log("DynamicSelect - if you provide a possibleValueSourceName, you should not also provide a tableName or processName");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [tableName, processName, fieldName, possibleValueSourceName]);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// default value - needs to be an array (from initialValues (array) prop) for multiple mode - //
|
// default value - needs to be an array (from initialValues (array) prop) for multiple mode - //
|
||||||
@ -133,7 +198,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
|||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
// console.log(`doing a search with ${searchTerm}`);
|
// console.log(`doing a search with ${searchTerm}`);
|
||||||
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, fieldName, searchTerm ?? "", null, otherValues);
|
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, fieldName ?? possibleValueSourceName, searchTerm ?? "", null, otherValues);
|
||||||
|
|
||||||
if(tableMetaData == null && tableName)
|
if(tableMetaData == null && tableName)
|
||||||
{
|
{
|
||||||
@ -166,7 +231,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setOptions([]);
|
setOptions([]);
|
||||||
console.log("Refreshing possible values...");
|
console.log("Refreshing possible values...");
|
||||||
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, fieldName, searchTerm ?? "", null, otherValues);
|
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, fieldName ?? possibleValueSourceName, searchTerm ?? "", null, otherValues);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setOptions([ ...results ]);
|
setOptions([ ...results ]);
|
||||||
setOtherValuesWhenResultsWereLoaded(JSON.stringify(Object.fromEntries(otherValues)));
|
setOtherValuesWhenResultsWereLoaded(JSON.stringify(Object.fromEntries(otherValues)));
|
||||||
@ -206,7 +271,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
|||||||
onChange(value ? new QPossibleValue(value) : null);
|
onChange(value ? new QPossibleValue(value) : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(setFieldValueRef)
|
else if(setFieldValueRef && fieldName)
|
||||||
{
|
{
|
||||||
setFieldValueRef(fieldName, value ? value.id : null);
|
setFieldValueRef(fieldName, value ? value.id : null);
|
||||||
}
|
}
|
||||||
@ -282,28 +347,13 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
|||||||
let autocompleteSX = {};
|
let autocompleteSX = {};
|
||||||
if (variant == "outlined")
|
if (variant == "outlined")
|
||||||
{
|
{
|
||||||
autocompleteSX = {
|
autocompleteSX = getAutocompleteOutlinedStyle(isDisabled);
|
||||||
"& .MuiOutlinedInput-root": {
|
|
||||||
borderRadius: "0.75rem",
|
|
||||||
},
|
|
||||||
"& .MuiInputBase-root": {
|
|
||||||
padding: "0.5rem",
|
|
||||||
background: isDisabled ? "#f0f2f5!important" : "initial",
|
|
||||||
},
|
|
||||||
"& .MuiOutlinedInput-root .MuiAutocomplete-input": {
|
|
||||||
padding: "0",
|
|
||||||
fontSize: "1rem"
|
|
||||||
},
|
|
||||||
"& .Mui-disabled .MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderColor: inputBorderColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const autocomplete = (
|
const autocomplete = (
|
||||||
<Box>
|
<Box>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
id={overrideId ?? fieldName}
|
id={overrideId ?? fieldName ?? possibleValueSourceName}
|
||||||
sx={autocompleteSX}
|
sx={autocompleteSX}
|
||||||
open={open}
|
open={open}
|
||||||
fullWidth
|
fullWidth
|
||||||
@ -383,7 +433,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
|||||||
inForm &&
|
inForm &&
|
||||||
<Box mt={0.75}>
|
<Box mt={0.75}>
|
||||||
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
||||||
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={fieldName} render={msg => <span data-field-error="true">{msg}</span>} /></div>}
|
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={fieldName ?? possibleValueSourceName} render={msg => <span data-field-error="true">{msg}</span>} /></div>}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
482
src/qqq/components/sharing/ShareModal.tsx
Normal file
482
src/qqq/components/sharing/ShareModal.tsx
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
||||||
|
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||||
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
|
import {Alert} from "@mui/material";
|
||||||
|
import Autocomplete from "@mui/material/Autocomplete";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Card from "@mui/material/Card";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import Modal from "@mui/material/Modal";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import FormData from "form-data";
|
||||||
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
|
import {QCancelButton} from "qqq/components/buttons/DefaultButtons";
|
||||||
|
import DynamicSelect, {getAutocompleteOutlinedStyle} from "qqq/components/forms/DynamicSelect";
|
||||||
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
import React, {useEffect, useReducer, useState} from "react";
|
||||||
|
|
||||||
|
interface ShareModalProps
|
||||||
|
{
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
tableMetaData: QTableMetaData;
|
||||||
|
record: QRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShareModal.defaultProps = {};
|
||||||
|
|
||||||
|
|
||||||
|
interface CurrentShare
|
||||||
|
{
|
||||||
|
shareId: any;
|
||||||
|
scopeId: string;
|
||||||
|
audienceType: string;
|
||||||
|
audienceId: any;
|
||||||
|
audienceLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Scope
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scopeOptions: Scope[] = [
|
||||||
|
{id: "READ_ONLY", label: "Read-Only"},
|
||||||
|
{id: "READ_WRITE", label: "Read and Edit"}
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultScope = scopeOptions[0];
|
||||||
|
|
||||||
|
const qController = Client.getInstance();
|
||||||
|
|
||||||
|
interface ShareableTableMetaData
|
||||||
|
{
|
||||||
|
sharedRecordTableName: string;
|
||||||
|
assetIdFieldName: string;
|
||||||
|
scopeFieldName: string;
|
||||||
|
audienceTypesPossibleValueSourceName: string;
|
||||||
|
audiencePossibleValueSourceName: string;
|
||||||
|
thisTableOwnerIdFieldName: string;
|
||||||
|
audienceTypes: {[name: string]: any}; // values here are: ShareableAudienceType
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** component containing a Modal dialog for sharing records
|
||||||
|
*******************************************************************************/
|
||||||
|
export default function ShareModal({open, onClose, tableMetaData, record}: ShareModalProps): JSX.Element
|
||||||
|
{
|
||||||
|
const [statusString, setStatusString] = useState("Loading...");
|
||||||
|
const [alert, setAlert] = useState(null as string);
|
||||||
|
|
||||||
|
const [selectedAudienceOption, setSelectedAudienceOption] = useState(null as {id: string, label: string});
|
||||||
|
const [selectedAudienceType, setSelectedAudienceType] = useState(null);
|
||||||
|
const [selectedAudienceId, setSelectedAudienceId] = useState(null);
|
||||||
|
const [selectedScopeId, setSelectedScopeId] = useState(defaultScope.id);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const [currentShares, setCurrentShares] = useState([] as CurrentShare[])
|
||||||
|
const [needToLoadCurrentShares, setNeedToLoadCurrentShares] = useState(true);
|
||||||
|
const [everLoadedCurrentShares, setEverLoadedCurrentShares] = useState(false);
|
||||||
|
|
||||||
|
const shareableTableMetaData = tableMetaData.shareableTableMetaData as ShareableTableMetaData;
|
||||||
|
|
||||||
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
|
if(!shareableTableMetaData)
|
||||||
|
{
|
||||||
|
console.error(`Did not find a shareableTableMetaData on table ${tableMetaData.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
// trigger initial load, and post any changes, re-load //
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(needToLoadCurrentShares)
|
||||||
|
{
|
||||||
|
loadShares();
|
||||||
|
}
|
||||||
|
}, [needToLoadCurrentShares]);
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function close(event: object, reason: string)
|
||||||
|
{
|
||||||
|
if (reason === "backdropClick" || reason === "escapeKeyDown")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function handleAudienceChange(value: any | any[], reason: string)
|
||||||
|
{
|
||||||
|
if(value)
|
||||||
|
{
|
||||||
|
const [audienceType, audienceId] = value.id.split(":");
|
||||||
|
setSelectedAudienceType(audienceType);
|
||||||
|
setSelectedAudienceId(audienceId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setSelectedAudienceType(null);
|
||||||
|
setSelectedAudienceId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function handleScopeChange(event: React.SyntheticEvent, value: any | any[], reason: string)
|
||||||
|
{
|
||||||
|
if(value)
|
||||||
|
{
|
||||||
|
setSelectedScopeId(value.id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setSelectedScopeId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
async function editingExistingShareScope(shareId: number, value: any | any[])
|
||||||
|
{
|
||||||
|
setStatusString("Saving...");
|
||||||
|
setAlert(null);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("tableName", tableMetaData.name);
|
||||||
|
formData.append("recordId", record.values.get(tableMetaData.primaryKeyField));
|
||||||
|
formData.append("shareId", shareId);
|
||||||
|
formData.append("scopeId", value.id);
|
||||||
|
|
||||||
|
const processResult = await qController.processRun("editSharedRecord", formData, null, true);
|
||||||
|
|
||||||
|
if (processResult instanceof QJobError)
|
||||||
|
{
|
||||||
|
const jobError = processResult as QJobError;
|
||||||
|
setStatusString(null);
|
||||||
|
setAlert("Error editing shared record: " + jobError.error);
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const result = processResult as QJobComplete;
|
||||||
|
setStatusString(null);
|
||||||
|
setAlert(null);
|
||||||
|
setNeedToLoadCurrentShares(true);
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
async function loadShares()
|
||||||
|
{
|
||||||
|
setNeedToLoadCurrentShares(false);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("tableName", tableMetaData.name);
|
||||||
|
formData.append("recordId", record.values.get(tableMetaData.primaryKeyField));
|
||||||
|
const processResult = await qController.processRun("getSharedRecords", formData, null, true);
|
||||||
|
|
||||||
|
setStatusString("Loading...");
|
||||||
|
setAlert(null)
|
||||||
|
|
||||||
|
if (processResult instanceof QJobError)
|
||||||
|
{
|
||||||
|
const jobError = processResult as QJobError;
|
||||||
|
setStatusString(null);
|
||||||
|
setAlert("Error loading: " + jobError.error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const result = processResult as QJobComplete;
|
||||||
|
|
||||||
|
const newCurrentShares: CurrentShare[] = [];
|
||||||
|
for (let i in result.values["resultList"])
|
||||||
|
{
|
||||||
|
newCurrentShares.push(result.values["resultList"][i].values);
|
||||||
|
}
|
||||||
|
setCurrentShares(newCurrentShares);
|
||||||
|
setEverLoadedCurrentShares(true);
|
||||||
|
|
||||||
|
setStatusString(null);
|
||||||
|
setAlert(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
async function saveNewShare()
|
||||||
|
{
|
||||||
|
setSubmitting(true)
|
||||||
|
setStatusString("Saving...");
|
||||||
|
setAlert(null);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("tableName", tableMetaData.name);
|
||||||
|
formData.append("recordId", record.values.get(tableMetaData.primaryKeyField));
|
||||||
|
formData.append("audienceType", selectedAudienceType);
|
||||||
|
formData.append("audienceId", selectedAudienceId);
|
||||||
|
formData.append("scopeId", selectedScopeId);
|
||||||
|
|
||||||
|
const processResult = await qController.processRun("insertSharedRecord", formData, null, true);
|
||||||
|
|
||||||
|
if (processResult instanceof QJobError)
|
||||||
|
{
|
||||||
|
const jobError = processResult as QJobError;
|
||||||
|
setStatusString(null);
|
||||||
|
setAlert("Error sharing record: " + jobError.error);
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const result = processResult as QJobComplete;
|
||||||
|
setStatusString(null);
|
||||||
|
setAlert(null);
|
||||||
|
setSelectedAudienceOption(null);
|
||||||
|
setNeedToLoadCurrentShares(true);
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
async function removeShare(shareId: number)
|
||||||
|
{
|
||||||
|
setStatusString("Deleting...");
|
||||||
|
setAlert(null);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("tableName", tableMetaData.name);
|
||||||
|
formData.append("recordId", record.values.get(tableMetaData.primaryKeyField));
|
||||||
|
formData.append("shareId", shareId);
|
||||||
|
|
||||||
|
const processResult = await qController.processRun("deleteSharedRecord", formData, null, true);
|
||||||
|
|
||||||
|
if (processResult instanceof QJobError)
|
||||||
|
{
|
||||||
|
const jobError = processResult as QJobError;
|
||||||
|
setStatusString(null);
|
||||||
|
setAlert("Error deleting share: " + jobError.error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const result = processResult as QJobComplete;
|
||||||
|
setNeedToLoadCurrentShares(true);
|
||||||
|
setStatusString(null);
|
||||||
|
setAlert(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function getScopeOption(scopeId: string): Scope
|
||||||
|
{
|
||||||
|
for (let scopeOption of scopeOptions)
|
||||||
|
{
|
||||||
|
if(scopeOption.id == scopeId)
|
||||||
|
{
|
||||||
|
return (scopeOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function renderScopeDropdown(id: string, defaultValue: Scope, onChange: (event: React.SyntheticEvent, value: any | any[], reason: string) => void)
|
||||||
|
{
|
||||||
|
const isDisabled = (id == "new-share-scope" && submitting);
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
id={id}
|
||||||
|
disabled={isDisabled}
|
||||||
|
renderInput={(params) => (<TextField {...params} label="Scope" variant="outlined" autoComplete="off" type="search" InputProps={{...params.InputProps}} />)}
|
||||||
|
options={scopeOptions}
|
||||||
|
// @ts-ignore
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
onChange={onChange}
|
||||||
|
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||||
|
// @ts-ignore Property label does not exist on string | {thing with label}
|
||||||
|
getOptionLabel={(option) => option.label}
|
||||||
|
autoSelect={true}
|
||||||
|
autoHighlight={true}
|
||||||
|
disableClearable
|
||||||
|
fullWidth
|
||||||
|
sx={getAutocompleteOutlinedStyle(isDisabled)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////
|
||||||
|
// render the modal //
|
||||||
|
//////////////////////
|
||||||
|
return (<Modal open={open} onClose={close}>
|
||||||
|
<div className="share">
|
||||||
|
<Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%", display: "flex", height: "100%", flexDirection: "column", justifyContent: "center"}}>
|
||||||
|
<Card sx={{my: 5, mx: "auto", p: 3}}>
|
||||||
|
|
||||||
|
{/* header */}
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="flex-start">
|
||||||
|
<Typography variant="h4" pb={1} fontWeight="600">
|
||||||
|
Share {tableMetaData.label}: {record?.recordLabel ?? record?.values?.get(tableMetaData.primaryKeyField) ?? "Unknown"}
|
||||||
|
<Box color={colors.gray.main} pb={"0.5rem"} fontSize={"0.875rem"} fontWeight="400" maxWidth="590px">
|
||||||
|
{/* todo move to helpContent (what do we attach the meta-data too??) */}
|
||||||
|
Select a user or a group to share this record with.
|
||||||
|
You can choose if they should only be able to Read the record, or also make Edits to it.
|
||||||
|
</Box>
|
||||||
|
<Box fontSize={14} maxWidth="590px" pb={1} fontWeight="300">
|
||||||
|
{alert && <Alert color="error" onClose={() => setAlert(null)}>{alert}</Alert>}
|
||||||
|
{statusString}
|
||||||
|
{!alert && !statusString && (<> </>)}
|
||||||
|
</Box>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* body */}
|
||||||
|
<Box pb={3} display="flex" flexDirection="column">
|
||||||
|
{/* row for adding a new share */}
|
||||||
|
<Box display="flex" flexDirection="row" alignItems="center">
|
||||||
|
<Box width="350px" pr={2} mb={-1.5}>
|
||||||
|
<DynamicSelect
|
||||||
|
possibleValueSourceName={shareableTableMetaData.audiencePossibleValueSourceName}
|
||||||
|
fieldLabel="User or Group" // todo should come from shareableTableMetaData
|
||||||
|
initialValue={selectedAudienceOption?.id}
|
||||||
|
initialDisplayValue={selectedAudienceOption?.label}
|
||||||
|
inForm={false}
|
||||||
|
onChange={handleAudienceChange}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box width="180px" pr={2}>
|
||||||
|
{renderScopeDropdown("new-share-scope", defaultScope, handleScopeChange)}
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Tooltip title={selectedAudienceId == null ? "Select a user or group to share with." : null}>
|
||||||
|
<span>
|
||||||
|
<Button disabled={submitting || selectedAudienceId == null} sx={iconButtonSX} onClick={() => saveNewShare()}>
|
||||||
|
<Icon color={selectedAudienceId == null ? "secondary" : "info"}>save</Icon>
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* row showing existing shares */}
|
||||||
|
<Box pt={3}>
|
||||||
|
<Box pb="0.25rem">
|
||||||
|
<h5 style={{fontWeight: "600"}}>Current Shares
|
||||||
|
{
|
||||||
|
everLoadedCurrentShares ? <> ({currentShares.length})</> : <></>
|
||||||
|
}
|
||||||
|
</h5>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{border: `1px solid ${colors.grayLines.main}`, borderRadius: "1rem", overflow: "hidden"}}>
|
||||||
|
<Box sx={{overflow: "auto"}} height="210px" pt="0.75rem">
|
||||||
|
{
|
||||||
|
currentShares.map((share) => (
|
||||||
|
<Box key={share.shareId} display="flex" justifyContent="space-between" alignItems="center" p="0.25rem" pb="0.75rem" fontSize="1rem">
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Box width="310px" pl="1rem">{share.audienceLabel}</Box>
|
||||||
|
<Box width="160px">{renderScopeDropdown(`scope-${share.shareId}`, getScopeOption(share.scopeId), (event: React.SyntheticEvent, value: any | any[], reason: string) => editingExistingShareScope(share.shareId, value))}</Box>
|
||||||
|
</Box>
|
||||||
|
<Box pr="1rem">
|
||||||
|
<Button sx={{...iconButtonSX, ...redIconButton}} onClick={() => removeShare(share.shareId)}><Icon>clear</Icon></Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* footer */}
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="flex-end">
|
||||||
|
<QCancelButton label="Done" iconName="check" onClickHandler={() => close(null, null)} disabled={false} />
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</Modal>);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconButtonSX =
|
||||||
|
{
|
||||||
|
border: `1px solid ${colors.grayLines.main} !important`,
|
||||||
|
borderRadius: "0.75rem",
|
||||||
|
textTransform: "none",
|
||||||
|
fontSize: "1rem",
|
||||||
|
fontWeight: "400",
|
||||||
|
width: "40px",
|
||||||
|
minWidth: "40px",
|
||||||
|
paddingLeft: 0,
|
||||||
|
paddingRight: 0,
|
||||||
|
color: colors.secondary.main,
|
||||||
|
"&:hover": {color: colors.secondary.main},
|
||||||
|
"&:focus": {color: colors.secondary.main},
|
||||||
|
"&:focus:not(:hover)": {color: colors.secondary.main},
|
||||||
|
};
|
||||||
|
|
||||||
|
const redIconButton =
|
||||||
|
{
|
||||||
|
color: colors.error.main,
|
||||||
|
"&:hover": {color: colors.error.main},
|
||||||
|
"&:focus": {color: colors.error.main},
|
||||||
|
"&:focus:not(:hover)": {color: colors.error.main},
|
||||||
|
};
|
||||||
|
|
@ -49,11 +49,13 @@ import Tooltip from "@mui/material/Tooltip/Tooltip";
|
|||||||
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";
|
||||||
import {QActionsMenuButton, QCancelButton, QDeleteButton, QEditButton} from "qqq/components/buttons/DefaultButtons";
|
import {QActionsMenuButton, QCancelButton, QDeleteButton, QEditButton, standardWidth} from "qqq/components/buttons/DefaultButtons";
|
||||||
import EntityForm from "qqq/components/forms/EntityForm";
|
import EntityForm from "qqq/components/forms/EntityForm";
|
||||||
|
import MDButton from "qqq/components/legacy/MDButton";
|
||||||
import {GotoRecordButton} from "qqq/components/misc/GotoRecordDialog";
|
import {GotoRecordButton} from "qqq/components/misc/GotoRecordDialog";
|
||||||
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||||
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
|
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
|
||||||
|
import ShareModal from "qqq/components/sharing/ShareModal";
|
||||||
import DashboardWidgets from "qqq/components/widgets/DashboardWidgets";
|
import DashboardWidgets from "qqq/components/widgets/DashboardWidgets";
|
||||||
import BaseLayout from "qqq/layouts/BaseLayout";
|
import BaseLayout from "qqq/layouts/BaseLayout";
|
||||||
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
||||||
@ -82,6 +84,10 @@ RecordView.defaultProps =
|
|||||||
|
|
||||||
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
|
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Record View Screen component.
|
||||||
|
*******************************************************************************/
|
||||||
function RecordView({table, launchProcess}: Props): JSX.Element
|
function RecordView({table, launchProcess}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const {id} = useParams();
|
const {id} = useParams();
|
||||||
@ -117,11 +123,14 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
|
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
|
||||||
const [showEditChildForm, setShowEditChildForm] = useState(null as any);
|
const [showEditChildForm, setShowEditChildForm] = useState(null as any);
|
||||||
const [showAudit, setShowAudit] = useState(false);
|
const [showAudit, setShowAudit] = useState(false);
|
||||||
|
const [showShareModal, setShowShareModal] = useState(false);
|
||||||
|
|
||||||
|
const [isDeleteSubmitting, setIsDeleteSubmitting] = useState(false);
|
||||||
|
|
||||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||||
const closeActionsMenu = () => setActionsMenu(null);
|
const closeActionsMenu = () => setActionsMenu(null);
|
||||||
|
|
||||||
const {accentColor, setPageHeader, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen, keyboardHelpOpen, helpHelpActive, recordAnalytics} = useContext(QContext);
|
const {accentColor, setPageHeader, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen, keyboardHelpOpen, helpHelpActive, recordAnalytics, userId: currentUserId} = useContext(QContext);
|
||||||
|
|
||||||
if (localStorage.getItem(tableVariantLocalStorageKey))
|
if (localStorage.getItem(tableVariantLocalStorageKey))
|
||||||
{
|
{
|
||||||
@ -622,6 +631,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
const handleClickDeleteButton = () =>
|
const handleClickDeleteButton = () =>
|
||||||
{
|
{
|
||||||
setDeleteConfirmationOpen(true);
|
setDeleteConfirmationOpen(true);
|
||||||
|
setIsDeleteSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteConfirmClose = () =>
|
const handleDeleteConfirmClose = () =>
|
||||||
@ -631,6 +641,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
const handleDelete = (event: { preventDefault: () => void }) =>
|
const handleDelete = (event: { preventDefault: () => void }) =>
|
||||||
{
|
{
|
||||||
|
setIsDeleteSubmitting(true);
|
||||||
event?.preventDefault();
|
event?.preventDefault();
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
@ -639,11 +650,13 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
await qController.delete(tableName, id)
|
await qController.delete(tableName, id)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
{
|
{
|
||||||
|
setIsDeleteSubmitting(false);
|
||||||
const path = pathParts.slice(0, -1).join("/");
|
const path = pathParts.slice(0, -1).join("/");
|
||||||
navigate(path, {state: {deleteSuccess: true}});
|
navigate(path, {state: {deleteSuccess: true}});
|
||||||
})
|
})
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
{
|
{
|
||||||
|
setIsDeleteSubmitting(false);
|
||||||
setDeleteConfirmationOpen(false);
|
setDeleteConfirmationOpen(false);
|
||||||
console.log("Caught:");
|
console.log("Caught:");
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@ -759,6 +772,68 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** function to open the sharing modal
|
||||||
|
*******************************************************************************/
|
||||||
|
const openShareModal = () =>
|
||||||
|
{
|
||||||
|
setShowShareModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** function to close the sharing modal
|
||||||
|
*******************************************************************************/
|
||||||
|
const closeShareModal = () =>
|
||||||
|
{
|
||||||
|
setShowShareModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** render the share button (if allowed for table)
|
||||||
|
*******************************************************************************/
|
||||||
|
const renderShareButton = () =>
|
||||||
|
{
|
||||||
|
if (tableMetaData && tableMetaData.shareableTableMetaData)
|
||||||
|
{
|
||||||
|
let shareDisabled = true;
|
||||||
|
let disabledTooltipText = "";
|
||||||
|
if(tableMetaData.shareableTableMetaData.thisTableOwnerIdFieldName && record)
|
||||||
|
{
|
||||||
|
const ownerId = record.values.get(tableMetaData.shareableTableMetaData.thisTableOwnerIdFieldName);
|
||||||
|
if(ownerId != currentUserId)
|
||||||
|
{
|
||||||
|
disabledTooltipText = `Only the owner of a ${tableMetaData.label} may share it.`
|
||||||
|
shareDisabled = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
disabledTooltipText = "";
|
||||||
|
shareDisabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shareDisabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<Box width={standardWidth} mr={2}>
|
||||||
|
<Tooltip title={disabledTooltipText}>
|
||||||
|
<span>
|
||||||
|
<MDButton id="shareButton" type="button" color="info" size="small" onClick={() => openShareModal()} fullWidth startIcon={<Icon>group_add</Icon>} disabled={shareDisabled}>
|
||||||
|
Share
|
||||||
|
</MDButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<React.Fragment />);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const openModalProcess = (process: QProcessMetaData = null) =>
|
const openModalProcess = (process: QProcessMetaData = null) =>
|
||||||
{
|
{
|
||||||
navigate(process.name);
|
navigate(process.name);
|
||||||
@ -914,6 +989,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Box display="flex">
|
<Box display="flex">
|
||||||
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} />
|
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} />
|
||||||
|
{renderShareButton()}
|
||||||
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||||
</Box>
|
</Box>
|
||||||
{renderActionsMenu}
|
{renderActionsMenu}
|
||||||
@ -962,7 +1038,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleDeleteConfirmClose}>No</Button>
|
<Button onClick={handleDeleteConfirmClose}>No</Button>
|
||||||
<Button onClick={handleDelete} autoFocus>
|
<Button onClick={handleDelete} autoFocus disabled={isDeleteSubmitting}>
|
||||||
Yes
|
Yes
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
@ -1010,6 +1086,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
showShareModal && tableMetaData && record &&
|
||||||
|
<ShareModal open={showShareModal} onClose={closeShareModal} tableMetaData={tableMetaData} record={record}></ShareModal>
|
||||||
|
}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -47,6 +47,8 @@ module.exports = function (app)
|
|||||||
app.use("/download/*", getRequestHandler());
|
app.use("/download/*", getRequestHandler());
|
||||||
app.use("/metaData/*", getRequestHandler());
|
app.use("/metaData/*", getRequestHandler());
|
||||||
app.use("/data/*", getRequestHandler());
|
app.use("/data/*", getRequestHandler());
|
||||||
|
app.use("/possibleValues/*", getRequestHandler());
|
||||||
|
app.use("/possibleValues", getRequestHandler());
|
||||||
app.use("/widget/*", getRequestHandler());
|
app.use("/widget/*", getRequestHandler());
|
||||||
app.use("/serverInfo", getRequestHandler());
|
app.use("/serverInfo", getRequestHandler());
|
||||||
app.use("/manageSession", getRequestHandler());
|
app.use("/manageSession", getRequestHandler());
|
||||||
|
Reference in New Issue
Block a user