Skip to content
175 changes: 139 additions & 36 deletions src/components/AggregationPanel/AggregationPanel.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import LoaderDots from 'components/LoaderDots/LoaderDots.react';
import React, { useEffect, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styles from './AggregationPanel.scss';
import Parse from 'parse';
import {
AudioElement,
ButtonElement,
Expand All @@ -19,8 +20,14 @@ const AggregationPanel = ({
errorAggregatedData,
showNote,
setSelectedObjectId,
selectedObjectId
selectedObjectId,
depth = 0,
cloudCodeFunction = null,
panelTitle = null,
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const [nestedData, setNestedData] = useState(null);
const [isLoadingNested, setIsLoadingNested] = useState(false);

useEffect(() => {
if (Object.keys(errorAggregatedData).length !== 0) {
Expand All @@ -30,54 +37,150 @@ const AggregationPanel = ({
}, [errorAggregatedData, setSelectedObjectId, setErrorAggregatedData]);

const isLoading = useMemo(() =>
selectedObjectId && isLoadingCloudFunction && showAggregatedData,
[selectedObjectId, isLoadingCloudFunction, showAggregatedData]
depth === 0 && selectedObjectId && isLoadingCloudFunction && showAggregatedData,
[depth, selectedObjectId, isLoadingCloudFunction, showAggregatedData]
);

const shouldShowAggregatedData = useMemo(() =>
selectedObjectId && showAggregatedData && Object.keys(data).length !== 0 && Object.keys(errorAggregatedData).length === 0, [selectedObjectId, showAggregatedData, data, errorAggregatedData]
depth === 0
? (selectedObjectId && showAggregatedData && Object.keys(data).length !== 0 && Object.keys(errorAggregatedData).length === 0)
: true,
[depth, selectedObjectId, showAggregatedData, data, errorAggregatedData]
);

const fetchNestedData = useCallback(async () => {
setIsLoadingNested(true);
try {
const params = { objectId: selectedObjectId };
const result = await Parse.Cloud.run(cloudCodeFunction, params);
if (result?.panel?.segments) {
setNestedData(result);
} else {
const errorMsg = 'Improper JSON format';
showNote(errorMsg, true);
}
} catch (error) {
const errorMsg = error.message;
showNote(errorMsg, true);
} finally {
setIsLoadingNested(false);
}
}, [cloudCodeFunction, selectedObjectId, showNote]);

const handleToggle = useCallback(async () => {
if (!isExpanded && !nestedData && cloudCodeFunction) {
fetchNestedData();
}
setIsExpanded(prev => !prev);
}, [isExpanded, nestedData, cloudCodeFunction, fetchNestedData]);

const handleRefresh = useCallback(() => {
setNestedData(null);
fetchNestedData();
}, [fetchNestedData]);

const renderSegmentContent = (segment, index) => (
<div key={index} className={styles.segmentContainer}>
<h2 className={styles.heading}>{segment.title}</h2>
<div className={styles.segmentItems}>
{segment.items.map((item, idx) => {
switch (item.type) {
case 'text':
return <TextElement key={idx} text={item.text} />;
case 'keyValue':
return <KeyValueElement key={idx} item={item} />;
case 'table':
return <TableElement key={idx} columns={item.columns} rows={item.rows} />;
case 'image':
return <ImageElement key={idx} url={item.url} />;
case 'video':
return <VideoElement key={idx} url={item.url} />;
case 'audio':
return <AudioElement key={idx} url={item.url} />;
case 'button':
return <ButtonElement key={idx} item={item} showNote={showNote} />;
case 'panel':
return (
<div key={idx} className={styles.nestedPanelContainer}>
<AggregationPanel
data={{}}
isLoadingCloudFunction={false}
showAggregatedData={true}
setErrorAggregatedData={setErrorAggregatedData}
errorAggregatedData={errorAggregatedData}
showNote={showNote}
setSelectedObjectId={setSelectedObjectId}
selectedObjectId={selectedObjectId}
depth={depth + 1}
cloudCodeFunction={item.cloudCodeFunction}
panelTitle={item.title}
/>
</div>
);
default:
return null;
}
})}
</div>
</div>
);

if (depth > 0) {
return (
<div className={styles.nestedPanel} style={{ marginLeft: `${depth * 20}px` }}>
<div className={styles.nestedPanelHeader}>
<button
onClick={handleToggle}
className={`${styles.expandButton} ${isExpanded ? styles.expanded : ''}`}
>
<span>{isExpanded ? '▼' : '▶'}</span>
<span>{panelTitle}</span>
</button>
{isExpanded && (
<button
onClick={handleRefresh}
className={styles.refreshButton}
disabled={isLoadingNested}
>
<span className={styles.refreshIcon}>↻</span>
</button>
)}
</div>
{isExpanded && (
<div className={styles.nestedPanelContent}>
{isLoadingNested ? (
<div className={styles.loader}>
<LoaderDots />
</div>
) : (
nestedData && nestedData.panel.segments.map((segment, index) =>
renderSegmentContent(segment, index)
)
)}
</div>
)}
</div>
);
}

return (
<>
<div className={styles.aggregationPanel}>
{isLoading ? (
<div className={styles.center}>
<LoaderDots />
</div>
) : shouldShowAggregatedData ? (
data.panel.segments.map((segment, index) => (
<div key={index}>
<h2 className={styles.heading}>{segment.title}</h2>
<div className={styles.segmentItems}>
{segment.items.map((item, idx) => {
switch (item.type) {
case 'text':
return <TextElement key={idx} text={item.text} />;
case 'keyValue':
return <KeyValueElement key={idx} item={item} />;
case 'table':
return <TableElement key={idx} columns={item.columns} rows={item.rows} />;
case 'image':
return <ImageElement key={idx} url={item.url} />;
case 'video':
return <VideoElement key={idx} url={item.url} />;
case 'audio':
return <AudioElement key={idx} url={item.url} />;
case 'button':
return <ButtonElement key={idx} item={item} showNote={showNote} />;
default:
return null;
}
})}
</div>
</div>
))
<div className={styles.mainContent}>
{data.panel.segments.map((segment, index) =>
renderSegmentContent(segment, index)
)}
</div>
) : (
<div className={styles.loading}>
No object selected.
<div className={styles.center}>
No object selected.
</div>
)}
</>
</div>
);
};

Expand Down
65 changes: 63 additions & 2 deletions src/components/AggregationPanel/AggregationPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,14 @@
text-align: center;
border-radius: 5px;
font-size: 14px;

&:hover,
&:focus {
background-color: $darkBlue;
}
}

.loading{
.loading {
height: 100%;
display: flex;
flex-direction: column;
Expand All @@ -96,4 +97,64 @@
top: 50%;
left: 50%;
@include transform(translate(-50%, -50%));
}
}

.nestedPanel {
margin: 8px 0;
transition: all 0.3s ease;
}

.nestedPanelHeader {
display: flex;
border-left: 1px solid #e3e3ea;
align-items: center;
gap: 8px;
}

.expandButton {
display: flex;
align-items: center;
gap: 8px;
background: none;
border: none;
padding: 8px;
cursor: pointer;
font-weight: 500;
color: #333;

&:hover {
background-color: #f5f5f5;
}

&.expanded {
font-weight: 600;
}
}

.refreshButton {
background: none;
border: none;
padding: 4px;
cursor: pointer;
color: #666;
font-size: 16px;

&:hover {
color: #333;
}

&:disabled {
color: #ccc;
cursor: not-allowed;
}
}

.nestedPanelContent {
padding: 8px 0;
}

.loader {
display: flex;
align-items: center;
justify-content: center;
}
4 changes: 2 additions & 2 deletions src/dashboard/Data/Browser/DataBrowser.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default class DataBrowser extends React.Component {
firstSelectedCell: null,
selectedData: [],
prevClassName: props.className,
panelWidth: 300,
panelWidth: 400,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, both of them were unintentional changes, i will revert them back in next commit.

isResizing: false,
maxWidth: window.innerWidth - 300,
showAggregatedData: true,
Expand Down Expand Up @@ -591,7 +591,7 @@ export default class DataBrowser extends React.Component {
<ResizableBox
width={this.state.panelWidth}
height={Infinity}
minConstraints={[100, Infinity]}
minConstraints={[400, Infinity]}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert if not required for this PR; the panel should be resizable to min. 100 px width.

maxConstraints={[this.state.maxWidth, Infinity]}
onResizeStart={this.handleResizeStart} // Handle start of resizing
onResizeStop={this.handleResizeStop} // Handle end of resizing
Expand Down
Loading