react-table#CellProps TypeScript Examples
The following examples show how to use
react-table#CellProps.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: columns.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 6 votes |
createColumns: CreateColumns = () => [
{
Header: 'Details',
Cell: ({ row: { original } }: CellProps<Domain>) => (
<Link to={`/inventory/domain/${original.id}`}>
<FaSearch className="margin-x-auto display-block" />
</Link>
)
},
{
Header: 'Organization',
accessor: (e) => e.organization.name,
id: 'organizationName',
Filter: ColumnFilter
},
{
Header: 'Domain',
accessor: 'name',
id: 'reverseName',
Filter: ColumnFilter
},
{
Header: 'IP',
accessor: 'ip',
Filter: ColumnFilter
},
{
Header: 'Ports',
id: 'port',
disableSortBy: true,
accessor: ({ services }) =>
services.map((service) => service.port).join(', '),
Filter: ColumnFilter
},
{
Header: 'Services',
id: 'service',
disableSortBy: true,
accessor: (domain) => getServiceNames(domain),
Filter: ColumnFilter
},
{
Header: 'Vulnerabilities',
id: 'vulnerability',
accessor: (domain) =>
domain.vulnerabilities &&
domain.vulnerabilities
.map((vulnerability) => vulnerability.cve)
.join(', '),
Filter: ColumnFilter
},
{
Header: 'Last Seen',
id: 'updatedAt',
accessor: ({ updatedAt }) =>
`${formatDistanceToNow(parseISO(updatedAt))} ago`,
disableFilters: true
},
{
Header: 'First Seen',
id: 'createdAt',
accessor: ({ createdAt }) =>
`${formatDistanceToNow(parseISO(createdAt))} ago`,
disableFilters: true
}
]
Example #2
Source File: columns.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 6 votes |
createGroupedColumns = () =>
[
{
Header: 'Vulnerability',
accessor: 'title',
Cell: ({ value, row }: CellProps<Vulnerability>) =>
row.original.cve ? (
<a
href={`https://nvd.nist.gov/vuln/detail/${row.original.cve}`}
target="_blank"
rel="noopener noreferrer"
>
{value} {extLink}
</a>
) : (
<p>{row.original.title}</p>
),
width: 800,
Filter: ColumnFilter
},
{
Header: 'Severity',
id: 'severity',
accessor: ({ severity }) => (
<span
style={{
borderBottom: `6px solid ${getSeverityColor({
id: severity ?? ''
})}`,
width: '80px'
}}
>
{severity}
</span>
),
width: 100,
Filter: selectFilter(['Low', 'Medium', 'High', 'Critical', 'None'])
},
{
Header: 'Count',
accessor: 'cnt',
disableFilters: true
},
{
Header: 'Description',
accessor: 'description',
disableFilters: true
}
] as Column<Vulnerability>[]
Example #3
Source File: RawQueryFetcher.tsx From companion-kit with MIT License | 5 votes |
function TableForObject(props: { data: any }): JSX.Element {
const arr = props.data && props.data.array;
if (!arr || !Array.isArray(arr) || !arr.length) {
return (
<>
{JSON.stringify(props.data || '', null, 2)}
</>
);
}
const keysSet = new Set<string>();
arr.forEach(obj => {
Object.keys(obj).forEach(k => {
keysSet.add(k);
});
});
const keys = Array.from(keysSet);
const columns = React.useMemo(
() => keys.map(k => ({
Header: k,
accessor: k,
Cell: (row: CellProps<any>) => {
const o = row.cell.value;
if (o == null) {
return null;
}
if (typeof o === 'object') {
return <JSONTree data={o} />;
}
return o + '';
},
})),
[ keys ],
);
return Table({ columns, data: arr });
}
Example #4
Source File: ScanTasksView.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
ScanTasksView: React.FC = () => {
const { apiPost, token } = useAuthContext();
const [scanTasks, setScanTasks] = useState<ScanTask[]>([]);
const [totalResults, setTotalResults] = useState(0);
const [errors, setErrors] = useState<Errors>({});
const killScanTask = async (index: number) => {
try {
const row = scanTasks[index];
await apiPost(`/scan-tasks/${row.id}/kill`, { body: {} });
setScanTasks(
Object.assign([], scanTasks, {
[index]: {
...row,
status: 'failed'
}
})
);
} catch (e) {
setErrors({
global:
e.status === 422 ? 'Unable to kill scan' : e.message ?? e.toString()
});
console.log(e);
}
};
const renderExpanded = (row: Row<ScanTask>) => {
const { original } = row;
return (
<div className={classes.expandedRoot}>
{original.fargateTaskArn && (
<>
<h4>
Logs
{original.fargateTaskArn?.match('.*/(.*)') && (
<a
target="_blank"
rel="noopener noreferrer"
href={`https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group/${process
.env
.REACT_APP_FARGATE_LOG_GROUP!}/log-events/worker$252Fmain$252F${
(original.fargateTaskArn.match('.*/(.*)') || [])[1]
}`}
>
{' '}
(View all on CloudWatch)
</a>
)}
</h4>
<Log
token={token ?? ''}
url={`${process.env.REACT_APP_API_URL}/scan-tasks/${original.id}/logs`}
/>
</>
)}
<h4>Input</h4>
<small>
<pre>{JSON.stringify(JSON.parse(original.input), null, 2)}</pre>
</small>
<h4>Output</h4>
<small>
<pre>{original.output || 'None'}</pre>
</small>
{row.original.status !== 'finished' &&
row.original.status !== 'failed' && (
<>
<h4>Actions</h4>
<a
href="# "
onClick={(e) => {
e.preventDefault();
killScanTask(row.index);
}}
>
Kill
</a>
</>
)}
</div>
);
};
const columns: Column<ScanTask>[] = [
{
Header: 'ID',
accessor: 'id',
Filter: ColumnFilter,
disableSortBy: true,
disableFilters: true
},
{
Header: 'Status',
accessor: 'status',
Filter: selectFilter([
'created',
'queued',
'requested',
'started',
'finished',
'failed'
]),
disableSortBy: true
},
{
Header: 'Name',
id: 'name',
accessor: ({ scan }) => scan?.name,
Filter: selectFilter([
// TODO: sync this with the SCAN_SCHEMA
'censys',
'amass',
'findomain',
'portscanner',
'wappalyzer',
'censysIpv4',
'censysCertificates',
'sslyze',
'searchSync',
'cve',
'dotgov',
'webscraper',
'intrigueIdent',
'shodan',
'hibp',
'lookingGlass',
'dnstwist',
'peCybersixgill',
'peHibpSync',
'peShodan',
'peDomMasq',
'rootDomainSync'
]),
disableSortBy: true
},
{
Header: 'Created At',
id: 'createdAt',
accessor: ({ createdAt }) => dateAccessor(createdAt),
disableFilters: true
},
{
Header: 'Finished At',
id: 'finishedAt',
accessor: ({ finishedAt }) => dateAccessor(finishedAt),
disableFilters: true
},
{
Header: 'Details',
Cell: ({ row }: CellProps<ScanTask>) => (
<span
{...row.getToggleRowExpandedProps()}
className="text-center display-block"
>
{row.isExpanded ? <FaMinus /> : <FaPlus />}
</span>
),
disableFilters: true
}
];
const PAGE_SIZE = 25;
const fetchScanTasks = useCallback(
async (query: Query<ScanTask>) => {
const { page, sort, filters } = query;
try {
const { result, count } = await apiPost<ApiResponse>(
'/scan-tasks/search',
{
body: {
page,
sort: sort[0]?.id ?? 'createdAt',
order: sort[0]?.desc ? 'DESC' : 'ASC',
filters: filters
.filter((f) => Boolean(f.value))
.reduce(
(accum, next) => ({
...accum,
[next.id]: next.value
}),
{}
)
}
}
);
setScanTasks(result);
setTotalResults(count);
} catch (e) {
console.error(e);
}
},
[apiPost]
);
const renderPagination = (table: TableInstance<ScanTask>) => (
<Paginator table={table} totalResults={totalResults} />
);
return (
<>
{errors.global && <p className={classes.error}>{errors.global}</p>}
<Table<ScanTask>
renderPagination={renderPagination}
columns={columns}
data={scanTasks}
pageCount={Math.ceil(totalResults / PAGE_SIZE)}
fetchData={fetchScanTasks}
pageSize={PAGE_SIZE}
initialSortBy={[
{
id: 'createdAt',
desc: true
}
]}
renderExpanded={renderExpanded}
/>
</>
);
}
Example #5
Source File: ScansView.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
ScansView: React.FC = () => {
const { apiGet, apiPost, apiDelete } = useAuthContext();
const [showModal, setShowModal] = useState<Boolean>(false);
const [selectedRow, setSelectedRow] = useState<number>(0);
const [scans, setScans] = useState<Scan[]>([]);
const [organizationOptions, setOrganizationOptions] = useState<
OrganizationOption[]
>([]);
const [tags, setTags] = useState<OrganizationTag[]>([]);
const [scanSchema, setScanSchema] = useState<ScanSchema>({});
const columns: Column<Scan>[] = [
{
Header: 'Run',
id: 'run',
Cell: ({ row }: { row: { index: number } }) => (
<div
style={{ textAlign: 'center' }}
onClick={() => {
runScan(row.index);
}}
>
<FaPlayCircle />
</div>
),
disableFilters: true
},
{
Header: 'Name',
accessor: 'name',
width: 200,
id: 'name',
disableFilters: true
},
{
Header: 'Tags',
accessor: ({ tags }) => tags.map((tag) => tag.name).join(', '),
width: 150,
minWidth: 150,
id: 'tags',
disableFilters: true
},
{
Header: 'Mode',
accessor: ({ name }) =>
scanSchema[name] && scanSchema[name].isPassive ? 'Passive' : 'Active',
width: 150,
minWidth: 150,
id: 'mode',
disableFilters: true
},
{
Header: 'Frequency',
accessor: ({ frequency, isSingleScan }) => {
let val, unit;
if (frequency < 60 * 60) {
val = frequency / 60;
unit = 'minute';
} else if (frequency < 60 * 60 * 24) {
val = frequency / (60 * 60);
unit = 'hour';
} else {
val = frequency / (60 * 60 * 24);
unit = 'day';
}
if (isSingleScan) {
return 'Single Scan';
}
return `Every ${val} ${unit}${val === 1 ? '' : 's'}`;
},
width: 200,
id: 'frequency',
disableFilters: true
},
{
Header: 'Last Run',
accessor: (args: Scan) => {
return !args.lastRun ||
new Date(args.lastRun).getTime() === new Date(0).getTime()
? 'None'
: `${formatDistanceToNow(parseISO(args.lastRun))} ago`;
},
width: 200,
id: 'lastRun',
disableFilters: true
},
{
Header: 'Edit',
id: 'edit',
Cell: ({ row }: CellProps<Scan>) => (
<Link to={`/scans/${row.original.id}`} style={{ color: 'black' }}>
<FaEdit />
</Link>
),
disableFilters: true
},
{
Header: 'Delete',
id: 'delete',
Cell: ({ row }: { row: { index: number } }) => (
<span
onClick={() => {
setShowModal(true);
setSelectedRow(row.index);
}}
>
<FaTimes />
</span>
),
disableFilters: true
},
{
Header: 'Description',
accessor: ({ name }) => scanSchema[name]?.description,
width: 200,
maxWidth: 200,
id: 'description',
disableFilters: true
}
];
const [errors, setErrors] = useState<Errors>({});
const [values] = useState<ScanFormValues>({
name: 'censys',
arguments: '{}',
organizations: [],
frequency: 1,
frequencyUnit: 'minute',
isGranular: false,
isUserModifiable: false,
isSingleScan: false,
tags: []
});
React.useEffect(() => {
document.addEventListener('keyup', (e) => {
//Escape
if (e.keyCode === 27) {
setShowModal(false);
}
});
}, [apiGet]);
const fetchScans = useCallback(async () => {
try {
const { scans, organizations, schema } = await apiGet<{
scans: Scan[];
organizations: Organization[];
schema: ScanSchema;
}>('/scans/');
const tags = await apiGet<OrganizationTag[]>(`/organizations/tags`);
setScans(scans);
setScanSchema(schema);
setOrganizationOptions(
organizations.map((e) => ({ label: e.name, value: e.id }))
);
setTags(tags);
} catch (e) {
console.error(e);
}
}, [apiGet]);
const deleteRow = async (index: number) => {
try {
const row = scans[index];
await apiDelete(`/scans/${row.id}`, { body: {} });
setScans(scans.filter((scan) => scan.id !== row.id));
} catch (e) {
setErrors({
global:
e.status === 422 ? 'Unable to delete scan' : e.message ?? e.toString()
});
console.log(e);
}
};
const onSubmit = async (body: ScanFormValues) => {
try {
// For now, parse the arguments as JSON. We'll want to add a GUI for this in the future
body.arguments = JSON.parse(body.arguments);
setFrequency(body);
const scan = await apiPost('/scans/', {
body: {
...body,
organizations: body.organizations
? body.organizations.map((e) => e.value)
: [],
tags: body.tags ? body.tags.map((e) => ({ id: e.value })) : []
}
});
setScans(scans.concat(scan));
} catch (e) {
setErrors({
global: e.message ?? e.toString()
});
console.log(e);
}
};
const invokeScheduler = async () => {
setErrors({ ...errors, scheduler: '' });
try {
await apiPost('/scheduler/invoke', { body: {} });
} catch (e) {
console.error(e);
setErrors({ ...errors, scheduler: 'Invocation failed.' });
}
};
/**
* Manually runs a single scan, then immediately invokes the
* scheduler so the scan is run.
* @param index Row index
*/
const runScan = async (index: number) => {
const row = scans[index];
try {
await apiPost(`/scans/${row.id}/run`, { body: {} });
} catch (e) {
console.error(e);
setErrors({ ...errors, scheduler: 'Run failed.' });
}
await invokeScheduler();
};
return (
<>
<Table<Scan> columns={columns} data={scans} fetchData={fetchScans} />
<br></br>
<Button type="submit" outline onClick={invokeScheduler}>
Manually run scheduler
</Button>
{errors.scheduler && <p className={classes.error}>{errors.scheduler}</p>}
<h2>Add a scan</h2>
{errors.global && <p className={classes.error}>{errors.global}</p>}
<ScanForm
organizationOption={organizationOptions}
tags={tags}
propValues={values}
onSubmit={onSubmit}
type="create"
scanSchema={scanSchema}
></ScanForm>
<ImportExport<Scan>
name="scans"
fieldsToExport={['name', 'arguments', 'frequency']}
onImport={async (results) => {
// TODO: use a batch call here instead.
const createdScans = [];
for (const result of results) {
createdScans.push(
await apiPost('/scans/', {
body: {
...result,
// These fields are initially parsed as strings, so they need
// to be converted to objects.
arguments: JSON.parse(
((result.arguments as unknown) as string) || ''
)
}
})
);
}
setScans(scans.concat(...createdScans));
}}
getDataToExport={() =>
scans.map((scan) => ({
...scan,
arguments: JSON.stringify(scan.arguments)
}))
}
/>
{showModal && (
<div>
<Overlay />
<ModalContainer>
<Modal
actions={
<>
<Button
outline
type="button"
onClick={() => {
setShowModal(false);
}}
>
Cancel
</Button>
<Button
type="button"
onClick={() => {
deleteRow(selectedRow);
setShowModal(false);
}}
>
Delete
</Button>
</>
}
title={<h2>Delete scan?</h2>}
>
<p>
Are you sure you would like to delete the{' '}
<code>{scans[selectedRow].name}</code> scan?
</p>
</Modal>
</ModalContainer>
</div>
)}
</>
);
}
Example #6
Source File: columns.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
createColumns = (updateVulnerability: any) =>
[
{
Header: 'Vulnerability',
accessor: 'title',
Cell: ({ value, row }: CellProps<Vulnerability>) =>
row.original.cve ? (
<a
href={`https://nvd.nist.gov/vuln/detail/${row.original.cve}`}
target="_blank"
rel="noopener noreferrer"
>
{value} {extLink}
</a>
) : (
<p>{row.original.title}</p>
),
width: 800,
Filter: ColumnFilter
},
{
Header: 'Severity',
id: 'severity',
accessor: ({ severity, substate }) => (
<span
style={{
borderBottom: `6px solid ${getSeverityColor({
id: severity ?? ''
})}`,
width: '80px'
}}
// className={substate === 'unconfirmed' ? classes.severity : undefined}
>
{severity}
</span>
),
width: 100,
Filter: selectFilter(['Low', 'Medium', 'High', 'Critical', 'None'])
},
{
Header: 'KEV',
accessor: 'isKev',
Cell: ({ value, row }: CellProps<Vulnerability>) =>
value ? (
<a
href={`https://www.cisa.gov/known-exploited-vulnerabilities-catalog`}
target="_blank"
rel="noopener noreferrer"
>
Yes {extLink}
</a>
) : (
<p>No</p>
),
width: 50,
Filter: selectFilter([
{ value: 'true', label: 'Yes' },
{ value: 'false', label: 'No' }
])
},
{
Header: 'Domain',
id: 'domain',
accessor: ({ domain }) => (
<Link to={`/inventory/domain/${domain?.id}`}>{domain?.name}</Link>
),
width: 800,
Filter: ColumnFilter
},
{
Header: 'Product',
id: 'cpe',
accessor: ({ cpe, service }) => {
const product =
service &&
service.products.find(
(product) => cpe && product.cpe && cpe.includes(product.cpe)
);
if (product)
return product.name + (product.version ? ' ' + product.version : '');
else return cpe;
},
width: 100,
Filter: ColumnFilter
},
{
Header: 'Days Open',
id: 'createdAt',
accessor: ({ createdAt, actions = [] }) => {
// Calculates the total number of days a vulnerability has been open
let daysOpen = 0;
let lastOpenDate = createdAt;
let lastState = 'open';
actions.reverse();
for (const action of actions) {
if (action.state === 'closed' && lastState === 'open') {
daysOpen += differenceInCalendarDays(
parseISO(action.date),
parseISO(lastOpenDate)
);
lastState = 'closed';
} else if (action.state === 'open' && lastState === 'closed') {
lastOpenDate = action.date;
lastState = 'open';
}
}
if (lastState === 'open') {
daysOpen += differenceInCalendarDays(
new Date(),
parseISO(lastOpenDate)
);
}
return daysOpen;
},
disableFilters: true
},
{
Header: 'Status',
id: 'state',
width: 100,
maxWidth: 200,
accessor: 'state',
Filter: selectFilter([
'open',
'open (unconfirmed)',
'open (exploitable)',
'closed',
'closed (false positive)',
'closed (accepted risk)',
'closed (remediated)'
]),
Cell: ({ row }: CellProps<Vulnerability>) => (
<Dropdown
id="state-dropdown"
name="state-dropdown"
onChange={(e) => {
updateVulnerability(row.index, {
substate: e.target.value
});
}}
value={row.original.substate}
style={{ display: 'inline-block', width: '200px' }}
>
<option value="unconfirmed">Open (Unconfirmed)</option>
<option value="exploitable">Open (Exploitable)</option>
<option value="false-positive">Closed (False Positive)</option>
<option value="accepted-risk">Closed (Accepted Risk)</option>
<option value="remediated">Closed (Remediated)</option>
</Dropdown>
)
},
{
Header: 'Details',
Cell: ({ row }: CellProps<Vulnerability>) => (
<Link
to={`/inventory/vulnerability/${row.original.id}`}
style={{
fontSize: '14px',
cursor: 'pointer',
color: '#484D51',
textDecoration: 'none'
}}
>
DETAILS
</Link>
),
disableFilters: true
}
] as Column<Vulnerability>[]
Example #7
Source File: CalculatedPropertyTable.tsx From viewer-components-react with MIT License | 4 votes |
CalculatedPropertyTable = ({
iModelId,
mappingId,
groupId,
setSelectedCalculatedProperty,
setGroupModifyView,
onCalculatedPropertyModify,
isLoadingCalculatedProperties: isLoadingGroupProperties,
calculatedProperties,
refreshCalculatedProperties,
selectedCalculatedProperty,
}: CalculatedPropertyTableProps) => {
const apiContext = useContext(ApiContext);
const [
showCalculatedPropertyDeleteModal,
setShowCalculatedPropertyDeleteModal,
] = useState<boolean>(false);
const calculatedPropertiesColumns = useMemo(
() => [
{
Header: "Table",
columns: [
{
id: "propertyName",
Header: "Calculated Property",
accessor: "propertyName",
Cell: (value: CellProps<CalculatedPropertyType>) => (
<div
className='iui-anchor'
onClick={() => onCalculatedPropertyModify(value)}
>
{value.row.original.propertyName}
</div>
),
},
{
id: "dropdown",
Header: "",
width: 80,
Cell: (value: CellProps<CalculatedPropertyType>) => {
return (
<DropdownMenu
menuItems={(close: () => void) => [
<MenuItem
key={0}
onClick={() => onCalculatedPropertyModify(value)}
icon={<SvgEdit />}
>
Modify
</MenuItem>,
<MenuItem
key={1}
onClick={() => {
setSelectedCalculatedProperty(value.row.original);
setShowCalculatedPropertyDeleteModal(true);
close();
}}
icon={<SvgDelete />}
>
Remove
</MenuItem>,
]}
>
<IconButton styleType='borderless'>
<SvgMore
style={{
width: "16px",
height: "16px",
}}
/>
</IconButton>
</DropdownMenu>
);
},
},
],
},
],
[onCalculatedPropertyModify, setSelectedCalculatedProperty],
);
return (
<>
<Button
startIcon={<SvgAdd />}
styleType='high-visibility'
onClick={() => {
setGroupModifyView(PropertyMenuView.ADD_CALCULATED_PROPERTY);
}}
>
Add Calculated Property
</Button>
<Table<CalculatedPropertyType>
data={calculatedProperties}
density='extra-condensed'
columns={calculatedPropertiesColumns}
emptyTableContent='No Calculated Properties'
isSortable
isLoading={isLoadingGroupProperties}
/>
<DeleteModal
entityName={selectedCalculatedProperty?.propertyName ?? ""}
show={showCalculatedPropertyDeleteModal}
setShow={setShowCalculatedPropertyDeleteModal}
onDelete={async () => {
const reportingClientApi = new ReportingClient(apiContext.prefix);
await reportingClientApi.deleteCalculatedProperty(
apiContext.accessToken,
iModelId,
mappingId,
groupId,
selectedCalculatedProperty?.id ?? "",
);
}}
refresh={refreshCalculatedProperties}
/>
</>
);
}
Example #8
Source File: CustomCalculationTable.tsx From viewer-components-react with MIT License | 4 votes |
CustomCalculationTable = ({
iModelId,
mappingId,
groupId,
setSelectedCustomCalculation,
setGroupModifyView,
onCustomCalculationModify,
isLoadingCustomCalculations,
customCalculations,
refreshCustomCalculations,
selectedCustomCalculation,
}: CustomCalculationTableProps) => {
const apiContext = useContext(ApiContext);
const [
showCustomCalculationDeleteModal,
setShowCustomCalculationDeleteModal,
] = useState<boolean>(false);
const CustomCalculationsColumns = useMemo(
() => [
{
Header: "Table",
columns: [
{
id: "propertyName",
Header: "Custom Calculation",
accessor: "propertyName",
Cell: (value: CellProps<CustomCalculationType>) => (
<div
className='iui-anchor'
onClick={() => onCustomCalculationModify(value)}
>
{value.row.original.propertyName}
</div>
),
},
{
id: "formula",
Header: "Formula",
accessor: "formula",
},
{
id: "dropdown",
Header: "",
width: 80,
Cell: (value: CellProps<CustomCalculationType>) => {
return (
<DropdownMenu
menuItems={(close: () => void) => [
<MenuItem
key={0}
onClick={() => onCustomCalculationModify(value)}
icon={<SvgEdit />}
>
Modify
</MenuItem>,
<MenuItem
key={1}
onClick={() => {
setSelectedCustomCalculation(value.row.original);
setShowCustomCalculationDeleteModal(true);
close();
}}
icon={<SvgDelete />}
>
Remove
</MenuItem>,
]}
>
<IconButton styleType='borderless'>
<SvgMore
style={{
width: "16px",
height: "16px",
}}
/>
</IconButton>
</DropdownMenu>
);
},
},
],
},
],
[onCustomCalculationModify, setSelectedCustomCalculation],
);
return (
<>
<Button
startIcon={<SvgAdd />}
styleType='high-visibility'
onClick={() => {
setGroupModifyView(PropertyMenuView.ADD_CUSTOM_CALCULATION);
}}
>
Add Custom Calculation
</Button>
<Table<CustomCalculationType>
data={customCalculations}
density='extra-condensed'
columns={CustomCalculationsColumns}
emptyTableContent='No Custom Calculations'
isSortable
isLoading={isLoadingCustomCalculations}
/>
<DeleteModal
entityName={selectedCustomCalculation?.propertyName ?? ""}
show={showCustomCalculationDeleteModal}
setShow={setShowCustomCalculationDeleteModal}
onDelete={async () => {
const reportingClientApi = new ReportingClient(apiContext.prefix);
await reportingClientApi.deleteCustomCalculation(
apiContext.accessToken,
iModelId,
mappingId,
groupId,
selectedCustomCalculation?.id ?? "",
);
}}
refresh={refreshCustomCalculations}
/>
</>
);
}
Example #9
Source File: GroupPropertyTable.tsx From viewer-components-react with MIT License | 4 votes |
GroupPropertyTable = ({
iModelId,
mappingId,
groupId,
selectedGroupProperty,
onGroupPropertyModify,
setSelectedGroupProperty,
isLoadingGroupProperties,
groupProperties,
refreshGroupProperties,
setGroupModifyView,
}: GroupPropertyTableProps) => {
const apiContext = useContext(ApiContext);
const [showGroupPropertyDeleteModal, setShowGroupPropertyDeleteModal] =
useState<boolean>(false);
const groupPropertiesColumns = useMemo(
() => [
{
Header: "Table",
columns: [
{
id: "propertyName",
Header: "Property",
accessor: "propertyName",
Cell: (value: CellProps<GroupPropertyType>) => (
<div
className='iui-anchor'
onClick={() => onGroupPropertyModify(value)}
>
{value.row.original.propertyName}
</div>
),
},
{
id: "dropdown",
Header: "",
width: 80,
Cell: (value: CellProps<GroupPropertyType>) => {
return (
<DropdownMenu
menuItems={(close: () => void) => [
<MenuItem
key={0}
onClick={() => onGroupPropertyModify(value)}
icon={<SvgEdit />}
>
Modify
</MenuItem>,
<MenuItem
key={1}
onClick={() => {
setSelectedGroupProperty(value.row.original);
setShowGroupPropertyDeleteModal(true);
close();
}}
icon={<SvgDelete />}
>
Remove
</MenuItem>,
]}
>
<IconButton styleType='borderless'>
<SvgMore
style={{
width: "16px",
height: "16px",
}}
/>
</IconButton>
</DropdownMenu>
);
},
},
],
},
],
[onGroupPropertyModify, setSelectedGroupProperty],
);
return (
<>
<Button
startIcon={<SvgAdd />}
styleType='high-visibility'
onClick={() => {
setGroupModifyView(PropertyMenuView.ADD_GROUP_PROPERTY);
}}
>
Add Property
</Button>
<Table<GroupPropertyType>
data={groupProperties}
density='extra-condensed'
columns={groupPropertiesColumns}
emptyTableContent='No Group Properties'
isSortable
isLoading={isLoadingGroupProperties}
/>
<DeleteModal
entityName={selectedGroupProperty?.propertyName ?? ""}
show={showGroupPropertyDeleteModal}
setShow={setShowGroupPropertyDeleteModal}
onDelete={async () => {
const reportingClientApi = new ReportingClient(apiContext.prefix);
await reportingClientApi.deleteGroupProperty(
apiContext.accessToken,
iModelId,
mappingId,
groupId,
selectedGroupProperty?.id ?? "",
);
}}
refresh={refreshGroupProperties}
/>
</>
);
}
Example #10
Source File: Grouping.tsx From viewer-components-react with MIT License | 4 votes |
Groupings = ({ mapping, goBack }: GroupsTreeProps) => {
const iModelConnection = useActiveIModelConnection() as IModelConnection;
const apiContext = useContext(ApiContext);
const iModelId = useActiveIModelConnection()?.iModelId as string;
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [groupsView, setGroupsView] = useState<GroupsView>(GroupsView.GROUPS);
const [selectedGroup, setSelectedGroup] = useState<GroupType | undefined>(
undefined,
);
const hilitedElements = useRef<Map<string, string[]>>(new Map());
const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({});
const [isLoadingQuery, setLoadingQuery] = useState<boolean>(false);
const [groups, setGroups] = useState<Group[]>([]);
useEffect(() => {
void fetchGroups(setGroups, iModelId, mapping.id ?? "", setIsLoading, apiContext);
}, [apiContext, iModelId, mapping.id, setIsLoading]);
const refresh = useCallback(async () => {
setGroupsView(GroupsView.GROUPS);
setSelectedGroup(undefined);
setGroups([]);
await fetchGroups(setGroups, iModelId, mapping.id ?? "", setIsLoading, apiContext);
}, [apiContext, iModelId, mapping.id, setGroups]);
const addGroup = () => {
// TODO Retain selection in view without emphasizes. Goal is to make it so we can distinguish
// hilited elements from regular elements without emphasis due to it blocking selection. For now clearing
// selection.
clearEmphasizedElements();
setGroupsView(GroupsView.ADD);
};
const onModify = useCallback((value) => {
clearEmphasizedElements();
setSelectedGroup(value.row.original);
setGroupsView(GroupsView.MODIFYING);
}, []);
const openProperties = useCallback((value) => {
clearEmphasizedElements();
setSelectedGroup(value.row.original);
setGroupsView(GroupsView.PROPERTIES);
}, []);
const groupsColumns = useMemo(
() => [
{
Header: "Table",
columns: [
{
id: "groupName",
Header: "Group",
accessor: "groupName",
Cell: (value: CellProps<GroupType>) => (
<>
{isLoadingQuery ? (
value.row.original.groupName
) : (
<div
className='iui-anchor'
onClick={(e) => {
e.stopPropagation();
openProperties(value);
}}
>
{value.row.original.groupName}
</div>
)}
</>
),
},
{
id: "description",
Header: "Description",
accessor: "description",
},
{
id: "dropdown",
Header: "",
width: 80,
Cell: (value: CellProps<GroupType>) => {
return (
<div onClick={(e) => e.stopPropagation()}>
<DropdownMenu
disabled={isLoadingQuery}
menuItems={(close: () => void) => [
<MenuItem
key={0}
onClick={() => onModify(value)}
icon={<SvgEdit />}
>
Modify
</MenuItem>,
<MenuItem
key={1}
onClick={() => openProperties(value)}
icon={<SvgList />}
>
Properties
</MenuItem>,
<MenuItem
key={2}
onClick={() => {
setSelectedGroup(value.row.original);
setShowDeleteModal(true);
close();
}}
icon={<SvgDelete />}
>
Remove
</MenuItem>,
]}
>
<IconButton
disabled={isLoadingQuery}
styleType='borderless'
>
<SvgMore
style={{
width: "16px",
height: "16px",
}}
/>
</IconButton>
</DropdownMenu>
</div>
);
},
},
],
},
],
[isLoadingQuery, onModify, openProperties],
);
// Temp
const stringToColor = function (str: string) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let colour = "#";
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
colour += (`00${value.toString(16)}`).substr(-2);
}
return colour;
};
const onSelect = useCallback(
async (selectedData: GroupType[] | undefined) => {
clearEmphasizedElements();
if (selectedData && selectedData.length > 0) {
setLoadingQuery(true);
let allIds: string[] = [];
for (const row of selectedData) {
const query = row.query ?? "";
if (hilitedElements.current.has(query)) {
const hilitedIds = hilitedElements.current.get(query) ?? [];
visualizeElements(hilitedIds, stringToColor(row.id ?? ""));
allIds = allIds.concat(hilitedIds);
} else {
try {
const ids: string[] = await fetchIdsFromQuery(
query,
iModelConnection,
);
if (ids.length === 0) {
toaster.warning(`${row.groupName}'s query is valid but produced no results.`);
}
const hiliteIds = await visualizeElementsById(
ids,
stringToColor(row.id ?? ""),
iModelConnection,
);
hilitedElements.current.set(query, hiliteIds);
allIds = allIds.concat(ids);
} catch {
const index = groups.findIndex((group) => group.id === row.id);
setSelectedRows((rowIds) => {
const selectedRowIds = { ...rowIds };
delete selectedRowIds[index];
return selectedRowIds;
});
toaster.negative(`Could not load ${row.groupName}. Query could not be resolved.`);
}
}
}
await zoomToElements(allIds);
setLoadingQuery(false);
}
},
[iModelConnection, groups],
);
const controlledState = useCallback(
(state) => {
return {
...state,
selectedRowIds: { ...selectedRows },
};
},
[selectedRows],
);
const propertyMenuGoBack = useCallback(async () => {
clearEmphasizedElements();
setGroupsView(GroupsView.GROUPS);
await refresh();
}, [refresh]);
const tableStateReducer = (
newState: TableState,
action: ActionType,
_previousState: TableState,
instance?: TableInstance,
): TableState => {
switch (action.type) {
case actions.toggleRowSelected: {
const newSelectedRows = {
...selectedRows,
};
if (action.value) {
newSelectedRows[action.id] = true;
} else {
delete newSelectedRows[action.id];
}
setSelectedRows(newSelectedRows);
newState.selectedRowIds = newSelectedRows;
break;
}
case actions.toggleAllRowsSelected: {
if (!instance?.rowsById) {
break;
}
const newSelectedRows = {} as Record<string, boolean>;
if (action.value) {
Object.keys(instance.rowsById).forEach(
(id) => (newSelectedRows[id] = true),
);
}
setSelectedRows(newSelectedRows);
newState.selectedRowIds = newSelectedRows;
break;
}
default:
break;
}
return newState;
};
switch (groupsView) {
case GroupsView.ADD:
return (
<GroupAction
iModelId={iModelId}
mappingId={mapping.id ?? ""}
goBack={async () => {
clearEmphasizedElements();
setGroupsView(GroupsView.GROUPS);
await refresh();
}}
/>
);
case GroupsView.MODIFYING:
return selectedGroup ? (
<GroupAction
iModelId={iModelId}
mappingId={mapping.id ?? ""}
group={selectedGroup}
goBack={async () => {
clearEmphasizedElements();
setGroupsView(GroupsView.GROUPS);
await refresh();
}}
/>
) : null;
case GroupsView.PROPERTIES:
return selectedGroup ? (
<PropertyMenu
iModelId={iModelId}
mappingId={mapping.id ?? ""}
group={selectedGroup}
goBack={propertyMenuGoBack}
/>
) : null;
default:
return (
<>
<WidgetHeader
title={mapping.mappingName ?? ""}
disabled={isLoading || isLoadingQuery}
returnFn={async () => {
clearEmphasizedElements();
await goBack();
}}
/>
<div className='groups-container'>
<Button
startIcon={
isLoadingQuery ? <ProgressRadial size="small" indeterminate /> : <SvgAdd />
}
styleType='high-visibility'
disabled={isLoadingQuery}
onClick={() => addGroup()}
>
{isLoadingQuery ? "Loading Group(s)" : "Add Group"}
</Button>
<Table<GroupType>
data={groups}
density='extra-condensed'
columns={groupsColumns}
emptyTableContent='No Groups available.'
isSortable
isSelectable
onSelect={onSelect}
isLoading={isLoading}
isRowDisabled={() => isLoadingQuery}
stateReducer={tableStateReducer}
useControlledState={controlledState}
/>
</div>
<DeleteModal
entityName={selectedGroup?.groupName ?? ""}
show={showDeleteModal}
setShow={setShowDeleteModal}
onDelete={async () => {
const reportingClientApi = new ReportingClient(apiContext.prefix);
await reportingClientApi.deleteGroup(
apiContext.accessToken,
iModelId,
mapping.id ?? "",
selectedGroup?.id ?? "",
);
}}
refresh={refresh}
/>
</>
);
}
}
Example #11
Source File: Mapping.tsx From viewer-components-react with MIT License | 4 votes |
Mappings = () => {
const apiContext = useContext(ApiContext);
const iModelId = useActiveIModelConnection()?.iModelId as string;
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
const [showImportModal, setShowImportModal] = useState<boolean>(false);
const [showBlockingOverlay, setShowBlockingOverlay] = useState<boolean>(false);
const [mappingView, setMappingView] = useState<MappingView>(
MappingView.MAPPINGS
);
const [selectedMapping, setSelectedMapping] = useState<
Mapping | undefined
>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [mappings, setMappings] = useState<Mapping[]>([]);
useEffect(() => {
void fetchMappings(setMappings, iModelId, setIsLoading, apiContext);
}, [apiContext, iModelId, setIsLoading]);
useEffect(() => {
const removeListener =
Presentation.selection.selectionChange.addListener(onSelectionChanged);
return () => {
removeListener();
};
}, []);
const refresh = useCallback(async () => {
setMappingView(MappingView.MAPPINGS);
setSelectedMapping(undefined);
setMappings([]);
await fetchMappings(setMappings, iModelId, setIsLoading, apiContext);
}, [apiContext, iModelId, setMappings]);
const addMapping = async () => {
setMappingView(MappingView.ADDING);
};
const mappingsColumns = useMemo(
() => [
{
Header: "Table",
columns: [
{
id: "mappingName",
Header: "Mapping Name",
accessor: "mappingName",
Cell: (value: CellProps<{ mappingName: string }>) => (
<div
className="iui-anchor"
onClick={() => {
setSelectedMapping(value.row.original);
setMappingView(MappingView.GROUPS);
}}
>
{value.row.original.mappingName}
</div>
),
},
{
id: "description",
Header: "Description",
accessor: "description",
},
{
id: "dropdown",
Header: "",
width: 80,
Cell: (value: CellProps<MappingType>) => {
return (
<DropdownMenu
menuItems={(close: () => void) => [
<MenuItem
key={0}
onClick={() => {
setSelectedMapping(value.row.original);
setMappingView(MappingView.MODIFYING);
}}
icon={<SvgEdit />}
>
Modify
</MenuItem>,
<MenuItem
key={1}
onClick={async () => {
setSelectedMapping(value.row.original);
setShowBlockingOverlay(true);
close();
await toggleExtraction(apiContext, iModelId, value.row.original);
await refresh();
setShowBlockingOverlay(false);
}}
icon={<SvgProcess />}
>
{value.row.original.extractionEnabled ? "Disable extraction" : "Enable extraction"}
</MenuItem>,
<MenuItem
key={2}
onClick={() => {
setSelectedMapping(value.row.original);
setShowDeleteModal(true);
close();
}}
icon={<SvgDelete />}
>
Remove
</MenuItem>,
]}
>
<IconButton styleType="borderless">
<SvgMore
style={{
width: "16px",
height: "16px",
}}
/>
</IconButton>
</DropdownMenu>
);
},
},
],
},
],
[apiContext, iModelId, refresh]
);
switch (mappingView) {
case MappingView.ADDING:
return <MappingAction iModelId={iModelId} returnFn={refresh} />;
case MappingView.MODIFYING:
return (
<MappingAction
iModelId={iModelId}
mapping={selectedMapping}
returnFn={refresh}
/>
);
case MappingView.GROUPS:
return (
<Groupings
mapping={selectedMapping as Mapping}
goBack={refresh}
/>
);
default:
return (
<>
<BlockingOverlay isVisible={showBlockingOverlay} />
<WidgetHeader title="Mappings" />
<div className="mappings-container">
<div className="table-toolbar">
<Button
startIcon={<SvgAdd />}
onClick={async () => addMapping()}
styleType="high-visibility"
>
New
</Button>
<ButtonGroup onClick={() => setShowImportModal(true)}>
<IconButton title="Import Mappings">
<SvgImport />
</IconButton>
</ButtonGroup>
</div>
<Table<MappingType>
data={mappings}
density="extra-condensed"
columns={mappingsColumns}
emptyTableContent="No Mappings available."
isSortable
isLoading={isLoading}
/>
</div>
<DeleteModal
entityName={selectedMapping?.mappingName ?? ""}
show={showDeleteModal}
setShow={setShowDeleteModal}
onDelete={async () => {
const reportingClientApi = new ReportingClient(apiContext.prefix);
await reportingClientApi.deleteMapping(
apiContext.accessToken,
iModelId,
selectedMapping?.id ?? ""
);
}}
refresh={refresh}
/>
<MappingImportWizardModal
show={showImportModal}
setShow={setShowImportModal}
onFinish={refresh}
/>
</>
);
}
}
Example #12
Source File: PropertyMenu.tsx From viewer-components-react with MIT License | 4 votes |
PropertyMenu = ({
iModelId,
mappingId,
group,
goBack,
hideGroupProps = false,
hideCalculatedProps = false,
hideCustomCalculationProps = false,
}: PropertyModifyProps) => {
const groupId = group.id ?? "";
const apiContext = useContext(ApiContext);
const iModelConnection = useActiveIModelConnection() as IModelConnection;
const [propertyMenuView, setPropertyMenuView] = useState<PropertyMenuView>(
PropertyMenuView.DEFAULT,
);
const [selectedGroupProperty, setSelectedGroupProperty] = useState<
GroupPropertyType | undefined
>(undefined);
const [selectedCalculatedProperty, setSelectedCalculatedProperty] = useState<
CalculatedPropertyType | undefined
>(undefined);
const [selectedCustomCalculation, setSelectedCustomCalculation] = useState<
CustomCalculationType | undefined
>(undefined);
const [isInformationPanelOpen, setIsInformationPanelOpen] =
useState<boolean>(false);
const [resolvedHiliteIds, setResolvedHiliteIds] = useState<string[]>([]);
const [keySet, setKeySet] = useState<KeySet>();
const [isLoading, setIsLoading] = useState<boolean>(true);
const fetchGroupProperties = useMemo(
() => {
const reportingClientApi = new ReportingClient(apiContext.prefix);
return async () => reportingClientApi.getGroupProperties(apiContext.accessToken, iModelId, mappingId, groupId);
},
[apiContext, iModelId, mappingId, groupId],
);
const { isLoading: isLoadingGroupProperties, data: groupProperties, refreshData: refreshGroupProperties } =
useCombinedFetchRefresh<GroupPropertyType>(fetchGroupProperties);
const fetchCalculatedProperties = useMemo(
() => {
const reportingClientApi = new ReportingClient(apiContext.prefix);
return async () => reportingClientApi.getCalculatedProperties(apiContext.accessToken, iModelId, mappingId, groupId);
},
[apiContext, iModelId, mappingId, groupId],
);
const { isLoading: isLoadingCalculatedProperties, data: calculatedProperties, refreshData: refreshCalculatedProperties } =
useCombinedFetchRefresh<CalculatedPropertyType>(fetchCalculatedProperties);
const fetchCustomCalculations = useMemo(
() => {
const reportingClientApi = new ReportingClient(apiContext.prefix);
return async () => reportingClientApi.getCustomCalculations(apiContext.accessToken, iModelId, mappingId, groupId);
},
[apiContext, iModelId, mappingId, groupId],
);
const { isLoading: isLoadingCustomCalculations, data: customCalculations, refreshData: refreshCustomCalculations } =
useCombinedFetchRefresh<CustomCalculationType>(fetchCustomCalculations);
const properties = useMemo(() => convertToPropertyMap(groupProperties, calculatedProperties), [groupProperties, calculatedProperties]);
useEffect(() => {
const initialize = async () => {
try {
const ids = await fetchIdsFromQuery(group.query ?? "", iModelConnection);
if (ids.length === 0) {
toaster.warning("The query is valid but produced no results.");
await goBack();
}
const keys = await manufactureKeys(ids, iModelConnection);
setKeySet(keys);
Presentation.selection.clearSelection(
"GroupingMappingWidget",
iModelConnection,
);
clearEmphasizedElements();
const resolvedIds = await visualizeElementsByKeys(keys, "red");
await zoomToElements(resolvedIds);
setResolvedHiliteIds(resolvedIds);
setIsLoading(false);
} catch {
toaster.negative(`Could not load ${group.groupName}.`);
await goBack();
}
};
void initialize();
}, [iModelConnection, group.query, goBack, group.groupName]);
const onGroupPropertyModify = useCallback(
(value: CellProps<GroupPropertyType>) => {
setSelectedGroupProperty(value.row.original);
setPropertyMenuView(PropertyMenuView.MODIFY_GROUP_PROPERTY);
},
[],
);
const onCalculatedPropertyModify = useCallback(
(value: CellProps<CalculatedPropertyType>) => {
setSelectedCalculatedProperty(value.row.original);
setPropertyMenuView(PropertyMenuView.MODIFY_CALCULATED_PROPERTY);
},
[],
);
const onCustomCalculationModify = useCallback(
(value: CellProps<CustomCalculationType>) => {
setSelectedCustomCalculation(value.row.original);
setPropertyMenuView(PropertyMenuView.MODIFY_CUSTOM_CALCULATION);
},
[],
);
const groupPropertyReturn = useCallback(async (modified: boolean) => {
setPropertyMenuView(PropertyMenuView.DEFAULT);
modified && await refreshGroupProperties();
}, [refreshGroupProperties]);
const calculatedPropertyReturn = useCallback(async (modified: boolean) => {
visualizeElements(resolvedHiliteIds, "red");
await zoomToElements(resolvedHiliteIds);
setPropertyMenuView(PropertyMenuView.DEFAULT);
modified && await refreshCalculatedProperties();
}, [resolvedHiliteIds, refreshCalculatedProperties]);
const customCalculationReturn = useCallback(async (modified: boolean) => {
setPropertyMenuView(PropertyMenuView.DEFAULT);
modified && await refreshCustomCalculations();
}, [refreshCustomCalculations]);
if (isLoading) {
return (
<div className='loading-overlay'>
<Text>Loading Group</Text>
<ProgressRadial indeterminate />
<Text>Please wait...</Text>
</div>
);
}
switch (propertyMenuView) {
case PropertyMenuView.ADD_GROUP_PROPERTY:
return (
<GroupPropertyAction
iModelId={iModelId}
mappingId={mappingId}
groupId={groupId}
keySet={keySet ?? new KeySet()}
returnFn={groupPropertyReturn}
/>
);
case PropertyMenuView.MODIFY_GROUP_PROPERTY:
return (
<GroupPropertyAction
iModelId={iModelId}
mappingId={mappingId}
groupId={groupId}
keySet={keySet ?? new KeySet()}
groupPropertyId={selectedGroupProperty?.id ?? ""}
groupPropertyName={selectedGroupProperty?.propertyName ?? ""}
returnFn={groupPropertyReturn}
/>
);
case PropertyMenuView.ADD_CALCULATED_PROPERTY:
return (
<CalculatedPropertyAction
iModelId={iModelId}
mappingId={mappingId}
groupId={groupId}
ids={resolvedHiliteIds}
returnFn={calculatedPropertyReturn}
/>
);
case PropertyMenuView.MODIFY_CALCULATED_PROPERTY:
return (
<CalculatedPropertyAction
iModelId={iModelId}
mappingId={mappingId}
groupId={groupId}
property={selectedCalculatedProperty}
ids={resolvedHiliteIds}
returnFn={calculatedPropertyReturn}
/>
);
case PropertyMenuView.ADD_CUSTOM_CALCULATION:
return (
<CustomCalculationAction
iModelId={iModelId}
mappingId={mappingId}
groupId={groupId}
properties={properties}
returnFn={customCalculationReturn}
/>
);
case PropertyMenuView.MODIFY_CUSTOM_CALCULATION:
return (
<CustomCalculationAction
iModelId={iModelId}
mappingId={mappingId}
groupId={groupId}
properties={properties}
customCalculation={selectedCustomCalculation}
returnFn={customCalculationReturn}
/>
);
default:
return (
<InformationPanelWrapper className='property-menu-wrapper'>
<div className='property-header'>
<WidgetHeader
title={`${group.groupName ?? ""}`}
returnFn={goBack}
/>
<IconButton
styleType='borderless'
onClick={() => setIsInformationPanelOpen(true)}
>
<SvgProperties />
</IconButton>
</div>
<div className='property-menu-container'>
{!hideGroupProps && (
<div className='property-table'>
<GroupPropertyTable
iModelId={iModelId}
mappingId={mappingId}
groupId={groupId}
onGroupPropertyModify={onGroupPropertyModify}
setSelectedGroupProperty={setSelectedGroupProperty}
setGroupModifyView={setPropertyMenuView}
isLoadingGroupProperties={isLoadingGroupProperties}
groupProperties={groupProperties}
refreshGroupProperties={refreshGroupProperties}
selectedGroupProperty={selectedGroupProperty}
/>
</div>
)}
{!hideCalculatedProps && (
<div className='property-table'>
<CalculatedPropertyTable
iModelId={iModelId}
mappingId={mappingId}
groupId={groupId}
onCalculatedPropertyModify={onCalculatedPropertyModify}
setSelectedCalculatedProperty={setSelectedCalculatedProperty}
setGroupModifyView={setPropertyMenuView}
isLoadingCalculatedProperties={isLoadingCalculatedProperties}
calculatedProperties={calculatedProperties}
refreshCalculatedProperties={refreshCalculatedProperties}
selectedCalculatedProperty={selectedCalculatedProperty}
/>
</div>
)}
{!hideCustomCalculationProps && (
<div className='property-table'>
<CustomCalculationTable
iModelId={iModelId}
mappingId={mappingId}
groupId={groupId}
onCustomCalculationModify={onCustomCalculationModify}
setSelectedCustomCalculation={setSelectedCustomCalculation}
setGroupModifyView={setPropertyMenuView}
isLoadingCustomCalculations={isLoadingCustomCalculations}
customCalculations={customCalculations}
refreshCustomCalculations={refreshCustomCalculations}
selectedCustomCalculation={selectedCustomCalculation}
/>
</div>
)}
</div>
<InformationPanel
className='information-panel'
isOpen={isInformationPanelOpen}
>
<InformationPanelHeader
onClose={() => setIsInformationPanelOpen(false)}
>
<Text variant='subheading'>{`${group.groupName ?? ""
} Information`}</Text>
</InformationPanelHeader>
<InformationPanelBody>
<div className='information-body'>
<LabeledTextarea
label='Query'
rows={15}
readOnly
defaultValue={group.query ?? ""}
/>
</div>
</InformationPanelBody>
</InformationPanel>
</InformationPanelWrapper>
);
}
}