Skip to main content

Target type ReportLayout

The custom widget can be rendered in Report layout As reports are not visible in mobile app, custom widget with target type 'ReportLayout' is also not visible.

Coding guidelines

  • pageContext The page context provided has following content
{ 
data: manipulated data from totalData according to configurations & required to show the reports in particular format,
config: configurations in Report Layout from admin app,
filters: object {columnName, columnValue, filterValue} on drilldown popup,
instanceTypeName: string value,
onClick: function called when clicked on any value in reports,
onSortApplied: function,
setShowDrilldown: function to update showDrilldown value,
showDrilldown: boolean value,
sortState: {sortedBy, isDescending},
states: object of the states of an instance type,
totalData: array,
typePluralName,
}
  • Along with this, pageContext contains few more properties that are common to all widget types. For details check: common pageContext
  • Any API calls made from the widget will use the logged-in users' context.
  • While configuring Report layout custom widget, custom program needs to be configured as well.
    • Custom Program is used for data manipulations.
    • Custom widget is used for data visualization in reports.

Typical use cases

  • Used in displaying custom widgets for the Report layout.
  • Role based visibility can be controlled by Report layout configurations.
  • Validity can be controlled by configurations.
  • Parent filters, state filters & attribute filters are configurable from Report layout.
  • Typically used to show non-standard data visualization or user interactions that require data fetching or update requirements with additional business logic or validations.

Sample Code

Here's an example of a home layout widget that:

  • gets data from pageContext & showed in tabular form
  • ExportToCsv button is displayed to download the data using built-in 'ExportCSVButton' component
  • drill down is dispalyed using built-in 'ShowDrillDownData' component.
function round(num, n) {
const d = Math.pow(10, n);
return Math.round((num + Number.EPSILON) * d) / d;
}

const ELEMENT_TYPE = 'ElementType';
const LEVEL = 'Level';
const BUILDING = 'Building';

const columns = [
{ key: ELEMENT_TYPE, label: ELEMENT_TYPE },
{ key: LEVEL, label: LEVEL },
{ key: BUILDING, label: BUILDING },
{ key: 'Total Nos', type: 'number', label: 'total Nos' },
{ key: 'Total m3', type: 'number', label: 'total m3' },
{ key: 'Drawing Issued Nos', type: 'number', label: 'drawing Issued Nos' },
{ key: 'Drawing Balance Nos', type: 'number', label: 'drawing Balance Nos' },
{ key: 'Drawing ISSUED %', type: '%', label: 'drawing ISSUED %' },
{
key: 'drawing Available To Cast',
type: 'number',
label: 'drawing Available To Cast'
},
{
key: 'Production Casted Nos',
type: 'number',
label: 'production Casted Nos'
},
{
key: 'Production Casted Cum',
type: 'number',
label: 'production Casted Cum'
},
{ key: 'Casted %', type: '%', label: 'casted %' },
{
key: 'Production Balance Nos',
type: 'number',
label: 'production Balance Nos'
},
{
key: 'Production Balance Cum',
type: 'number',
label: 'production Balance Cum'
},
{ key: 'DELIVERED Nos', type: 'number', label: 'delivered Nos' },
{ key: 'DELIVERED Cum', type: 'number', label: 'delivered Cum' },
{ key: 'DELIVERED %', type: '%', label: 'delivered %' },
{ key: 'Stock Nos', type: 'number', label: 'stock Nos' },
{ key: 'Stock Cum', type: 'number', label: 'stock Cum' },
{ key: 'ERECTED Nos', type: 'number', label: 'erected Nos' },
{ key: 'ERECTED Cum', type: 'number', label: 'erected Cum' },
{ key: 'ERECTED %', type: '%', label: 'erected %' },
{ key: 'Stock At Site Nos', type: 'number', label: 'stock At Site Nos' },
{ key: 'Stock At Site Cum', type: 'number', label: 'stock At Site Cum' }
];

function ProjectSummaryCustomReport(pageContext) {
return <RenderTable pageContext={pageContext} />;
}

const RenderTable = ({ pageContext }) => {
const { protrakComponents } = React.useContext(customWidgetContext);
const {
data,
config,
showDrilldown,
onClick,
setShowDrilldown,
totalData,
filters,
instanceType,
sortState,
onSortApplied,
typePluralName,
states
} = pageContext;
const aggregator = config.group.attribute.attributeName;
const { Box, Button, ButtonEnums, ExportCSVButton, ShowDrillDownData } =
protrakComponents;
const GRAND_TOTAL = 'Grand Total';
const grandTotal = data.filter((item) => item.groupName === GRAND_TOTAL)[0];
const accumulator = {};
data.forEach((row) => {
columns.forEach((column) => {
if (row.groupName !== GRAND_TOTAL) {
if (column.type === '%') {
accumulator[column.key] =
(accumulator[column.key] ? accumulator[column.key] : 0) +
row[column.key];
}
}
});
});
Object.keys(accumulator).forEach((key) => {
grandTotal[key] = accumulator[key] / (data.length - 1);
});

data.forEach((row) => {
if (row.groupName !== GRAND_TOTAL) {
const groupName = row.groupName;
const arr = groupName.split('|');
row[ELEMENT_TYPE] = arr && arr.length === 3 ? arr[0].trim() : '-';
row[LEVEL] = arr && arr.length === 3 ? arr[1].trim() : '-';
row[BUILDING] = arr && arr.length === 3 ? arr[2].trim() : '-';
}
if (row.groupName === GRAND_TOTAL) {
row[ELEMENT_TYPE] = GRAND_TOTAL;
}
});

const cellStyle = {
border: '1px solid #dddddd',
textAlign: 'left',
padding: '8px',
minWidth: '2rem'
};

const cellStyle1 = {
border: '1px solid #dddddd',
textAlign: 'left',
padding: '8px',
minWidth: '2rem',
fontWeight: 'bold',
color: 'rgb(150,54,52)'
};

const cellStyleForCol1 = {
border: '1px solid #dddddd',
textAlign: 'center',
padding: '8px',
minWidth: '2rem',
background: 'rgb(230,184,183)'
};

const cellStyleForCol2 = {
border: '1px solid #dddddd',
textAlign: 'center',
padding: '8px',
minWidth: '2rem',
background: 'rgb(204,192,218)'
};

const cellStyleForCol3 = {
border: '1px solid #dddddd',
textAlign: 'center',
padding: '8px',
minWidth: '2rem',
background: 'rgb(196,215,155)'
};

const cellStyleForCol4 = {
border: '1px solid #dddddd',
textAlign: 'center',
padding: '8px',
minWidth: '2rem',
background: 'rgb(252,213,180)'
};

const cellStyleForCol5 = {
border: '1px solid #dddddd',
textAlign: 'center',
padding: '8px',
minWidth: '2rem',
background: 'rgb(141,180,226)'
};

return (
<Box display="flex" direction="column">
<Box alignSelf="baseline">
<ExportCSVButton
data={data}
headers={columns}
filename={typePluralName + '-' + config.name}
attributeFieldss={config.fields}
/>
</Box>
{ShowDrillDownData(
showDrilldown,
totalData,
config,
filters,
instanceType,
setShowDrilldown,
sortState,
onSortApplied,
typePluralName,
states
)}
<Box direction="Column" height="calc(100vh - 14rem)" overflow="auto">
<table
style={{
border: '1px solid grey',
fontFamily: 'inherit',
borderCollapse: 'collapse',
width: '100%'
}}
>
<thead style={{ position: 'sticky', top: '0' }}>
<tr>
<th rowspan="2" style={cellStyleForCol1}>
Element Type
</th>
<th rowspan="2" style={cellStyleForCol1}>
Floor
</th>
<th rowspan="2" style={cellStyleForCol1}>
Building
</th>
<th colspan="2" style={cellStyleForCol1}>
Total Qty
</th>
<th colspan="3" style={cellStyleForCol2}>
Drawings
</th>
<th rowspan="2" style={cellStyleForCol2}>
Dwg Available to cast
</th>
<th colspan="5" style={cellStyleForCol3}>
Production
</th>
<th colspan="5" style={cellStyleForCol4}>
Delivery and Stock
</th>
<th colspan="5" style={cellStyleForCol5}>
Erected and Stock at Site
</th>
</tr>
<tr>
<th style={cellStyleForCol1}> Nos </th>
<th style={cellStyleForCol1}> m3 </th>
<th style={cellStyleForCol2}> Issued No.s </th>
<th style={cellStyleForCol2}> Balance No.s </th>
<th style={cellStyleForCol2}> Issued in %</th>
<th style={cellStyleForCol3}> Casted No. </th>
<th style={cellStyleForCol3}> Casted cum.</th>
<th style={cellStyleForCol3}> Casted %</th>
<th style={cellStyleForCol3}> Balance No.s </th>
<th style={cellStyleForCol3}> Balance cum </th>
<th style={cellStyleForCol4}> Delievered Nos </th>
<th style={cellStyleForCol4}> Delievered Cum </th>
<th style={cellStyleForCol4}> Delivered %</th>
<th style={cellStyleForCol4}> Stock Nos </th>
<th style={cellStyleForCol4}> Stock Cum </th>
<th style={cellStyleForCol5}> Erected Nos </th>
<th style={cellStyleForCol5}> Erected Cum </th>
<th style={cellStyleForCol5}> Erected %</th>
<th style={cellStyleForCol5}> Stock Nos </th>
<th style={cellStyleForCol5}> Stock Cum </th>
</tr>
</thead>

<tbody>
{data.map((row) => {
return (
<tr key={row.id}>
{columns.map((column) => {
let value = row[column.key];
if (column.type) {
value = round(value, 2);
}
if (column.key.includes('%')) {
value += '%';
}
return column.key === ELEMENT_TYPE ||
column.key === LEVEL ||
column.key === BUILDING ? (
<td key={row.id + column.key} style={cellStyle1}>
{value}
</td>
) : (
<td key={row.id + column.key} style={cellStyle}>
<Button
onClick={() =>
onClick({
filterValue: row[aggregator],
columnValue: row[column.key],
columnName: column.key
})
}
appearance={ButtonEnums.Appearance.Link}
text={value}
/>
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</Box>
</Box>
);
};