- Finish department cycle times.

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-01-26 16:09:46 -05:00
parent 89224e871c
commit 120a8a4576
7 changed files with 47735 additions and 71 deletions

View File

@@ -5,6 +5,7 @@ import {Badge, Card, Space, Table, Tag} from 'antd';
import {gql, useQuery} from "@apollo/client";
import {DateTimeFormatterFunction} from "../../utils/DateFormatter";
import {isEmpty} from "lodash";
import {useTranslation} from "react-i18next";
require('./job-lifecycle.styles.scss');
@@ -12,8 +13,8 @@ require('./job-lifecycle.styles.scss');
export function JobLifecycleComponent({job, statuses, ...rest}) {
const [loading, setLoading] = useState(true);
const [lifecycleData, setLifecycleData] = useState(null);
const { t } = useTranslation(); // Used for tracking external state changes.
// Used for tracking external state changes.
const {data} = useQuery(gql`
query get_job_test($id: uuid!){
jobs_by_pk(id:$id){
@@ -43,7 +44,7 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
const data = response.data.transition[job.id];
setLifecycleData(data);
} catch (err) {
console.error(`Error getting Job Lifecycle Data: ${err.message}`);
console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`);
} finally {
setLoading(false);
}
@@ -53,30 +54,30 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
useEffect(() => {
if (!data) return;
setTimeout(() => {
getLifecycleData().catch(err => console.error(`Error getting Job Lifecycle Data: ${err.message}`));
}, 1000);
getLifecycleData().catch(err => console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`));
}, 500);
}, [data, getLifecycleData]);
const columns = [
{
title: 'Value',
title: t('job_lifecycle.columns.value'),
dataIndex: 'value',
key: 'value',
},
{
title: 'Start',
title: t('job_lifecycle.columns.start'),
dataIndex: 'start',
key: 'start',
render: (text) => DateTimeFormatterFunction(text),
sorter: (a, b) => moment(a.start).unix() - moment(b.start).unix(),
},
{
title: 'Relative Start',
title: t('job_lifecycle.columns.relative_start'),
dataIndex: 'start_readable',
key: 'start_readable',
},
{
title: 'End',
title: t('job_lifecycle.columns.end'),
dataIndex: 'end',
key: 'end',
sorter: (a, b) => {
@@ -88,33 +89,23 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
}
return moment(a.end).unix() - moment(b.end).unix();
},
render: (text) => isEmpty(text) ? 'N/A' : DateTimeFormatterFunction(text)
render: (text) => isEmpty(text) ? t('job_lifecycle.content.not_available') : DateTimeFormatterFunction(text)
},
{
title: 'Relative End',
title: t('job_lifecycle.columns.relative_end'),
dataIndex: 'end_readable',
key: 'end_readable',
},
{
title: 'Duration',
title: t('job_lifecycle.columns.duration'),
dataIndex: 'duration_readable',
key: 'duration_readable',
sorter: (a, b) => a.duration - b.duration,
},
];
useEffect(() => {
console.log('Statuses');
console.dir(statuses, {depth: null});
}, [statuses]);
// useEffect(() => {
// console.log('LifeCycle Data');
// console.dir(lifecycleData, {depth: null})
// }, [lifecycleData]);
return (
<Card loading={loading} title='Job Lifecycle Component'>
<Card loading={loading} title={t('job_lifecycle.content.title')}>
{!loading ? (
lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? (
<Space direction='vertical' style={{width: '100%'}}>
@@ -123,7 +114,7 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
title={(
<Space direction='horizontal' size='small'>
<Badge status='processing' count={lifecycleData.durations.totalStatuses}/>
Historical Status Durations
{t('job_lifecycle.content.title_durations')}
</Space>
)}
@@ -165,22 +156,26 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
>
<div>{key.roundedPercentage}</div>
{key.percentage > 5 ?
<div style={{
backgroundColor: '#f0f2f5',
borderRadius: '5px',
paddingRight: '2px',
paddingLeft: '2px',
fontSize: '0.8rem',
}}>
{key.status}
</div> : null}
<>
<div>{key.roundedPercentage}</div>
<div style={{
backgroundColor: '#f0f2f5',
borderRadius: '5px',
paddingRight: '2px',
paddingLeft: '2px',
fontSize: '0.8rem',
}}>
{key.status}
</div>
</>
: null}
</div>
);
})}
</div>
<Card type='inner' title='Legend' style={{marginTop: '10px'}}>
<Card type='inner' title={t('job_lifecycle.content.legend_title')} style={{marginTop: '10px'}}>
<div>
{lifecycleData.durations.summations.map((key) => (
<Tag color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
@@ -200,15 +195,21 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
</div>
</Card>
<Card style={{marginTop: '10px'}}>
<span
style={{fontWeight: 'bold'}}>Previous Status Accumulated Time:</span> {lifecycleData.durations.humanReadableTotal}
<ul>
<li>
<span style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.previous_status_accumulated_time')}:</span> {lifecycleData.durations.humanReadableTotal}
</li>
<li>
<span style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.current_status_accumulated_time')} ({lifecycleData.lifecycle[0].value}):</span> {lifecycleData.durations.totalCurrentStatusDuration.humanReadable}
</li>
</ul>
</Card>
</Card>
<Card type='inner' title={(
<>
<Space direction="horizontal" size="small">
<Badge status='processing' count={lifecycleData.lifecycle.length}/>
Transitions
{t('job_lifecycle.content.title_transitions')}
</Space>
</>
)}>
@@ -217,12 +218,12 @@ export function JobLifecycleComponent({job, statuses, ...rest}) {
</Space>
) : (
<Card type='inner' style={{textAlign: 'center'}}>
There is currently no lifecycle data for this job.
{t('job_lifecycle.content.data_unavailable')}
</Card>
)
) : (
<Card type='inner' title='Loading'>
Loading Job Timelines....
<Card type='inner' title={t('job_lifecycle.content.title_loading')}>
{t('job_lifecycle.content.loading')}
</Card>
)}
</Card>

View File

@@ -336,7 +336,7 @@ export function JobsDetailPage({
</Tabs.TabPane>
<Tabs.TabPane
forceRender
tab={<span><BarsOutlined />Lifecycle</span>}
tab={<span><BarsOutlined />{t('menus.jobsdetail.lifecycle')}</span>}
key="lifecycle"
>
<JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses}/>

View File

@@ -113,11 +113,11 @@
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
"jobinvoiced": "Job has been invoiced.",
"jobconverted": "Job converted and assigned number {{ro_number}}.",
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
"jobimported": "Job imported.",
"jobinproductionchange": "Job production status set to {{inproduction}}",
"jobinvoiced": "Job has been invoiced.",
"jobioucreated": "IOU Created.",
"jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.",
"jobnoteadded": "Note added to Job.",
@@ -255,7 +255,6 @@
"saving": "Error encountered while saving. {{message}}"
},
"fields": {
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
"address1": "Address 1",
"address2": "Address 2",
"appt_alt_transport": "Appointment Alternative Transportation Options",
@@ -332,6 +331,9 @@
"md_ded_notes": "Deductible Notes",
"md_email_cc": "Auto Email CC: $t(printcenter.subjects.jobs.{{template}})",
"md_from_emails": "Additional From Emails",
"md_functionality_toggles": {
"parts_queue_toggle": "Auto Add Imported/Supplemented Jobs to Parts Queue"
},
"md_hour_split": {
"paint": "Paint Hour Split",
"prep": "Prep Hour Split"
@@ -354,9 +356,6 @@
},
"md_payment_types": "Payment Types",
"md_referral_sources": "Referral Sources",
"md_functionality_toggles": {
"parts_queue_toggle": "Auto Add Imported/Supplemented Jobs to Parts Queue"
},
"md_tasks_presets": {
"hourstype": "",
"memo": "",
@@ -474,6 +473,7 @@
"editaccess": "Users -> Edit access"
}
},
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
"responsibilitycenter": "Responsibility Center",
"responsibilitycenter_accountdesc": "Account Description",
"responsibilitycenter_accountitem": "Item",
@@ -815,6 +815,10 @@
"usage": "Usage",
"vehicle": "Vehicle Description"
},
"readiness": {
"notready": "Not Ready",
"ready": "Ready"
},
"status": {
"in": "Available",
"inservice": "In Service",
@@ -824,10 +828,6 @@
},
"successes": {
"saved": "Courtesy Car saved successfully."
},
"readiness": {
"notready": "Not Ready",
"ready": "Ready"
}
},
"csi": {
@@ -1214,6 +1214,31 @@
"updated": "Inventory line updated."
}
},
"job_lifecycle": {
"columns": {
"duration": "Duration",
"end": "End",
"relative_end": "Relative End",
"relative_start": "Relative Start",
"start": "Start",
"value": "Value"
},
"content": {
"current_status_accumulated_time": "Current Status Accumulated Time",
"data_unavailable": " There is currently no Lifecycle data for this Job.",
"legend_title": "Legend",
"loading": "Loading Job Timelines....",
"not_available": "N/A",
"previous_status_accumulated_time": "Previous Status Accumulated Time",
"title": "Job Lifecycle Component",
"title_durations": "Historical Status Duration's",
"title_loading": "Loading",
"title_transitions": "Transitions"
},
"errors": {
"fetch": "Error getting Job Lifecycle Data"
}
},
"job_payments": {
"buttons": {
"goback": "Go Back",
@@ -2014,6 +2039,7 @@
"general": "General",
"insurance": "Insurance Information",
"labor": "Labor",
"lifecycle": "Lifecycle",
"partssublet": "Parts & Bills",
"rates": "Rates",
"repairdata": "Repair Data",
@@ -2922,8 +2948,8 @@
"shop-templates": "Shop Templates | $t(titles.app)",
"shop_vendors": "Vendors | $t(titles.app)",
"techconsole": "Technician Console | $t(titles.app)",
"techjoblookup": "Technician Job Lookup | $t(titles.app)",
"techjobclock": "Technician Job Clock | $t(titles.app)",
"techjoblookup": "Technician Job Lookup | $t(titles.app)",
"techshiftclock": "Technician Shift Clock | $t(titles.app)",
"temporarydocs": "Temporary Documents | $t(titles.app)",
"timetickets": "Time Tickets | $t(titles.app)",

View File

@@ -113,11 +113,11 @@
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobinvoiced": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
"jobinproductionchange": "",
"jobinvoiced": "",
"jobioucreated": "",
"jobmodifylbradj": "",
"jobnoteadded": "",
@@ -255,13 +255,9 @@
"saving": ""
},
"fields": {
"ReceivableCustomField": "",
"address1": "",
"address2": "",
"appt_alt_transport": "",
"md_functionality_toggles": {
"parts_queue_toggle": ""
},
"appt_colors": {
"color": "",
"label": ""
@@ -335,6 +331,9 @@
"md_ded_notes": "",
"md_email_cc": "",
"md_from_emails": "",
"md_functionality_toggles": {
"parts_queue_toggle": ""
},
"md_hour_split": {
"paint": "",
"prep": ""
@@ -474,6 +473,7 @@
"editaccess": ""
}
},
"ReceivableCustomField": "",
"responsibilitycenter": "",
"responsibilitycenter_accountdesc": "",
"responsibilitycenter_accountitem": "",
@@ -815,6 +815,10 @@
"usage": "",
"vehicle": ""
},
"readiness": {
"notready": "",
"ready": ""
},
"status": {
"in": "",
"inservice": "",
@@ -824,10 +828,6 @@
},
"successes": {
"saved": ""
},
"readiness": {
"notready": "",
"ready": ""
}
},
"csi": {
@@ -1214,6 +1214,31 @@
"updated": ""
}
},
"job_lifecycle": {
"columns": {
"duration": "",
"end": "",
"relative_end": "",
"relative_start": "",
"start": "",
"value": ""
},
"content": {
"current_status_accumulated_time": "",
"data_unavailable": "",
"legend_title": "",
"loading": "",
"not_available": "",
"previous_status_accumulated_time": "",
"title": "",
"title_durations": "",
"title_loading": "",
"title_transitions": ""
},
"errors": {
"fetch": "Error al obtener los datos del ciclo de vida del trabajo"
}
},
"job_payments": {
"buttons": {
"goback": "",
@@ -2014,6 +2039,7 @@
"general": "",
"insurance": "",
"labor": "Labor",
"lifecycle": "",
"partssublet": "Piezas / Subarrendamiento",
"rates": "",
"repairdata": "Datos de reparación",
@@ -2922,8 +2948,8 @@
"shop-templates": "",
"shop_vendors": "Vendedores | $t(titles.app)",
"techconsole": "$t(titles.app)",
"techjoblookup": "$t(titles.app)",
"techjobclock": "$t(titles.app)",
"techjoblookup": "$t(titles.app)",
"techshiftclock": "$t(titles.app)",
"temporarydocs": "",
"timetickets": "",

View File

@@ -113,11 +113,11 @@
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobinvoiced": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
"jobinproductionchange": "",
"jobinvoiced": "",
"jobioucreated": "",
"jobmodifylbradj": "",
"jobnoteadded": "",
@@ -255,7 +255,6 @@
"saving": ""
},
"fields": {
"ReceivableCustomField": "",
"address1": "",
"address2": "",
"appt_alt_transport": "",
@@ -332,13 +331,13 @@
"md_ded_notes": "",
"md_email_cc": "",
"md_from_emails": "",
"md_functionality_toggles": {
"parts_queue_toggle": ""
},
"md_hour_split": {
"paint": "",
"prep": ""
},
"md_functionality_toggles": {
"parts_queue_toggle": ""
},
"md_ins_co": {
"city": "",
"name": "",
@@ -474,6 +473,7 @@
"editaccess": ""
}
},
"ReceivableCustomField": "",
"responsibilitycenter": "",
"responsibilitycenter_accountdesc": "",
"responsibilitycenter_accountitem": "",
@@ -815,6 +815,10 @@
"usage": "",
"vehicle": ""
},
"readiness": {
"notready": "",
"ready": ""
},
"status": {
"in": "",
"inservice": "",
@@ -824,10 +828,6 @@
},
"successes": {
"saved": ""
},
"readiness": {
"notready": "",
"ready": ""
}
},
"csi": {
@@ -1214,6 +1214,31 @@
"updated": ""
}
},
"job_lifecycle": {
"columns": {
"duration": "",
"end": "",
"relative_end": "",
"relative_start": "",
"start": "",
"value": ""
},
"content": {
"current_status_accumulated_time": "",
"data_unavailable": "",
"legend_title": "",
"loading": "",
"not_available": "",
"previous_status_accumulated_time": "",
"title": "",
"title_durations": "",
"title_loading": "",
"title_transitions": ""
},
"errors": {
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
}
},
"job_payments": {
"buttons": {
"goback": "",
@@ -2014,6 +2039,7 @@
"general": "",
"insurance": "",
"labor": "La main d'oeuvre",
"lifecycle": "",
"partssublet": "Pièces / Sous-location",
"rates": "",
"repairdata": "Données de réparation",
@@ -2922,8 +2948,8 @@
"shop-templates": "",
"shop_vendors": "Vendeurs | $t(titles.app)",
"techconsole": "$t(titles.app)",
"techjoblookup": "$t(titles.app)",
"techjobclock": "$t(titles.app)",
"techjoblookup": "$t(titles.app)",
"techshiftclock": "$t(titles.app)",
"temporarydocs": "",
"timetickets": "",

File diff suppressed because it is too large Load Diff

View File

@@ -14,11 +14,21 @@ const getColor = (key) => {
const calculateStatusDuration = (transitions, statuses) => {
let statusDuration = {};
let totalDuration = 0;
let totalCurrentStatusDuration = null;
let summations = [];
transitions.forEach((transition, index) => {
let duration = transition.duration;
totalDuration += duration;
if (transition.start && !transition.end) {
const startMoment = moment(transition.start);
const nowMoment = moment();
const duration = moment.duration(nowMoment.diff(startMoment));
totalCurrentStatusDuration = {
value: duration.asMilliseconds(),
humanReadable: durationToHumanReadable(duration)
};
}
if (!transition.prev_value) {
statusDuration[transition.value] = {
@@ -84,6 +94,7 @@ const calculateStatusDuration = (transitions, statuses) => {
}) : summations,
totalStatuses: summations.length,
total: totalDuration,
totalCurrentStatusDuration,
humanReadableTotal
};
}