Reformat all project files to use the prettier config file.
This commit is contained in:
@@ -1,168 +1,183 @@
|
||||
import {Card, Table, Tag} from "antd";
|
||||
import { Card, Table, Tag } from "antd";
|
||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import dayjs from '../../../utils/day';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import dayjs from "../../../utils/day";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import axios from "axios";
|
||||
|
||||
const fortyFiveDaysAgo = () => dayjs().subtract(45, 'day').toLocaleString();
|
||||
const fortyFiveDaysAgo = () => dayjs().subtract(45, "day").toLocaleString();
|
||||
|
||||
export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardProps}) {
|
||||
console.log("🚀 ~ JobLifecycleDashboardComponent ~ bodyshop:", bodyshop)
|
||||
const {t} = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [lifecycleData, setLifecycleData] = useState(null);
|
||||
export default function JobLifecycleDashboardComponent({ data, bodyshop, ...cardProps }) {
|
||||
console.log("🚀 ~ JobLifecycleDashboardComponent ~ bodyshop:", bodyshop);
|
||||
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_ro_statuses
|
||||
});
|
||||
setLifecycleData(response.data.durations);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
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_ro_statuses
|
||||
});
|
||||
setLifecycleData(response.data.durations);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
getLifecycleData().catch(e => {
|
||||
console.error(`Error in getLifecycleData: ${e}`);
|
||||
})
|
||||
}, [data, bodyshop]);
|
||||
getLifecycleData().catch((e) => {
|
||||
console.error(`Error in getLifecycleData: ${e}`);
|
||||
});
|
||||
}, [data, bodyshop]);
|
||||
|
||||
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) + '%';
|
||||
}
|
||||
},
|
||||
];
|
||||
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) return null;
|
||||
|
||||
if (!data.job_lifecycle || !lifecycleData) return <DashboardRefreshRequired {...cardProps} />;
|
||||
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()}`
|
||||
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,
|
||||
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,
|
||||
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>
|
||||
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>
|
||||
</LoadingSkeleton>
|
||||
</Card>
|
||||
);
|
||||
);
|
||||
})}
|
||||
</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: "${dayjs().subtract(45, 'day').toISOString()}"
|
||||
_gte: "${dayjs().subtract(45, "day").toISOString()}"
|
||||
}
|
||||
}) {
|
||||
id
|
||||
|
||||
@@ -1,159 +1,124 @@
|
||||
import {Card} from "antd";
|
||||
import { Card } from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../../utils/day";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Bar, CartesianGrid, ComposedChart, Legend, Line, ResponsiveContainer, Tooltip, XAxis, YAxis,} from "recharts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Bar, CartesianGrid, ComposedChart, Legend, Line, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
||||
import * as Utils from "../../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardMonthlyEmployeeEfficiency({
|
||||
data,
|
||||
...cardProps
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.monthly_employee_efficiency)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
export default function DashboardMonthlyEmployeeEfficiency({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.monthly_employee_efficiency) return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const ticketsByDate = _.groupBy(data.monthly_employee_efficiency, (item) =>
|
||||
dayjs(item.date).format("YYYY-MM-DD")
|
||||
);
|
||||
const ticketsByDate = _.groupBy(data.monthly_employee_efficiency, (item) => dayjs(item.date).format("YYYY-MM-DD"));
|
||||
|
||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||
|
||||
const chartData = listOfDays.reduce((acc, val) => {
|
||||
//Sum up the current day.
|
||||
let dailyHrs;
|
||||
if (!!ticketsByDate[val]) {
|
||||
dailyHrs = ticketsByDate[val].reduce(
|
||||
(dayAcc, dayVal) => {
|
||||
return {
|
||||
actual: dayAcc.actual + dayVal.actualhrs,
|
||||
productive: dayAcc.productive + dayVal.productivehrs,
|
||||
};
|
||||
},
|
||||
{actual: 0, productive: 0}
|
||||
);
|
||||
} else {
|
||||
dailyHrs = {actual: 0, productive: 0};
|
||||
}
|
||||
const chartData = listOfDays.reduce((acc, val) => {
|
||||
//Sum up the current day.
|
||||
let dailyHrs;
|
||||
if (!!ticketsByDate[val]) {
|
||||
dailyHrs = ticketsByDate[val].reduce(
|
||||
(dayAcc, dayVal) => {
|
||||
return {
|
||||
actual: dayAcc.actual + dayVal.actualhrs,
|
||||
productive: dayAcc.productive + dayVal.productivehrs
|
||||
};
|
||||
},
|
||||
{ actual: 0, productive: 0 }
|
||||
);
|
||||
} else {
|
||||
dailyHrs = { actual: 0, productive: 0 };
|
||||
}
|
||||
|
||||
const dailyEfficiency =
|
||||
((dailyHrs.productive - dailyHrs.actual) / dailyHrs.actual + 1) * 100;
|
||||
const dailyEfficiency = ((dailyHrs.productive - dailyHrs.actual) / dailyHrs.actual + 1) * 100;
|
||||
|
||||
const theValue = {
|
||||
date: dayjs(val).format("DD"),
|
||||
// ...dailyHrs,
|
||||
actual: dailyHrs.actual.toFixed(1),
|
||||
productive: dailyHrs.productive.toFixed(1),
|
||||
dailyEfficiency: isNaN(dailyEfficiency) ? 0 : dailyEfficiency.toFixed(1),
|
||||
accActual:
|
||||
acc.length > 0
|
||||
? acc[acc.length - 1].accActual + dailyHrs.actual
|
||||
: dailyHrs.actual,
|
||||
const theValue = {
|
||||
date: dayjs(val).format("DD"),
|
||||
// ...dailyHrs,
|
||||
actual: dailyHrs.actual.toFixed(1),
|
||||
productive: dailyHrs.productive.toFixed(1),
|
||||
dailyEfficiency: isNaN(dailyEfficiency) ? 0 : dailyEfficiency.toFixed(1),
|
||||
accActual: acc.length > 0 ? acc[acc.length - 1].accActual + dailyHrs.actual : dailyHrs.actual,
|
||||
|
||||
accProductive:
|
||||
acc.length > 0
|
||||
? acc[acc.length - 1].accProductive + dailyHrs.productive
|
||||
: dailyHrs.productive,
|
||||
accEfficiency: 0,
|
||||
};
|
||||
accProductive: acc.length > 0 ? acc[acc.length - 1].accProductive + dailyHrs.productive : dailyHrs.productive,
|
||||
accEfficiency: 0
|
||||
};
|
||||
|
||||
theValue.accEfficiency =
|
||||
((theValue.accProductive - theValue.accActual) /
|
||||
(theValue.accActual || 0) +
|
||||
1) *
|
||||
100;
|
||||
theValue.accEfficiency = ((theValue.accProductive - theValue.accActual) / (theValue.accActual || 0) + 1) * 100;
|
||||
|
||||
if (isNaN(theValue.accEfficiency)) {
|
||||
theValue.accEfficiency = 0;
|
||||
} else {
|
||||
theValue.accEfficiency = theValue.accEfficiency.toFixed(1);
|
||||
}
|
||||
if (isNaN(theValue.accEfficiency)) {
|
||||
theValue.accEfficiency = 0;
|
||||
} else {
|
||||
theValue.accEfficiency = theValue.accEfficiency.toFixed(1);
|
||||
}
|
||||
|
||||
return [...acc, theValue];
|
||||
}, []);
|
||||
return [...acc, theValue];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.monthlyemployeeefficiency")}
|
||||
{...cardProps}
|
||||
>
|
||||
<div style={{height: "100%"}}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<ComposedChart
|
||||
data={chartData}
|
||||
margin={{top: 20, right: 20, bottom: 20, left: 20}}
|
||||
>
|
||||
<CartesianGrid stroke="#f5f5f5"/>
|
||||
<XAxis dataKey="date"/>
|
||||
<YAxis
|
||||
yAxisId="left"
|
||||
orientation="left"
|
||||
stroke="#8884d8"
|
||||
unit=" hrs"
|
||||
/>
|
||||
<YAxis
|
||||
yAxisId="right"
|
||||
orientation="right"
|
||||
stroke="#82ca9d"
|
||||
unit="%"
|
||||
/>
|
||||
<Tooltip/>
|
||||
<Legend/>
|
||||
<Line
|
||||
yAxisId="right"
|
||||
name="Accumulated Efficiency"
|
||||
type="monotone"
|
||||
unit="%"
|
||||
dataKey="accEfficiency"
|
||||
stroke="#152228"
|
||||
connectNulls
|
||||
// activeDot={{ r: 8 }}
|
||||
/>
|
||||
<Line
|
||||
name="Daily Efficiency"
|
||||
yAxisId="right"
|
||||
unit="%"
|
||||
type="monotone"
|
||||
connectNulls
|
||||
dataKey="dailyEfficiency"
|
||||
stroke="#d31717"
|
||||
/>
|
||||
<Bar
|
||||
name="Actual Hours"
|
||||
dataKey="actual"
|
||||
yAxisId="left"
|
||||
unit=" hrs"
|
||||
//stackId="day"
|
||||
barSize={20}
|
||||
fill="#102568"
|
||||
format={"0.0"}
|
||||
/>
|
||||
<Bar
|
||||
name="Productive Hours"
|
||||
dataKey="productive"
|
||||
yAxisId="left"
|
||||
unit=" hrs"
|
||||
//stackId="day"
|
||||
barSize={20}
|
||||
fill="#017664"
|
||||
format={"0.0"}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
return (
|
||||
<Card title={t("dashboard.titles.monthlyemployeeefficiency")} {...cardProps}>
|
||||
<div style={{ height: "100%" }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<ComposedChart data={chartData} margin={{ top: 20, right: 20, bottom: 20, left: 20 }}>
|
||||
<CartesianGrid stroke="#f5f5f5" />
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis yAxisId="left" orientation="left" stroke="#8884d8" unit=" hrs" />
|
||||
<YAxis yAxisId="right" orientation="right" stroke="#82ca9d" unit="%" />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line
|
||||
yAxisId="right"
|
||||
name="Accumulated Efficiency"
|
||||
type="monotone"
|
||||
unit="%"
|
||||
dataKey="accEfficiency"
|
||||
stroke="#152228"
|
||||
connectNulls
|
||||
// activeDot={{ r: 8 }}
|
||||
/>
|
||||
<Line
|
||||
name="Daily Efficiency"
|
||||
yAxisId="right"
|
||||
unit="%"
|
||||
type="monotone"
|
||||
connectNulls
|
||||
dataKey="dailyEfficiency"
|
||||
stroke="#d31717"
|
||||
/>
|
||||
<Bar
|
||||
name="Actual Hours"
|
||||
dataKey="actual"
|
||||
yAxisId="left"
|
||||
unit=" hrs"
|
||||
//stackId="day"
|
||||
barSize={20}
|
||||
fill="#102568"
|
||||
format={"0.0"}
|
||||
/>
|
||||
<Bar
|
||||
name="Productive Hours"
|
||||
dataKey="productive"
|
||||
yAxisId="left"
|
||||
unit=" hrs"
|
||||
//stackId="day"
|
||||
barSize={20}
|
||||
fill="#017664"
|
||||
format={"0.0"}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardMonthlyEmployeeEfficiencyGql = `
|
||||
monthly_employee_efficiency: timetickets(where: {_and: [{date: {_gte: "${dayjs()
|
||||
.startOf("month")
|
||||
.format("YYYY-MM-DD")}"}},{date: {_lte: "${dayjs()
|
||||
.endOf("month")
|
||||
.format("YYYY-MM-DD")}"}} ]}) {
|
||||
.format("YYYY-MM-DD")}"}},{date: {_lte: "${dayjs().endOf("month").format("YYYY-MM-DD")}"}} ]}) {
|
||||
actualhrs
|
||||
productivehrs
|
||||
employeeid
|
||||
|
||||
@@ -1,164 +1,138 @@
|
||||
import {Card, Input, Space, Table, Typography} from "antd";
|
||||
import { Card, Input, Space, Table, Typography } from "antd";
|
||||
import axios from "axios";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {alphaSort} from "../../../utils/sorters";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { alphaSort } from "../../../utils/sorters";
|
||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||
import Dinero from "dinero.js";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import {pageLimit} from "../../../utils/config";
|
||||
import { pageLimit } from "../../../utils/config";
|
||||
|
||||
export default function DashboardMonthlyJobCosting({data, ...cardProps}) {
|
||||
const {t} = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [costingData, setcostingData] = useState(null);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
});
|
||||
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [costingData, setcostingData] = useState(null);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function getCostingData() {
|
||||
if (data && data.monthly_sales) {
|
||||
setLoading(true);
|
||||
const response = await axios.post("/job/costingmulti", {
|
||||
jobids: data.monthly_sales.map((x) => x.id),
|
||||
});
|
||||
setcostingData(response.data);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
async function getCostingData() {
|
||||
if (data && data.monthly_sales) {
|
||||
setLoading(true);
|
||||
const response = await axios.post("/job/costingmulti", {
|
||||
jobids: data.monthly_sales.map((x) => x.id)
|
||||
});
|
||||
setcostingData(response.data);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
getCostingData();
|
||||
}, [data]);
|
||||
getCostingData();
|
||||
}, [data]);
|
||||
|
||||
if (!data) return null;
|
||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||
};
|
||||
const columns = [
|
||||
{
|
||||
title: t("bodyshop.fields.responsibilitycenter"),
|
||||
dataIndex: "cost_center",
|
||||
key: "cost_center",
|
||||
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.sales"),
|
||||
dataIndex: "sales",
|
||||
key: "sales",
|
||||
sorter: (a, b) =>
|
||||
parseFloat(a.sales.substring(1)) - parseFloat(b.sales.substring(1)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "sales" && state.sortedInfo.order,
|
||||
},
|
||||
if (!data) return null;
|
||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
const columns = [
|
||||
{
|
||||
title: t("bodyshop.fields.responsibilitycenter"),
|
||||
dataIndex: "cost_center",
|
||||
key: "cost_center",
|
||||
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
|
||||
sortOrder: state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.sales"),
|
||||
dataIndex: "sales",
|
||||
key: "sales",
|
||||
sorter: (a, b) => parseFloat(a.sales.substring(1)) - parseFloat(b.sales.substring(1)),
|
||||
sortOrder: state.sortedInfo.columnKey === "sales" && state.sortedInfo.order
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.labels.costs"),
|
||||
dataIndex: "costs",
|
||||
key: "costs",
|
||||
sorter: (a, b) =>
|
||||
parseFloat(a.costs.substring(1)) - parseFloat(b.costs.substring(1)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "costs" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.costs"),
|
||||
dataIndex: "costs",
|
||||
key: "costs",
|
||||
sorter: (a, b) => parseFloat(a.costs.substring(1)) - parseFloat(b.costs.substring(1)),
|
||||
sortOrder: state.sortedInfo.columnKey === "costs" && state.sortedInfo.order
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.labels.gpdollars"),
|
||||
dataIndex: "gpdollars",
|
||||
key: "gpdollars",
|
||||
sorter: (a, b) =>
|
||||
parseFloat(a.gpdollars.substring(1)) -
|
||||
parseFloat(b.gpdollars.substring(1)),
|
||||
{
|
||||
title: t("jobs.labels.gpdollars"),
|
||||
dataIndex: "gpdollars",
|
||||
key: "gpdollars",
|
||||
sorter: (a, b) => parseFloat(a.gpdollars.substring(1)) - parseFloat(b.gpdollars.substring(1)),
|
||||
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "gpdollars" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.gppercent"),
|
||||
dataIndex: "gppercent",
|
||||
key: "gppercent",
|
||||
sorter: (a, b) =>
|
||||
parseFloat(a.gppercent.slice(0, -1) || 0) -
|
||||
parseFloat(b.gppercent.slice(0, -1) || 0),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "gppercent" && state.sortedInfo.order,
|
||||
},
|
||||
];
|
||||
const filteredData =
|
||||
searchText === ""
|
||||
? (costingData && costingData.allCostCenterData) || []
|
||||
: costingData.allCostCenterData.filter((d) =>
|
||||
(d.cost_center || "")
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase())
|
||||
);
|
||||
sortOrder: state.sortedInfo.columnKey === "gpdollars" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.gppercent"),
|
||||
dataIndex: "gppercent",
|
||||
key: "gppercent",
|
||||
sorter: (a, b) => parseFloat(a.gppercent.slice(0, -1) || 0) - parseFloat(b.gppercent.slice(0, -1) || 0),
|
||||
sortOrder: state.sortedInfo.columnKey === "gppercent" && state.sortedInfo.order
|
||||
}
|
||||
];
|
||||
const filteredData =
|
||||
searchText === ""
|
||||
? (costingData && costingData.allCostCenterData) || []
|
||||
: costingData.allCostCenterData.filter((d) =>
|
||||
(d.cost_center || "").toString().toLowerCase().includes(searchText.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.monthlyjobcosting")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
value={searchText}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setSearchText(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
{...cardProps}
|
||||
>
|
||||
<LoadingSkeleton loading={loading}>
|
||||
<div style={{height: "100%"}}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{position: "top", defaultPageSize: pageLimit}}
|
||||
columns={columns}
|
||||
scroll={{x: true, y: "calc(100% - 4em)"}}
|
||||
rowKey="id"
|
||||
style={{height: "100%"}}
|
||||
dataSource={filteredData}
|
||||
summary={() => (
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
<Typography.Title level={4}>
|
||||
{t("general.labels.totals")}
|
||||
</Typography.Title>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{Dinero(
|
||||
costingData &&
|
||||
costingData.allSummaryData &&
|
||||
costingData.allSummaryData.totalSales
|
||||
).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{Dinero(
|
||||
costingData &&
|
||||
costingData.allSummaryData &&
|
||||
costingData.allSummaryData.totalCost
|
||||
).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{Dinero(
|
||||
costingData &&
|
||||
costingData.allSummaryData &&
|
||||
costingData.allSummaryData.gpdollars
|
||||
).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell></Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</LoadingSkeleton>
|
||||
</Card>
|
||||
);
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.monthlyjobcosting")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
value={searchText}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setSearchText(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
{...cardProps}
|
||||
>
|
||||
<LoadingSkeleton loading={loading}>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 4em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "100%" }}
|
||||
dataSource={filteredData}
|
||||
summary={() => (
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{Dinero(
|
||||
costingData && costingData.allSummaryData && costingData.allSummaryData.totalSales
|
||||
).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{Dinero(costingData && costingData.allSummaryData && costingData.allSummaryData.totalCost).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{Dinero(costingData && costingData.allSummaryData && costingData.allSummaryData.gpdollars).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell></Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</LoadingSkeleton>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,64 @@
|
||||
import {Card} from "antd";
|
||||
import { Card } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Cell, Pie, PieChart, ResponsiveContainer, Sector} from "recharts";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Cell, Pie, PieChart, ResponsiveContainer, Sector } from "recharts";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardMonthlyLaborSales({data, ...cardProps}) {
|
||||
const {t} = useTranslation();
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
if (!data) return null;
|
||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
export default function DashboardMonthlyLaborSales({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
if (!data) return null;
|
||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const laborData = {};
|
||||
const laborData = {};
|
||||
|
||||
data.monthly_sales.forEach((job) => {
|
||||
job.joblines.forEach((jobline) => {
|
||||
if (!jobline.mod_lbr_ty) return;
|
||||
if (!laborData[jobline.mod_lbr_ty])
|
||||
laborData[jobline.mod_lbr_ty] = Dinero();
|
||||
laborData[jobline.mod_lbr_ty] = laborData[jobline.mod_lbr_ty].add(
|
||||
Dinero({
|
||||
amount: Math.round(
|
||||
(job[`rate_${jobline.mod_lbr_ty.toLowerCase()}`] || 0) * 100
|
||||
),
|
||||
}).multiply(jobline.mod_lb_hrs || 0)
|
||||
);
|
||||
});
|
||||
data.monthly_sales.forEach((job) => {
|
||||
job.joblines.forEach((jobline) => {
|
||||
if (!jobline.mod_lbr_ty) return;
|
||||
if (!laborData[jobline.mod_lbr_ty]) laborData[jobline.mod_lbr_ty] = Dinero();
|
||||
laborData[jobline.mod_lbr_ty] = laborData[jobline.mod_lbr_ty].add(
|
||||
Dinero({
|
||||
amount: Math.round((job[`rate_${jobline.mod_lbr_ty.toLowerCase()}`] || 0) * 100)
|
||||
}).multiply(jobline.mod_lb_hrs || 0)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const chartData = Object.keys(laborData).map((key) => {
|
||||
return {
|
||||
name: t(`joblines.fields.lbr_types.${key.toUpperCase()}`),
|
||||
value: laborData[key].getAmount() / 100,
|
||||
color: pieColor(key.toUpperCase()),
|
||||
};
|
||||
});
|
||||
const chartData = Object.keys(laborData).map((key) => {
|
||||
return {
|
||||
name: t(`joblines.fields.lbr_types.${key.toUpperCase()}`),
|
||||
value: laborData[key].getAmount() / 100,
|
||||
color: pieColor(key.toUpperCase())
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Card title={t("dashboard.titles.monthlylaborsales")} {...cardProps}>
|
||||
<div style={{height: "100%"}}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart margin={0} padding={0}>
|
||||
<Pie
|
||||
data={chartData}
|
||||
activeIndex={activeIndex}
|
||||
activeShape={renderActiveShape}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius="60%"
|
||||
// outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
onMouseEnter={(throwaway, index) => setActiveIndex(index)}
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color}/>
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
return (
|
||||
<Card title={t("dashboard.titles.monthlylaborsales")} {...cardProps}>
|
||||
<div style={{ height: "100%" }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart margin={0} padding={0}>
|
||||
<Pie
|
||||
data={chartData}
|
||||
activeIndex={activeIndex}
|
||||
activeShape={renderActiveShape}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius="60%"
|
||||
// outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
onMouseEnter={(throwaway, index) => setActiveIndex(index)}
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardMonthlyRevenueGraphGql = `
|
||||
@@ -69,75 +66,75 @@ export const DashboardMonthlyRevenueGraphGql = `
|
||||
`;
|
||||
|
||||
const pieColor = (type) => {
|
||||
if (type === "LAA") return "lightgreen";
|
||||
else if (type === "LAB") return "dodgerblue";
|
||||
else if (type === "LAD") return "aliceblue";
|
||||
else if (type === "LAE") return "seafoam";
|
||||
else if (type === "LAG") return "chartreuse";
|
||||
else if (type === "LAF") return "magenta";
|
||||
else if (type === "LAM") return "gold";
|
||||
else if (type === "LAR") return "crimson";
|
||||
else if (type === "LAU") return "slategray";
|
||||
else if (type === "LA1") return "slategray";
|
||||
else if (type === "LA2") return "slategray";
|
||||
else if (type === "LA3") return "slategray";
|
||||
else if (type === "LA4") return "slategray";
|
||||
return "slategray";
|
||||
if (type === "LAA") return "lightgreen";
|
||||
else if (type === "LAB") return "dodgerblue";
|
||||
else if (type === "LAD") return "aliceblue";
|
||||
else if (type === "LAE") return "seafoam";
|
||||
else if (type === "LAG") return "chartreuse";
|
||||
else if (type === "LAF") return "magenta";
|
||||
else if (type === "LAM") return "gold";
|
||||
else if (type === "LAR") return "crimson";
|
||||
else if (type === "LAU") return "slategray";
|
||||
else if (type === "LA1") return "slategray";
|
||||
else if (type === "LA2") return "slategray";
|
||||
else if (type === "LA3") return "slategray";
|
||||
else if (type === "LA4") return "slategray";
|
||||
return "slategray";
|
||||
};
|
||||
|
||||
const renderActiveShape = (props) => {
|
||||
//const RADIAN = Math.PI / 180;
|
||||
const {
|
||||
cx,
|
||||
cy,
|
||||
//midAngle,
|
||||
innerRadius,
|
||||
outerRadius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
fill,
|
||||
payload,
|
||||
// percent,
|
||||
value,
|
||||
} = props;
|
||||
// const sin = Math.sin(-RADIAN * midAngle);
|
||||
// const cos = Math.cos(-RADIAN * midAngle);
|
||||
// // const sx = cx + (outerRadius + 10) * cos;
|
||||
// const sy = cy + (outerRadius + 10) * sin;
|
||||
// const mx = cx + (outerRadius + 30) * cos;
|
||||
// const my = cy + (outerRadius + 30) * sin;
|
||||
// //const ex = mx + (cos >= 0 ? 1 : -1) * 22;
|
||||
// const ey = my;
|
||||
//const textAnchor = cos >= 0 ? "start" : "end";
|
||||
//const RADIAN = Math.PI / 180;
|
||||
const {
|
||||
cx,
|
||||
cy,
|
||||
//midAngle,
|
||||
innerRadius,
|
||||
outerRadius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
fill,
|
||||
payload,
|
||||
// percent,
|
||||
value
|
||||
} = props;
|
||||
// const sin = Math.sin(-RADIAN * midAngle);
|
||||
// const cos = Math.cos(-RADIAN * midAngle);
|
||||
// // const sx = cx + (outerRadius + 10) * cos;
|
||||
// const sy = cy + (outerRadius + 10) * sin;
|
||||
// const mx = cx + (outerRadius + 30) * cos;
|
||||
// const my = cy + (outerRadius + 30) * sin;
|
||||
// //const ex = mx + (cos >= 0 ? 1 : -1) * 22;
|
||||
// const ey = my;
|
||||
//const textAnchor = cos >= 0 ? "start" : "end";
|
||||
|
||||
return (
|
||||
<g>
|
||||
<text x={cx} y={cy} dy={0} textAnchor="middle" fill={fill}>
|
||||
{payload.name}
|
||||
</text>
|
||||
<text x={cx} y={cy} dy={16} textAnchor="middle" fill={fill}>
|
||||
{Dinero({amount: Math.round(value * 100)}).toFormat()}
|
||||
</text>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
fill={fill}
|
||||
/>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
innerRadius={outerRadius + 6}
|
||||
outerRadius={outerRadius + 10}
|
||||
fill={fill}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
return (
|
||||
<g>
|
||||
<text x={cx} y={cy} dy={0} textAnchor="middle" fill={fill}>
|
||||
{payload.name}
|
||||
</text>
|
||||
<text x={cx} y={cy} dy={16} textAnchor="middle" fill={fill}>
|
||||
{Dinero({ amount: Math.round(value * 100) }).toFormat()}
|
||||
</text>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
fill={fill}
|
||||
/>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
innerRadius={outerRadius + 6}
|
||||
outerRadius={outerRadius + 10}
|
||||
fill={fill}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
// <path
|
||||
// d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`}
|
||||
|
||||
@@ -1,136 +1,134 @@
|
||||
import {Card} from "antd";
|
||||
import { Card } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Cell, Pie, PieChart, ResponsiveContainer, Sector} from "recharts";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Cell, Pie, PieChart, ResponsiveContainer, Sector } from "recharts";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardMonthlyPartsSales({data, ...cardProps}) {
|
||||
const {t} = useTranslation();
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
if (!data) return null;
|
||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
export default function DashboardMonthlyPartsSales({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
if (!data) return null;
|
||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const partData = {};
|
||||
const partData = {};
|
||||
|
||||
data.monthly_sales.forEach((job) => {
|
||||
job.joblines.forEach((jobline) => {
|
||||
if (!jobline.part_type) return;
|
||||
if (!partData[jobline.part_type]) partData[jobline.part_type] = Dinero();
|
||||
partData[jobline.part_type] = partData[jobline.part_type].add(
|
||||
Dinero({amount: Math.round((jobline.act_price || 0) * 100)}).multiply(
|
||||
jobline.part_qty || 0
|
||||
)
|
||||
);
|
||||
});
|
||||
data.monthly_sales.forEach((job) => {
|
||||
job.joblines.forEach((jobline) => {
|
||||
if (!jobline.part_type) return;
|
||||
if (!partData[jobline.part_type]) partData[jobline.part_type] = Dinero();
|
||||
partData[jobline.part_type] = partData[jobline.part_type].add(
|
||||
Dinero({ amount: Math.round((jobline.act_price || 0) * 100) }).multiply(jobline.part_qty || 0)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const chartData = Object.keys(partData).map((key) => {
|
||||
return {
|
||||
name: t(`joblines.fields.part_types.${key.toUpperCase()}`),
|
||||
value: partData[key].getAmount() / 100,
|
||||
color: pieColor(key.toUpperCase()),
|
||||
};
|
||||
});
|
||||
const chartData = Object.keys(partData).map((key) => {
|
||||
return {
|
||||
name: t(`joblines.fields.part_types.${key.toUpperCase()}`),
|
||||
value: partData[key].getAmount() / 100,
|
||||
color: pieColor(key.toUpperCase())
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Card title={t("dashboard.titles.monthlypartssales")} {...cardProps}>
|
||||
<div style={{height: "100%"}}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart margin={0} padding={0}>
|
||||
<Pie
|
||||
data={chartData}
|
||||
activeIndex={activeIndex}
|
||||
activeShape={renderActiveShape}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius="60%"
|
||||
// outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
onMouseEnter={(throwaway, index) => setActiveIndex(index)}
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color}/>
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
return (
|
||||
<Card title={t("dashboard.titles.monthlypartssales")} {...cardProps}>
|
||||
<div style={{ height: "100%" }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart margin={0} padding={0}>
|
||||
<Pie
|
||||
data={chartData}
|
||||
activeIndex={activeIndex}
|
||||
activeShape={renderActiveShape}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius="60%"
|
||||
// outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
onMouseEnter={(throwaway, index) => setActiveIndex(index)}
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardMonthlyRevenueGraphGql = `
|
||||
|
||||
`;
|
||||
const pieColor = (type) => {
|
||||
if (type === "PAA") return "darkgreen";
|
||||
else if (type === "PAC") return "green";
|
||||
else if (type === "PAE") return "gold";
|
||||
else if (type === "PAG") return "seafoam";
|
||||
else if (type === "PAL") return "chartreuse";
|
||||
else if (type === "PAM") return "magenta";
|
||||
else if (type === "PAN") return "crimson";
|
||||
else if (type === "PAO") return "gold";
|
||||
else if (type === "PAP") return "crimson";
|
||||
else if (type === "PAR") return "indigo";
|
||||
else if (type === "PAS") return "dodgerblue";
|
||||
else if (type === "PASL") return "dodgerblue";
|
||||
return "slategray";
|
||||
if (type === "PAA") return "darkgreen";
|
||||
else if (type === "PAC") return "green";
|
||||
else if (type === "PAE") return "gold";
|
||||
else if (type === "PAG") return "seafoam";
|
||||
else if (type === "PAL") return "chartreuse";
|
||||
else if (type === "PAM") return "magenta";
|
||||
else if (type === "PAN") return "crimson";
|
||||
else if (type === "PAO") return "gold";
|
||||
else if (type === "PAP") return "crimson";
|
||||
else if (type === "PAR") return "indigo";
|
||||
else if (type === "PAS") return "dodgerblue";
|
||||
else if (type === "PASL") return "dodgerblue";
|
||||
return "slategray";
|
||||
};
|
||||
|
||||
const renderActiveShape = (props) => {
|
||||
// const RADIAN = Math.PI / 180;
|
||||
const {
|
||||
cx,
|
||||
cy,
|
||||
// midAngle,
|
||||
innerRadius,
|
||||
outerRadius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
fill,
|
||||
payload,
|
||||
// percent,
|
||||
value,
|
||||
} = props;
|
||||
// const sin = Math.sin(-RADIAN * midAngle);
|
||||
// const cos = Math.cos(-RADIAN * midAngle);
|
||||
// const sx = cx + (outerRadius + 10) * cos;
|
||||
//const sy = cy + (outerRadius + 10) * sin;
|
||||
// const mx = cx + (outerRadius + 30) * cos;
|
||||
//const my = cy + (outerRadius + 30) * sin;
|
||||
// const ex = mx + (cos >= 0 ? 1 : -1) * 22;
|
||||
// const ey = my;
|
||||
// const textAnchor = cos >= 0 ? "start" : "end";
|
||||
// const RADIAN = Math.PI / 180;
|
||||
const {
|
||||
cx,
|
||||
cy,
|
||||
// midAngle,
|
||||
innerRadius,
|
||||
outerRadius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
fill,
|
||||
payload,
|
||||
// percent,
|
||||
value
|
||||
} = props;
|
||||
// const sin = Math.sin(-RADIAN * midAngle);
|
||||
// const cos = Math.cos(-RADIAN * midAngle);
|
||||
// const sx = cx + (outerRadius + 10) * cos;
|
||||
//const sy = cy + (outerRadius + 10) * sin;
|
||||
// const mx = cx + (outerRadius + 30) * cos;
|
||||
//const my = cy + (outerRadius + 30) * sin;
|
||||
// const ex = mx + (cos >= 0 ? 1 : -1) * 22;
|
||||
// const ey = my;
|
||||
// const textAnchor = cos >= 0 ? "start" : "end";
|
||||
|
||||
return (
|
||||
<g>
|
||||
<text x={cx} y={cy} dy={0} textAnchor="middle" fill={fill}>
|
||||
{payload.name}
|
||||
</text>
|
||||
<text x={cx} y={cy} dy={16} textAnchor="middle" fill={fill}>
|
||||
{Dinero({amount: Math.round(value * 100)}).toFormat()}
|
||||
</text>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
fill={fill}
|
||||
/>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
innerRadius={outerRadius + 6}
|
||||
outerRadius={outerRadius + 10}
|
||||
fill={fill}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
return (
|
||||
<g>
|
||||
<text x={cx} y={cy} dy={0} textAnchor="middle" fill={fill}>
|
||||
{payload.name}
|
||||
</text>
|
||||
<text x={cx} y={cy} dy={16} textAnchor="middle" fill={fill}>
|
||||
{Dinero({ amount: Math.round(value * 100) }).toFormat()}
|
||||
</text>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
fill={fill}
|
||||
/>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
innerRadius={outerRadius + 6}
|
||||
outerRadius={outerRadius + 10}
|
||||
fill={fill}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,83 +1,66 @@
|
||||
import {Card} from "antd";
|
||||
import { Card } from "antd";
|
||||
import dayjs from "../../../utils/day";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import _ from "lodash";
|
||||
import {Area, Bar, CartesianGrid, ComposedChart, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis,} from "recharts";
|
||||
import { Area, Bar, CartesianGrid, ComposedChart, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
||||
import Dinero from "dinero.js";
|
||||
import * as Utils from "../../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardMonthlyRevenueGraph({data, ...cardProps}) {
|
||||
const {t} = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const jobsByDate = _.groupBy(data.monthly_sales, (item) =>
|
||||
dayjs(item.date_invoiced).format("YYYY-MM-DD")
|
||||
);
|
||||
const jobsByDate = _.groupBy(data.monthly_sales, (item) => dayjs(item.date_invoiced).format("YYYY-MM-DD"));
|
||||
|
||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||
|
||||
const chartData = listOfDays.reduce((acc, val) => {
|
||||
//Sum up the current day.
|
||||
let dailySales;
|
||||
if (!!jobsByDate[val]) {
|
||||
dailySales = jobsByDate[val].reduce((dayAcc, dayVal) => {
|
||||
return dayAcc.add(
|
||||
Dinero((dayVal.job_totals && dayVal.job_totals.totals.subtotal) || 0)
|
||||
);
|
||||
}, Dinero());
|
||||
} else {
|
||||
dailySales = Dinero();
|
||||
}
|
||||
const chartData = listOfDays.reduce((acc, val) => {
|
||||
//Sum up the current day.
|
||||
let dailySales;
|
||||
if (!!jobsByDate[val]) {
|
||||
dailySales = jobsByDate[val].reduce((dayAcc, dayVal) => {
|
||||
return dayAcc.add(Dinero((dayVal.job_totals && dayVal.job_totals.totals.subtotal) || 0));
|
||||
}, Dinero());
|
||||
} else {
|
||||
dailySales = Dinero();
|
||||
}
|
||||
|
||||
const theValue = {
|
||||
date: dayjs(val).format("DD"),
|
||||
dailySales: dailySales.getAmount() / 100,
|
||||
accSales:
|
||||
acc.length > 0
|
||||
? acc[acc.length - 1].accSales + dailySales.getAmount() / 100
|
||||
: dailySales.getAmount() / 100,
|
||||
};
|
||||
const theValue = {
|
||||
date: dayjs(val).format("DD"),
|
||||
dailySales: dailySales.getAmount() / 100,
|
||||
accSales:
|
||||
acc.length > 0 ? acc[acc.length - 1].accSales + dailySales.getAmount() / 100 : dailySales.getAmount() / 100
|
||||
};
|
||||
|
||||
return [...acc, theValue];
|
||||
}, []);
|
||||
return [...acc, theValue];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card title={t("dashboard.titles.monthlyrevenuegraph")} {...cardProps}>
|
||||
<div style={{height: "100%"}}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<ComposedChart
|
||||
data={chartData}
|
||||
margin={{top: 20, right: 20, bottom: 20, left: 20}}
|
||||
>
|
||||
<CartesianGrid stroke="#f5f5f5"/>
|
||||
<XAxis dataKey="date"/>
|
||||
<YAxis/>
|
||||
<Tooltip
|
||||
formatter={(value, name, props) => value && value.toFixed(2)}
|
||||
/>
|
||||
<Legend/>
|
||||
<Area
|
||||
type="monotone"
|
||||
name="Accumulated Sales"
|
||||
dataKey="accSales"
|
||||
fill="#3CB371"
|
||||
stroke="#3CB371"
|
||||
/>
|
||||
<Bar
|
||||
name="Daily Sales"
|
||||
dataKey="dailySales"
|
||||
//stackId="day"
|
||||
barSize={20}
|
||||
fill="#413ea0"
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
return (
|
||||
<Card title={t("dashboard.titles.monthlyrevenuegraph")} {...cardProps}>
|
||||
<div style={{ height: "100%" }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<ComposedChart data={chartData} margin={{ top: 20, right: 20, bottom: 20, left: 20 }}>
|
||||
<CartesianGrid stroke="#f5f5f5" />
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis />
|
||||
<Tooltip formatter={(value, name, props) => value && value.toFixed(2)} />
|
||||
<Legend />
|
||||
<Area type="monotone" name="Accumulated Sales" dataKey="accSales" fill="#3CB371" stroke="#3CB371" />
|
||||
<Bar
|
||||
name="Daily Sales"
|
||||
dataKey="dailySales"
|
||||
//stackId="day"
|
||||
barSize={20}
|
||||
fill="#413ea0"
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardMonthlyRevenueGraphGql = `
|
||||
|
||||
@@ -1,34 +1,26 @@
|
||||
import {Card, Statistic} from "antd";
|
||||
import { Card, Statistic } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import dayjs from "../../../utils/day";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardProjectedMonthlySales({data, ...cardProps}) {
|
||||
const {t} = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.projected_monthly_sales)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
export default function DashboardProjectedMonthlySales({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.projected_monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const dollars =
|
||||
data.projected_monthly_sales &&
|
||||
data.projected_monthly_sales.reduce(
|
||||
(acc, val) =>
|
||||
acc.add(
|
||||
Dinero(
|
||||
val.job_totals &&
|
||||
val.job_totals.totals &&
|
||||
val.job_totals.totals.subtotal
|
||||
)
|
||||
),
|
||||
Dinero()
|
||||
);
|
||||
return (
|
||||
<Card title={t("dashboard.titles.projectedmonthlysales")} {...cardProps}>
|
||||
<Statistic value={dollars.toFormat()}/>
|
||||
</Card>
|
||||
const dollars =
|
||||
data.projected_monthly_sales &&
|
||||
data.projected_monthly_sales.reduce(
|
||||
(acc, val) => acc.add(Dinero(val.job_totals && val.job_totals.totals && val.job_totals.totals.subtotal)),
|
||||
Dinero()
|
||||
);
|
||||
return (
|
||||
<Card title={t("dashboard.titles.projectedmonthlysales")} {...cardProps}>
|
||||
<Statistic value={dollars.toFormat()} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardProjectedMonthlySalesGql = `
|
||||
@@ -38,12 +30,9 @@ export const DashboardProjectedMonthlySalesGql = `
|
||||
{_and: [
|
||||
{date_invoiced:{_is_null: false }},
|
||||
{date_invoiced: {_gte: "${dayjs()
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {date_invoiced: {_lte: "${dayjs()
|
||||
.endOf("month")
|
||||
.endOf("day")
|
||||
.toISOString()}"}}]},
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {date_invoiced: {_lte: "${dayjs().endOf("month").endOf("day").toISOString()}"}}]},
|
||||
{
|
||||
|
||||
_and:[
|
||||
@@ -51,10 +40,7 @@ _and:[
|
||||
{actual_completion: {_gte: "${dayjs()
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {actual_completion: {_lte: "${dayjs()
|
||||
.endOf("month")
|
||||
.endOf("day")
|
||||
.toISOString()}"}}
|
||||
.toISOString()}"}}, {actual_completion: {_lte: "${dayjs().endOf("month").endOf("day").toISOString()}"}}
|
||||
|
||||
]
|
||||
},
|
||||
@@ -63,12 +49,9 @@ _and:[
|
||||
{date_invoiced: {_is_null: true}},
|
||||
{actual_completion: {_is_null: true}}
|
||||
{scheduled_completion: {_gte: "${dayjs()
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {scheduled_completion: {_lte: "${dayjs()
|
||||
.endOf("month")
|
||||
.endOf("day")
|
||||
.toISOString()}"}}
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {scheduled_completion: {_lte: "${dayjs().endOf("month").endOf("day").toISOString()}"}}
|
||||
|
||||
|
||||
]}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import {SyncOutlined} from "@ant-design/icons";
|
||||
import {Card} from "antd";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Card } from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function DashboardRefreshRequired(props) {
|
||||
const {t} = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
<SyncOutlined style={{fontSize: "300%", margin: "1rem"}}/>
|
||||
<div>{t("dashboard.errors.refreshrequired")}</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
return (
|
||||
<Card {...props}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
textOverflow: "ellipsis"
|
||||
}}
|
||||
>
|
||||
<SyncOutlined style={{ fontSize: "300%", margin: "1rem" }} />
|
||||
<div>{t("dashboard.errors.refreshrequired")}</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons";
|
||||
import { Card, Space, Switch, Table, Tooltip, Typography } from "antd";
|
||||
import dayjs from "../../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
@@ -13,26 +9,20 @@ import { onlyUnique } from "../../../utils/arrayHelper";
|
||||
import { alphaSort, dateSort } from "../../../utils/sorters";
|
||||
import useLocalStorage from "../../../utils/useLocalStorage";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction,
|
||||
} from "../../owner-name-display/owner-name-display.component";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: {},
|
||||
filteredInfo: {}
|
||||
});
|
||||
|
||||
const [isTvModeScheduledIn, setIsTvModeScheduledIn] = useLocalStorage(
|
||||
"isTvModeScheduledIn",
|
||||
false
|
||||
);
|
||||
const [isTvModeScheduledIn, setIsTvModeScheduledIn] = useLocalStorage("isTvModeScheduledIn", false);
|
||||
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_in_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
if (!data.scheduled_in_today) return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const appt = []; // Flatten Data
|
||||
data.scheduled_in_today.forEach((item) => {
|
||||
@@ -68,13 +58,13 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
vehicleid: item.job.vehicleid,
|
||||
note: item.note,
|
||||
start: item.start,
|
||||
title: item.title,
|
||||
title: item.title
|
||||
};
|
||||
appt.push(i);
|
||||
}
|
||||
});
|
||||
appt.sort(function (a, b) {
|
||||
return dayjs(a.start) - dayjs(b.start);
|
||||
return dayjs(a.start) - dayjs(b.start);
|
||||
});
|
||||
|
||||
const tvFontSize = 16;
|
||||
@@ -87,35 +77,28 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
key: "start",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => dateSort(a.start, b.start),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<TimeFormatter>{record.start}</TimeFormatter>
|
||||
</span>
|
||||
),
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Link to={"/manage/jobs/" + record.jobid} onClick={(e) => e.stopPropagation()}>
|
||||
<Space>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
@@ -124,23 +107,18 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
@@ -150,7 +128,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
@@ -159,23 +137,15 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||
a.v_model_desc || ""
|
||||
}`,
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Link to={"/manage/vehicles/" + record.vehicleid} onClick={(e) => e.stopPropagation()}>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
@@ -183,7 +153,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
record.v_model_yr || ""
|
||||
} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
@@ -191,9 +161,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "alt_transport" &&
|
||||
state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order,
|
||||
filters:
|
||||
(appt &&
|
||||
appt
|
||||
@@ -202,47 +170,38 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport",
|
||||
value: [s],
|
||||
value: [s]
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.alt_transport}
|
||||
</span>
|
||||
),
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{record.alt_transport}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lab"),
|
||||
dataIndex: "joblines_body",
|
||||
key: "joblines_body",
|
||||
sorter: (a, b) => a.joblines_body - b.joblines_body,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "joblines_body" &&
|
||||
state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "joblines_body" && state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.joblines_body.toFixed(1)}
|
||||
</span>
|
||||
),
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{record.joblines_body.toFixed(1)}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lar"),
|
||||
dataIndex: "joblines_ref",
|
||||
key: "joblines_ref",
|
||||
sorter: (a, b) => a.joblines_ref - b.joblines_ref,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.joblines_ref.toFixed(1)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{record.joblines_ref.toFixed(1)}</span>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const columns = [
|
||||
@@ -252,30 +211,23 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
key: "start",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => dateSort(a.start, b.start),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
|
||||
render: (text, record) => <TimeFormatter>{record.start}</TimeFormatter>,
|
||||
sortOrder: state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
|
||||
render: (text, record) => <TimeFormatter>{record.start}</TimeFormatter>
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Link to={"/manage/jobs/" + record.jobid} onClick={(e) => e.stopPropagation()}>
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
@@ -283,23 +235,18 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
@@ -307,7 +254,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("dashboard.labels.phone"),
|
||||
@@ -320,7 +267,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
</Space>
|
||||
),
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ea"),
|
||||
@@ -328,9 +275,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
key: "ownr_ea",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
|
||||
),
|
||||
render: (text, record) => <a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
@@ -339,29 +284,19 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||
a.v_model_desc || ""
|
||||
}`,
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
<Link to={"/manage/vehicles/" + record.vehicleid} onClick={(e) => e.stopPropagation()}>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
@@ -370,8 +305,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
|
||||
filters:
|
||||
(appt &&
|
||||
appt
|
||||
@@ -380,12 +314,12 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Ins. Co.*",
|
||||
value: [s],
|
||||
value: [s]
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||
onFilter: (value, record) => value.includes(record.ins_co_nm)
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
@@ -393,9 +327,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "alt_transport" &&
|
||||
state.sortedInfo.order,
|
||||
sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order,
|
||||
filters:
|
||||
(appt &&
|
||||
appt
|
||||
@@ -404,13 +336,13 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport",
|
||||
value: [s],
|
||||
value: [s]
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||
},
|
||||
onFilter: (value, record) => value.includes(record.alt_transport)
|
||||
}
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
@@ -419,15 +351,12 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledindate", {
|
||||
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||
date: dayjs().startOf("day").format("MM/DD/YYYY")
|
||||
})}
|
||||
extra={
|
||||
<Space>
|
||||
<Typography.Text>{t("general.labels.tvmode")}</Typography.Text>
|
||||
<Switch
|
||||
onClick={() => setIsTvModeScheduledIn(!isTvModeScheduledIn)}
|
||||
defaultChecked={isTvModeScheduledIn}
|
||||
/>
|
||||
<Switch onClick={() => setIsTvModeScheduledIn(!isTvModeScheduledIn)} defaultChecked={isTvModeScheduledIn} />
|
||||
</Space>
|
||||
}
|
||||
{...cardProps}
|
||||
@@ -449,11 +378,9 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
}
|
||||
|
||||
export const DashboardScheduledInTodayGql = `
|
||||
scheduled_in_today: appointments(where: {start: {_gte: "${dayjs()
|
||||
.startOf("day")
|
||||
.toISOString()}", _lte: "${dayjs()
|
||||
.endOf("day")
|
||||
.toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) {
|
||||
scheduled_in_today: appointments(where: {start: {_gte: "${dayjs().startOf("day").toISOString()}", _lte: "${dayjs()
|
||||
.endOf("day")
|
||||
.toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) {
|
||||
canceled
|
||||
id
|
||||
job {
|
||||
|
||||
@@ -1,452 +1,382 @@
|
||||
import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined,} from "@ant-design/icons";
|
||||
import {Card, Space, Switch, Table, Tooltip, Typography} from "antd";
|
||||
import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons";
|
||||
import { Card, Space, Switch, Table, Tooltip, Typography } from "antd";
|
||||
import dayjs from "../../../utils/day";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Link} from "react-router-dom";
|
||||
import {TimeFormatter} from "../../../utils/DateFormatter";
|
||||
import {onlyUnique} from "../../../utils/arrayHelper";
|
||||
import {alphaSort, dateSort} from "../../../utils/sorters";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { TimeFormatter } from "../../../utils/DateFormatter";
|
||||
import { onlyUnique } from "../../../utils/arrayHelper";
|
||||
import { alphaSort, dateSort } from "../../../utils/sorters";
|
||||
import useLocalStorage from "../../../utils/useLocalStorage";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../../owner-name-display/owner-name-display.component";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardScheduledOutToday({data, ...cardProps}) {
|
||||
const {t} = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: {},});
|
||||
const [isTvModeScheduledOut, setIsTvModeScheduledOut] = useLocalStorage(
|
||||
"isTvModeScheduledOut",
|
||||
false
|
||||
);
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_out_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: {}
|
||||
});
|
||||
const [isTvModeScheduledOut, setIsTvModeScheduledOut] = useLocalStorage("isTvModeScheduledOut", false);
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_out_today) return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const scheduledOutToday = data.scheduled_out_today.map((item) => {
|
||||
const joblines_body = item.joblines
|
||||
? item.joblines
|
||||
.filter((l) => l.mod_lbr_ty !== "LAR")
|
||||
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||
: 0;
|
||||
const joblines_ref = item.joblines
|
||||
? item.joblines
|
||||
.filter((l) => l.mod_lbr_ty === "LAR")
|
||||
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||
: 0;
|
||||
return {
|
||||
...item,
|
||||
joblines_body,
|
||||
joblines_ref,
|
||||
};
|
||||
});
|
||||
const scheduledOutToday = data.scheduled_out_today.map((item) => {
|
||||
const joblines_body = item.joblines
|
||||
? item.joblines.filter((l) => l.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||
: 0;
|
||||
const joblines_ref = item.joblines
|
||||
? item.joblines.filter((l) => l.mod_lbr_ty === "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||
: 0;
|
||||
return {
|
||||
...item,
|
||||
joblines_body,
|
||||
joblines_ref
|
||||
};
|
||||
});
|
||||
|
||||
console.log('Scheduled Out Today')
|
||||
console.dir(scheduledOutToday);
|
||||
console.log("Scheduled Out Today");
|
||||
console.dir(scheduledOutToday);
|
||||
|
||||
const tvFontSize = 18;
|
||||
const tvFontWeight = "bold";
|
||||
const tvFontSize = 18;
|
||||
const tvFontWeight = "bold";
|
||||
|
||||
const tvColumns = [
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "scheduled_completion" &&
|
||||
state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<span style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>
|
||||
const tvColumns = [
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||
sortOrder: state.sortedInfo.columnKey === "scheduled_completion" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
<span style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link to={"/manage/jobs/" + record.jobid} onClick={(e) => e.stopPropagation()}>
|
||||
<Space>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert"/>
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{color: "orangered"}}/>
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{color: "orangered"}}/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
console.log('Render record out today');
|
||||
console.dir(record);
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>
|
||||
<OwnerNameDisplay ownerObject={record}/>
|
||||
</Space>
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
console.log("Render record out today");
|
||||
console.dir(record);
|
||||
return record.ownerid ? (
|
||||
<Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<span style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>
|
||||
<OwnerNameDisplay ownerObject={record}/>
|
||||
</Link>
|
||||
) : (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||
a.v_model_desc || ""
|
||||
}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link to={"/manage/vehicles/" + record.vehicleid} onClick={(e) => e.stopPropagation()}>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<span
|
||||
style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>{`${
|
||||
record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "alt_transport" &&
|
||||
state.sortedInfo.order,
|
||||
filters:
|
||||
(scheduledOutToday &&
|
||||
scheduledOutToday
|
||||
.map((j) => j.alt_transport)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport*",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],onFilter: (value, record) => value.includes(record.alt_transport),
|
||||
render: (text, record) => (
|
||||
<span style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>
|
||||
{record.alt_transport}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
filters:
|
||||
(scheduledOutToday &&
|
||||
scheduledOutToday
|
||||
.map((j) => j.status)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status),render: (text, record) => (
|
||||
<span style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>
|
||||
{record.status}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lab"),
|
||||
dataIndex: "joblines_body",
|
||||
key: "joblines_body",
|
||||
sorter: (a, b) => a.joblines_body - b.joblines_body,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "joblines_body" &&
|
||||
state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>
|
||||
{record.joblines_body.toFixed(1)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lar"),
|
||||
dataIndex: "joblines_ref",
|
||||
key: "joblines_ref",
|
||||
sorter: (a, b) => a.joblines_ref - b.joblines_ref,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{fontSize: tvFontSize, fontWeight: tvFontWeight}}>
|
||||
{record.joblines_ref.toFixed(1)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
</Link>
|
||||
) : (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{`${
|
||||
record.v_model_yr || ""
|
||||
} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order,
|
||||
filters:
|
||||
(scheduledOutToday &&
|
||||
scheduledOutToday
|
||||
.map((j) => j.alt_transport)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport*",
|
||||
value: [s]
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{record.alt_transport}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
filters:
|
||||
(scheduledOutToday &&
|
||||
scheduledOutToday
|
||||
.map((j) => j.status)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s]
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
render: (text, record) => <span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{record.status}</span>
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lab"),
|
||||
dataIndex: "joblines_body",
|
||||
key: "joblines_body",
|
||||
sorter: (a, b) => a.joblines_body - b.joblines_body,
|
||||
sortOrder: state.sortedInfo.columnKey === "joblines_body" && state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{record.joblines_body.toFixed(1)}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lar"),
|
||||
dataIndex: "joblines_ref",
|
||||
key: "joblines_ref",
|
||||
sorter: (a, b) => a.joblines_ref - b.joblines_ref,
|
||||
sortOrder: state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{record.joblines_ref.toFixed(1)}</span>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "scheduled_completion" &&
|
||||
state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert"/>
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{color: "orangered"}}/>
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{color: "orangered"}}/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record}/>
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record}/>
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||
sortOrder: state.sortedInfo.columnKey === "scheduled_completion" && state.sortedInfo.order,
|
||||
render: (text, record) => <TimeFormatter>{record.scheduled_completion}</TimeFormatter>
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link to={"/manage/jobs/" + record.jobid} onClick={(e) => e.stopPropagation()}>
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("dashboard.labels.phone"),
|
||||
dataIndex: "ownr_ph",
|
||||
key: "ownr_ph",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (<Space size="small" wrap>
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid}/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("dashboard.labels.phone"),
|
||||
dataIndex: "ownr_ph",
|
||||
key: "ownr_ph",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<Space size="small" wrap>
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid}/>
|
||||
</Space>),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
key: "ownr_ea",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||
a.v_model_desc || ""
|
||||
}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
|
||||
filters:
|
||||
(scheduledOutToday &&
|
||||
scheduledOutToday
|
||||
.map((j) => j.ins_co_nm)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Ins. Co.*",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "alt_transport" &&
|
||||
state.sortedInfo.order,
|
||||
filters:
|
||||
(scheduledOutToday &&
|
||||
scheduledOutToday
|
||||
.map((j) => j.alt_transport)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport*",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.alt_transport),},
|
||||
];
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
key: "ownr_ea",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => <a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link to={"/manage/vehicles/" + record.vehicleid} onClick={(e) => e.stopPropagation()}>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
|
||||
sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
|
||||
filters:
|
||||
(scheduledOutToday &&
|
||||
scheduledOutToday
|
||||
.map((j) => j.ins_co_nm)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Ins. Co.*",
|
||||
value: [s]
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.ins_co_nm)
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order,
|
||||
filters:
|
||||
(scheduledOutToday &&
|
||||
scheduledOutToday
|
||||
.map((j) => j.alt_transport)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport*",
|
||||
value: [s]
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.alt_transport)
|
||||
}
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||
};
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledoutdate", {
|
||||
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||
})}
|
||||
extra={
|
||||
<Space>
|
||||
<Typography.Text>{t("general.labels.tvmode")}</Typography.Text>
|
||||
<Switch
|
||||
onClick={() => setIsTvModeScheduledOut(!isTvModeScheduledOut)}
|
||||
defaultChecked={isTvModeScheduledOut}
|
||||
/>
|
||||
</Space>
|
||||
}{...cardProps}
|
||||
>
|
||||
<div style={{height: "100%"}}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={false}
|
||||
columns={isTvModeScheduledOut ? tvColumns : columns}
|
||||
scroll={{x: true, y: "calc(100% - 2em)"}}
|
||||
rowKey="id"
|
||||
style={{height: "85%"}}
|
||||
dataSource={scheduledOutToday}
|
||||
size={isTvModeScheduledOut ? "small" : "middle"}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledoutdate", {
|
||||
date: dayjs().startOf("day").format("MM/DD/YYYY")
|
||||
})}
|
||||
extra={
|
||||
<Space>
|
||||
<Typography.Text>{t("general.labels.tvmode")}</Typography.Text>
|
||||
<Switch
|
||||
onClick={() => setIsTvModeScheduledOut(!isTvModeScheduledOut)}
|
||||
defaultChecked={isTvModeScheduledOut}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
{...cardProps}
|
||||
>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={false}
|
||||
columns={isTvModeScheduledOut ? tvColumns : columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "85%" }}
|
||||
dataSource={scheduledOutToday}
|
||||
size={isTvModeScheduledOut ? "small" : "middle"}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardScheduledOutTodayGql = `
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
import {Card, Statistic} from "antd";
|
||||
import { Card, Statistic } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardTotalProductionDollars({
|
||||
data,
|
||||
...cardProps
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.production_jobs) return <DashboardRefreshRequired {...cardProps} />;
|
||||
const dollars =
|
||||
data.production_jobs &&
|
||||
data.production_jobs.reduce(
|
||||
(acc, val) =>
|
||||
acc.add(Dinero(val.job_totals && val.job_totals.totals.subtotal)),
|
||||
Dinero()
|
||||
);
|
||||
|
||||
return (
|
||||
<Card title={t("dashboard.labels.dollarsinproduction")} {...cardProps}>
|
||||
<Statistic value={dollars.toFormat()}/>
|
||||
</Card>
|
||||
export default function DashboardTotalProductionDollars({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.production_jobs) return <DashboardRefreshRequired {...cardProps} />;
|
||||
const dollars =
|
||||
data.production_jobs &&
|
||||
data.production_jobs.reduce(
|
||||
(acc, val) => acc.add(Dinero(val.job_totals && val.job_totals.totals.subtotal)),
|
||||
Dinero()
|
||||
);
|
||||
|
||||
return (
|
||||
<Card title={t("dashboard.labels.dollarsinproduction")} {...cardProps}>
|
||||
<Statistic value={dollars.toFormat()} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,63 +1,47 @@
|
||||
import {Card, Space, Statistic} from "antd";
|
||||
import { Card, Space, Statistic } from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop} from "../../../redux/user/user.selectors";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../../redux/user/user.selectors";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(DashboardTotalProductionHours);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DashboardTotalProductionHours);
|
||||
|
||||
export function DashboardTotalProductionHours({
|
||||
bodyshop,
|
||||
data,
|
||||
...cardProps
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.production_jobs) return <DashboardRefreshRequired {...cardProps} />;
|
||||
const hours =
|
||||
data.production_jobs &&
|
||||
data.production_jobs.reduce(
|
||||
(acc, val) => {
|
||||
return {
|
||||
body: acc.body + val.labhrs.aggregate.sum.mod_lb_hrs,
|
||||
ref: acc.ref + val.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
total:
|
||||
acc.total +
|
||||
val.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
val.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
};
|
||||
},
|
||||
{body: 0, ref: 0, total: 0}
|
||||
);
|
||||
const aboveTargetHours = hours.total >= bodyshop.prodtargethrs;
|
||||
return (
|
||||
<Card {...cardProps} title={t("dashboard.titles.prodhrssummary")}>
|
||||
<Space wrap style={{flex: 1}}>
|
||||
<Statistic
|
||||
title={t("dashboard.labels.bodyhrs")}
|
||||
value={hours.body.toFixed(1)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("dashboard.labels.refhrs")}
|
||||
value={hours.ref.toFixed(1)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("dashboard.labels.prodhrs")}
|
||||
value={hours.total.toFixed(1)}
|
||||
valueStyle={{color: aboveTargetHours ? "green" : "red"}}
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
export function DashboardTotalProductionHours({ bodyshop, data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
if (!data) return null;
|
||||
if (!data.production_jobs) return <DashboardRefreshRequired {...cardProps} />;
|
||||
const hours =
|
||||
data.production_jobs &&
|
||||
data.production_jobs.reduce(
|
||||
(acc, val) => {
|
||||
return {
|
||||
body: acc.body + val.labhrs.aggregate.sum.mod_lb_hrs,
|
||||
ref: acc.ref + val.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
total: acc.total + val.labhrs.aggregate.sum.mod_lb_hrs + val.larhrs.aggregate.sum.mod_lb_hrs
|
||||
};
|
||||
},
|
||||
{ body: 0, ref: 0, total: 0 }
|
||||
);
|
||||
const aboveTargetHours = hours.total >= bodyshop.prodtargethrs;
|
||||
return (
|
||||
<Card {...cardProps} title={t("dashboard.titles.prodhrssummary")}>
|
||||
<Space wrap style={{ flex: 1 }}>
|
||||
<Statistic title={t("dashboard.labels.bodyhrs")} value={hours.body.toFixed(1)} />
|
||||
<Statistic title={t("dashboard.labels.refhrs")} value={hours.ref.toFixed(1)} />
|
||||
<Statistic
|
||||
title={t("dashboard.labels.prodhrs")}
|
||||
value={hours.total.toFixed(1)}
|
||||
valueStyle={{ color: aboveTargetHours ? "green" : "red" }}
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardTotalProductionHoursGql = ``;
|
||||
|
||||
Reference in New Issue
Block a user