- Job Lifecycle Dashboard Component.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import {Badge, Card, Space, Table, Tag} from "antd";
|
||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import moment from "moment";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import axios from "axios";
|
||||
|
||||
const fortyFiveDaysAgo = () =>moment().subtract(45, 'days').toLocaleString();
|
||||
|
||||
export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardProps}) {
|
||||
const {t} = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [lifecycleData, setLifecycleData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function getLifecycleData() {
|
||||
if (data && data.job_lifecycle) {
|
||||
setLoading(true);
|
||||
const response = await axios.post("/job/lifecycle", {
|
||||
jobids: data.job_lifecycle.map(x => x.id),
|
||||
statuses: bodyshop.md_order_statuses
|
||||
});
|
||||
setLifecycleData(response.data.durations);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
getLifecycleData().catch(e => {
|
||||
console.error(`Error in getLifecycleData: ${e}`);
|
||||
})
|
||||
}, [data]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t('job_lifecycle.columns.status'),
|
||||
dataIndex: 'status',
|
||||
bgColor: 'red',
|
||||
key: 'status',
|
||||
render: (text, record) => {
|
||||
return <Tag color={record.color}>{record.status}</Tag>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('job_lifecycle.columns.human_readable'),
|
||||
dataIndex: 'humanReadable',
|
||||
key: 'humanReadable',
|
||||
},
|
||||
{
|
||||
title: t('job_lifecycle.columns.status_count'),
|
||||
key: 'statusCount',
|
||||
render: (text, record) => {
|
||||
return lifecycleData.statusCounts[record.status];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('job_lifecycle.columns.percentage'),
|
||||
dataIndex: 'percentage',
|
||||
key: 'percentage',
|
||||
render: (text, record) => {
|
||||
return record.percentage.toFixed(2)+'%';
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
if (!data.job_lifecycle || !lifecycleData) return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const extra = `${t('job_lifecycle.content.calculated_based_on')} ${lifecycleData.jobs} ${t('job_lifecycle.content.jobs_in_since')} ${fortyFiveDaysAgo()}`
|
||||
|
||||
return (
|
||||
<Card title={t("job_lifecycle.titles.dashboard")} {...cardProps}>
|
||||
<LoadingSkeleton loading={loading}>
|
||||
<div style={{overflow: 'scroll', height: "100%"}}>
|
||||
<div id="bar-container" style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '100px',
|
||||
textAlign: 'center',
|
||||
borderRadius: '5px',
|
||||
borderWidth: '5px',
|
||||
borderStyle: 'solid',
|
||||
borderColor: '#f0f2f5',
|
||||
margin: 0,
|
||||
padding: 0
|
||||
}}>
|
||||
{lifecycleData.summations.map((key, index, array) => {
|
||||
const isFirst = index === 0;
|
||||
const isLast = index === array.length - 1;
|
||||
return (
|
||||
<div key={key.status} style={{
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
|
||||
borderTop: '1px solid #f0f2f5',
|
||||
borderBottom: '1px solid #f0f2f5',
|
||||
borderLeft: isFirst ? '1px solid #f0f2f5' : undefined,
|
||||
borderRight: isLast ? '1px solid #f0f2f5' : undefined,
|
||||
|
||||
backgroundColor: key.color,
|
||||
width: `${key.percentage}%`
|
||||
}}
|
||||
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
>
|
||||
|
||||
{key.percentage > 15 ?
|
||||
<>
|
||||
<div>{key.roundedPercentage}</div>
|
||||
<div style={{
|
||||
backgroundColor: '#f0f2f5',
|
||||
borderRadius: '5px',
|
||||
paddingRight: '2px',
|
||||
paddingLeft: '2px',
|
||||
fontSize: '0.8rem',
|
||||
}}>
|
||||
{key.status}
|
||||
</div>
|
||||
</>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Card extra={extra} type='inner' title={t('job_lifecycle.content.legend_title')}
|
||||
style={{marginTop: '10px'}}>
|
||||
<div>
|
||||
{lifecycleData.summations.map((key) => (
|
||||
<Tag color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
|
||||
<div
|
||||
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
style={{
|
||||
backgroundColor: '#f0f2f5',
|
||||
color: '#000',
|
||||
padding: '4px',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
{key.status} [{lifecycleData.statusCounts[key.status]}] ({key.roundedPercentage})
|
||||
</div>
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card style={{marginTop: "5px"}} type='inner' title={t("job_lifecycle.titles.top_durations")}>
|
||||
<Table size="small" pagination={false} columns={columns} dataSource={ lifecycleData.summations.sort((a, b) => b.value - a.value).slice(0, 3)}/>
|
||||
</Card>
|
||||
</div>
|
||||
</LoadingSkeleton>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const JobLifecycleDashboardGQL = `
|
||||
job_lifecycle: jobs(where: {
|
||||
actual_in: {
|
||||
_gte: "${moment().subtract(45, 'days').toISOString()}"
|
||||
}
|
||||
}) {
|
||||
id
|
||||
actual_in
|
||||
} `;
|
||||
Reference in New Issue
Block a user