Skip to main content

Target type DashboardLayout

The custom widget can be rendered in Dashboard layout. The custom widget with target type 'DashboardLayout' is visible in mobile app.

Coding guidelines

  • pageContext The page context provided has following content
{
instanceType: "Instance type name for which the layout is configured",
typePluralName: "Instance type plural name",
typeSingularName: "Instance type singular name",
}
  • 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.

Typical use cases

  • Used in displaying custom widgets for the Dashboard layout. Role based visibility can be controlled by Dashboard layout configurations.
  • 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

  1. Here's an example of a Dashboard layout widget that:
    • fetches instances of a particular type using in-built useProtrakApi hook and a custom promise function
    • displays data in a tabular form
const lifecycleUrl = '/lifecycles/Patent Application';
const docketId = 'DocketId';
const patentInstanceName = 'patent';
const invTOPatent = 'InvTOPatent';
const lastStateName = 'Granted';
const docketIdColumn = { name: 'Docket Id', displayName: ' Docket Id ' };

function PatentAlertDashboard(pageContext) {
const { protrakUtils, protrakComponents } = React.useContext(customWidgetContext);
const { useProtrakApi } = protrakUtils;
const { Container, Spinner, ErrorModal } = protrakComponents;

const { state: responsePatents, run } = useProtrakApi({
requestConfig: getStatesPromise
});

if (responsePatents.isLoading || !responsePatents.data) {
return (
<Container>
<Spinner />
</Container>
);
}

if (!responsePatents.isLoading) {
if (responsePatents.isError) {
return (
<ErrorModal message="Error while fetching data..." />
);
}

if (
responsePatents.data &&
responsePatents.data.states &&
responsePatents.data.states.length > 0
) {
const states = responsePatents.data.states.filter(state => state.name !== lastStateName);
return (
<TabularData
columns={[docketIdColumn, ...states]}
/>
);
} else {
return <RenderTable />;
}
}
}

function TabularData({ columns }) {
const { protrakUtils, protrakComponents } = React.useContext(customWidgetContext);
const {
useProtrakApi,
differenceInDaysFromToday
} = protrakUtils;
const {
Container,
Spinner,
ErrorModal
} = protrakComponents;
const { state: patents, run } = useProtrakApi({
requestConfig: getPatentsPromise
});
if (patents.isLoading || !patents.data) {
return (
<Container>
<Spinner />
</Container>
);
}

if (!patents.isLoading) {
if (patents.isError) {
return (
<ErrorModal message="Error while fetching data..." />
);
}
if (patents.data && patents.data.items && patents.data.items.length > 0) {
let groupedData = [];
const alertPatents = patents.data.items.filter(patent => {
if (patent && patent.state && patent.state.name === 'Granted') {
return false;
}
if (patent && patent.activities && patent.activities.length > 0) {
const activitiesCount = patent.activities.length;
patent.activities.sort((a, b) => new Date(a.endTime) - new Date(b.endTime));
const activity = patent.activities[activitiesCount - 1];
if (differenceInDaysFromToday(activity.startTime) >= 30) {
return true;
}
}
return false;
});
alertPatents.forEach(patent => {
const docketIdAttr =
patent &&
patent.attributes.find(attribute => {
return attribute.name === docketId;
});
const inventionRefAttr =
patent &&
patent.attributes.find(attribute => {
return attribute.name === invTOPatent;
});
const activitiesCount = patent.activities.length;
const state = patent.activities[activitiesCount - 1].toState;
groupedData.push({
patentId: patent.id,
docketId: docketIdAttr,
invention: inventionRefAttr,
state: state
});
});
if (groupedData.length > 0) {
return <Data columns={columns} patents={groupedData} />;
} else {
return <RenderTable columns={columns} />;
}
} else {
return <RenderTable columns={columns} />;
}
}
}

const Data = ({ columns, patents }) => {
const pageSize = 10;
const cellStyle = {
border: '1px solid #dddddd',
textAlign: 'center',
padding: '8px',
minWidth: '5rem'
};
const [take, setTake] = React.useState(1);
const [isError, setIsError] = React.useState(false);
//check if next page is available and also if index out of bound
const trimmedPatents = patents.slice(
take * pageSize - pageSize,
take * pageSize
);
const totalNumberOfPages = Math.ceil(patents.length / pageSize);
const onChange = pageNumber => {
if (totalNumberOfPages >= pageNumber && pageNumber > 0) {
setIsError(false);
setTake(pageNumber);
} else {
setIsError(true);
}
};
return (
<RenderTable
columns={columns}
body={
<tbody>
{trimmedPatents.map(patent => {
return (
<tr key={patent.patentId}>
{columns.map(column => {
if (column.name === 'Docket Id') {
const appUrl = document.location.origin.includes(
'localhost'
)
? '/Protrak'
: '';
return (
<td key={patent.patentId + column} style={cellStyle}>
{patent.docketId && patent.invention ? (
<a
href={`${appUrl}/#view_${patent.patentId
}`}
target="_blank"
>
{patent.docketId.textValue}
</a>
) : (
'-'
)}
</td>
);
}
if (column.name === patent.state.name) {
return (
<td
key={patent.patentId + column}
style={{ ...cellStyle, backgroundColor: 'red' }}
/>
);
}
return (
<td
key={patent.patentId + column}
style={{ ...cellStyle }}
/>
);
})}
</tr>
);
})}
</tbody>
}
statusBar={
<div style={{ paddingTop: '1rem' }}>
{' '}
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button disabled={!(take > 1)} onClick={() => setTake(take - 1)}>
{' '}
&#8249;
</button>
<span style={{ padding: '0.5rem' }}>Page</span>{' '}
<input
type="number"
onChange={e => onChange(e.target.value)}
onBlur={e => {
const page = e.target.value ? Number(e.target.value) : 0;
if (page !== take) {
onChange(page);
}
}}
value={take}
style={{
border: isError ? '1px solid red' : '1px solid grey',
padding: '0.35rem 0.9rem',
width: '3rem'
}}
/>{' '}
<span style={{ padding: '0.5rem' }}>of {totalNumberOfPages}</span>
<button
disabled={!(take * pageSize < patents.length)}
onClick={() => setTake(take + 1)}
>
{' '}
&#8250;
</button>
</div>
</div>
}
/>
);
};

const RenderTable = ({ columns = [], body, statusBar }) => {
const { protrakUtils } = React.useContext(customWidgetContext);
const cellStyle = {
border: '1px solid #dddddd',
textAlign: 'left',
padding: '8px',
minWidth: '5rem'
};

const { getDate } = protrakUtils;

const date = new Date(getDate().toString());
const formattedDate =
date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear();
const message = `Today's Date ${formattedDate}`;
return (
<div style={{ overflow: 'auto' }}>
<div
style={{ display: 'flex', marginTop: '1rem', marginBottom: '0.5rem' }}
>
<div style={{ width: '100%' }}>
<i className="fa fa-info-circle" style={{ color: '#2383dc' }} />
{message}
</div>{' '}
<div style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<div
style={{
backgroundColor: 'red',
width: '1.5rem',
height: '1.5rem',
marginLeft: '1rem',
marginRight: '0.4rem'
}}
/>
<span style={{ marginTop: '0.2rem' }}>
Patent is in current state from the last 30 days
</span>
</div>
</div>
</div>
<table
style={{
border: '1px solid grey',
borderCollapse: 'collapse',
width: '100%'
}}
>
<thead>
<tr>
{columns.map(column => {
return (
<th key={column.name} style={cellStyle}>
{' '}
{column.displayName}
</th>
);
})}
</tr>
</thead>
{body}
</table>
{statusBar}
</div>
);
};

const getPatentsPromise = () => {
return {
endpoint: 'instances', config: {
method: 'GET',
params: {
instanceTypeName: patentInstanceName,
'attributes[0]': docketId,
'attributes[1]': invTOPatent,
Skip: 0,
Take: 999,
'ActivityQuery.Type': 1
}
}
};
};

const getStatesPromise = () => {
return {
endpoint: lifecycleUrl, config: {
method: 'GET'
}
};
};

Here's a preview of how this custom widget looks like.

target_type_dashboardlayout.png