- Finish up with Statistics

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-07-30 17:24:55 -04:00
parent 65bb8c6f26
commit bbc446ef01
10 changed files with 472 additions and 299 deletions

View File

@@ -22,7 +22,7 @@ const CardColorLegend = ({ bodyshop }) => {
});
return (
<Col style={{ marginLeft: "15px" }}>
<Col>
<Typography>{t("production.labels.legend")}</Typography>
<List
grid={{

View File

@@ -1,7 +1,7 @@
import { SyncOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import Board from "./trello-board/index";
import { Button, notification, Skeleton, Space, Statistic } from "antd";
import { Button, notification, Skeleton, Space } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -175,28 +175,6 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
[boardLanes, client, getCardByID, isMoving, t, insertAuditTrail]
);
const totalHrs = useMemo(
() =>
data
.reduce(
(acc, val) =>
acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1),
[data]
);
const totalLAB = useMemo(
() => data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1),
[data]
);
const totalLAR = useMemo(
() => data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1),
[data]
);
const cardSettings = useMemo(
() =>
associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0
@@ -215,7 +193,17 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
orientation: false,
cardSize: "small",
model_info: true,
kiosk: false
kiosk: false,
totalHrs: true,
totalAmountInProduction: false,
totalLAB: true,
totalLAR: true,
jobsInProduction: true,
totalHrsOnBoard: false,
totalLABOnBoard: false,
totalLAROnBoard: false,
jobsOnBoard: false,
totalAmountOnBoard: true
},
[associationSettings]
);
@@ -234,14 +222,8 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
<div>
<IndefiniteLoading loading={isMoving} />
<PageHeader
title={
<Space>
<Statistic title={t("dashboard.titles.productionhours")} value={totalHrs} />
<Statistic title={t("dashboard.titles.labhours")} value={totalLAB} />
<Statistic title={t("dashboard.titles.larhours")} value={totalLAR} />
<Statistic title={t("appointments.labels.inproduction")} value={data && data.length} />
</Space>
}
title={cardSettings.cardcolor && <CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />}
style={{ paddingInline: 0, paddingBlock: 0 }}
extra={
<Space wrap>
<Button onClick={() => refetch && refetch()}>
@@ -256,11 +238,16 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
</Space>
}
/>
{cardSettings.cardcolor && <CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />}
<ProductionListDetailComponent jobs={data} />
<Board data={boardLanes} onDragEnd={onDragEnd} orientation={orientation} cardSettings={cardSettings} />
<Board
queryData={data}
data={boardLanes}
onDragEnd={onDragEnd}
orientation={orientation}
cardSettings={cardSettings}
/>
</div>
);
}

View File

@@ -2,10 +2,7 @@ import React, { useEffect, useMemo } from "react";
import { useQuery, useSubscription } from "@apollo/client";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
QUERY_JOBS_IN_PRODUCTION_WITH_STATUSES,
SUBSCRIPTION_JOBS_IN_PRODUCTION_WITH_STATUSES
} from "../../graphql/jobs.queries";
import { QUERY_JOBS_IN_PRODUCTION, SUBSCRIPTION_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries";
import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
@@ -24,16 +21,14 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
[bodyshop.md_ro_statuses.production_statuses, bodyshop.md_ro_statuses.additional_board_statuses]
);
const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION_WITH_STATUSES, {
variables: { statuses: combinedStatuses },
const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, {
pollInterval: 3600000,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
onError: (error) => console.error(`Error fetching jobs in production: ${error.message}`)
});
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION_WITH_STATUSES, {
variables: { statuses: combinedStatuses },
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION, {
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
});

View File

@@ -1,3 +1,4 @@
// ProductionBoardKanbanSettings.jsx
import { useMutation } from "@apollo/client";
import { Button, Card, Checkbox, Col, Form, notification, Popover, Radio, Row } from "antd";
import React, { useEffect, useState } from "react";
@@ -121,6 +122,22 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
].map((item) => renderCheckboxItem(item, `production.labels.${item}`))}
</Row>
</Card>
<Card title={t("production.settings.statistics_title")} style={cardStyle}>
<Row gutter={[16, 16]}>
{[
{ name: "totalHrs", label: "total_hours_in_production" },
{ name: "totalLAB", label: "total_lab_in_production" },
{ name: "totalLAR", label: "total_lar_in_production" },
{ name: "totalAmountInProduction", label: "total_amount_in_production" },
{ name: "jobsInProduction", label: "jobs_in_production" },
{ name: "totalHrsOnBoard", label: "total_hours_on_board" },
{ name: "totalLABOnBoard", label: "total_lab_on_board" },
{ name: "totalLAROnBoard", label: "total_lar_on_board" },
{ name: "jobsOnBoard", label: "total_jobs_on_board" },
{ name: "totalAmountOnBoard", label: "total_amount_on_board" }
].map((item) => renderCheckboxItem(item.name, `production.settings.statistics.${item.label}`))}
</Row>
</Card>
</>
);

View File

@@ -0,0 +1,145 @@
import React, { useMemo } from "react";
import { Statistic, Card } from "antd";
import { useTranslation } from "react-i18next";
const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
const { t } = useTranslation();
const calculateTotal = (items, key, subKey) => {
return items.reduce((acc, item) => acc + (item[key]?.aggregate?.sum?.[subKey] || 0), 0);
};
const calculateTotalAmount = (items, key) => {
return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 0);
};
const calculateReducerTotal = (lanes, key, subKey) => {
return lanes.reduce((acc, lane) => {
return (
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata[key]?.aggregate?.sum?.[subKey] || 0), 0)
);
}, 0);
};
const calculateReducerTotalAmount = (lanes, key) => {
return lanes.reduce((acc, lane) => {
return (
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata[key]?.totals?.subtotal?.amount || 0), 0)
);
}, 0);
};
const formatValue = (value, type) => {
if (type === "Jobs") {
return value.toFixed(0);
}
if (type === "Hrs") {
return value.toFixed(2);
}
return value;
};
const totalHrs = useMemo(() => {
if (!cardSettings.totalHrs) return null;
const total = calculateTotal(data, "labhrs", "mod_lb_hrs") + calculateTotal(data, "larhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [data, cardSettings.totalHrs]);
const totalLAB = useMemo(() => {
if (!cardSettings.totalLAB) return null;
const total = calculateTotal(data, "labhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [data, cardSettings.totalLAB]);
const totalLAR = useMemo(() => {
if (!cardSettings.totalLAR) return null;
const total = calculateTotal(data, "larhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [data, cardSettings.totalLAR]);
const jobsInProduction = useMemo(
() => (cardSettings.jobsInProduction ? data.length : null),
[data, cardSettings.jobsInProduction]
);
const totalAmountInProduction = useMemo(() => {
if (!cardSettings.totalAmountInProduction) return null;
const total = calculateTotalAmount(data, "job_totals");
return parseFloat(total.toFixed(2));
}, [data, cardSettings.totalAmountInProduction]);
const totalHrsOnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
const total =
calculateReducerTotal(reducerData.lanes, "labhrs", "mod_lb_hrs") +
calculateReducerTotal(reducerData.lanes, "larhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalHrsOnBoard]);
const totalLABOnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalLABOnBoard) return null;
const total = calculateReducerTotal(reducerData.lanes, "labhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalLABOnBoard]);
const totalLAROnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalLAROnBoard) return null;
const total = calculateReducerTotal(reducerData.lanes, "larhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalLAROnBoard]);
const jobsOnBoard = useMemo(
() =>
reducerData && cardSettings.jobsOnBoard
? reducerData.lanes.reduce((acc, lane) => acc + lane.cards.length, 0)
: null,
[reducerData, cardSettings.jobsOnBoard]
);
const totalAmountOnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalAmountOnBoard]);
const statistics = [
{ value: totalHrs, title: t("total_hours_in_production"), suffix: t("production.statistics.hours") },
{
value: totalAmountInProduction,
title: t("total_amount_in_production"),
prefix: t("production.statistics.currency_symbol")
},
{ value: totalLAB, title: t("total_lab_in_production"), suffix: t("production.statistics.hours") },
{ value: totalLAR, title: t("total_lar_in_production"), suffix: t("production.statistics.hours") },
{ value: jobsInProduction, title: t("jobs_in_production"), suffix: "Jobs" },
{ value: totalHrsOnBoard, title: t("total_hours_on_board"), suffix: t("production.statistics.hours") },
{
value: totalAmountOnBoard,
title: t("total_amount_on_board"),
prefix: t("production.statistics.currency_symbol")
},
{ value: totalLABOnBoard, title: t("total_lab_on_board"), suffix: t("production.statistics.hours") },
{ value: totalLAROnBoard, title: t("total_lar_on_board"), suffix: t("production.statistics.hours") },
{ value: jobsOnBoard, title: t("total_jobs_on_board"), suffix: "Jobs" }
];
return (
<div style={{ display: "flex", gap: "5px", flexWrap: "wrap", marginBottom: "5px" }}>
{statistics.map(
(stat, index) =>
stat.value !== null && (
<Card key={index}>
<Statistic
title={t(`production.statistics.${stat.title}`)}
value={formatValue(stat.value, stat.suffix)}
prefix={stat.prefix}
suffix={stat.suffix}
/>
</Card>
)
)}
</div>
);
};
export default ProductionStatistics;

View File

@@ -7,6 +7,7 @@ import Lane from "./Lane";
import { PopoverWrapper } from "react-popopo";
import * as actions from "../../../../redux/trello/trello.actions.js";
import { BoardWrapper } from "../styles/Base.js";
import ProductionStatistics from "../../production-board-kanban.statistics.jsx";
const useDragMap = () => {
const dragMapRef = useRef(new Map());
@@ -30,7 +31,8 @@ const BoardContainer = ({
orientation = "horizontal",
cardSettings = {},
eventBusHandle,
reducerData
reducerData,
queryData
}) => {
const [isDragging, setIsDragging] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
@@ -124,33 +126,36 @@ const BoardContainer = ({
);
return (
<PopoverWrapper>
<BoardWrapper orientation={orientation}>
<DragDropContext onDragEnd={onLaneDrag} onDragStart={onDragStart} contextId="production-board">
{currentReducerData.lanes.map((lane, index) => (
<Lane
key={lane.id}
id={lane.id}
title={lane.title}
index={index}
laneSortFunction={laneSortFunction}
orientation={orientation}
cards={lane.cards}
isDragging={isDragging}
isProcessing={isProcessing}
cardSettings={cardSettings}
maxLaneHeight={maxLaneHeight}
setMaxLaneHeight={setMaxLaneHeight}
maxCardHeight={maxCardHeight}
setMaxCardHeight={setMaxCardHeight}
maxCardWidth={maxCardWidth}
setMaxCardWidth={setMaxCardWidth}
lastDrag={getLastDragTime(lane.id)}
/>
))}
</DragDropContext>
</BoardWrapper>
</PopoverWrapper>
<div>
<ProductionStatistics data={queryData} reducerData={currentReducerData} cardSettings={cardSettings} />
<PopoverWrapper>
<BoardWrapper orientation={orientation}>
<DragDropContext onDragEnd={onLaneDrag} onDragStart={onDragStart} contextId="production-board">
{currentReducerData.lanes.map((lane, index) => (
<Lane
key={lane.id}
id={lane.id}
title={lane.title}
index={index}
laneSortFunction={laneSortFunction}
orientation={orientation}
cards={lane.cards}
isDragging={isDragging}
isProcessing={isProcessing}
cardSettings={cardSettings}
maxLaneHeight={maxLaneHeight}
setMaxLaneHeight={setMaxLaneHeight}
maxCardHeight={maxCardHeight}
setMaxCardHeight={setMaxCardHeight}
maxCardWidth={maxCardWidth}
setMaxCardWidth={setMaxCardWidth}
lastDrag={getLastDragTime(lane.id)}
/>
))}
</DragDropContext>
</BoardWrapper>
</PopoverWrapper>
</div>
);
};

View File

@@ -292,82 +292,6 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
}
`;
export const QUERY_JOBS_IN_PRODUCTION = gql`
query QUERY_JOBS_IN_PRODUCTION {
jobs(where: { inproduction: { _eq: true } }) {
id
updated_at
comment
status
category
iouparent
ro_number
ownerid
ownr_fn
ownr_ln
ownr_co_nm
v_model_yr
v_model_desc
clm_no
v_make_desc
v_color
vehicleid
plate_no
actual_in
scheduled_completion
scheduled_delivery
date_last_contacted
date_next_contact
ins_co_nm
clm_total
ownr_ph1
ownr_ph2
special_coverage_policy
owner_owing
production_vars
kanbanparent
alt_transport
employee_body
employee_refinish
employee_prep
employee_csr
est_ct_fn
est_ct_ln
suspended
date_repairstarted
joblines_status {
part_type
status
count
}
labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) {
aggregate {
sum {
mod_lb_hrs
}
}
}
larhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] }) {
aggregate {
sum {
mod_lb_hrs
}
}
}
subletLines: joblines(
where: { _and: { part_type: { _in: ["PAS", "PASL"] }, removed: { _eq: false } } }
order_by: { line_no: asc }
) {
id
line_desc
sublet_ignored
sublet_completed
jobid
}
}
}
`;
export const QUERY_LBR_HRS_BY_PK = gql`
query QUERY_LBR_HRS_BY_PK($id: uuid!) {
jobs_by_pk(id: $id) {
@@ -2529,9 +2453,18 @@ export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql`
}
`;
export const QUERY_JOBS_IN_PRODUCTION_WITH_STATUSES = gql`
query QUERY_JOBS_IN_PRODUCTION($statuses: [String!]) {
jobs(where: { inproduction: { _eq: true }, status: { _in: $statuses } }) {
export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
subscription SUBSCRIPTION_JOBS_IN_PRODUCTION {
jobs(where: { inproduction: { _eq: true } }) {
id
updated_at
}
}
`;
export const QUERY_JOBS_IN_PRODUCTION = gql`
query QUERY_JOBS_IN_PRODUCTION {
jobs(where: { inproduction: { _eq: true } }) {
id
updated_at
comment
@@ -2571,6 +2504,7 @@ export const QUERY_JOBS_IN_PRODUCTION_WITH_STATUSES = gql`
est_ct_fn
est_ct_ln
suspended
job_totals
date_repairstarted
joblines_status {
part_type
@@ -2604,21 +2538,3 @@ export const QUERY_JOBS_IN_PRODUCTION_WITH_STATUSES = gql`
}
}
`;
export const SUBSCRIPTION_JOBS_IN_PRODUCTION_WITH_STATUSES = gql`
subscription SUBSCRIPTION_JOBS_IN_PRODUCTION($statuses: [String!]) {
jobs(where: { inproduction: { _eq: true }, status: { _in: $statuses } }) {
id
updated_at
}
}
`;
export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
subscription SUBSCRIPTION_JOBS_IN_PRODUCTION {
jobs(where: { inproduction: { _eq: true } }) {
id
updated_at
}
}
`;

View File

@@ -2733,11 +2733,19 @@
"settings": {
"layout": "Layout",
"information": "Information",
"statistics_title": "Statistics",
"board_settings": "Board Settings",
"tabs": {
"card": "Card",
"board": "Board",
"lane": "Lane"
"statistics": {
"total_hours_in_production": "Hours in Production",
"total_lab_in_production": "Body Hours in Production",
"total_lar_in_production": "Refinish Hours in Production",
"total_amount_in_production": "Dollars in Production",
"jobs_in_production": "Jobs in Production",
"total_hours_on_board": "Hours on Board",
"total_lab_on_board": "Body Hours on Board",
"total_lar_on_board": "Refinish Hours on Board",
"total_amount_on_board": "Dollars on Board",
"total_jobs_on_board": "Jobs on Board"
}
},
"actions": {
@@ -2811,7 +2819,21 @@
},
"successes": {
"removed": "Job removed from production."
}
},
"statistics": {
"total_hours_in_production": "Hours in Production",
"total_lab_in_production": "Body Hours in Production",
"total_lar_in_production": "Refinish Hours in Production",
"total_amount_in_production": "Dollars in Production",
"jobs_in_production": "Jobs in Production",
"total_hours_on_board": "Hours on Board",
"total_lab_on_board": "Body Hours on Board",
"total_lar_on_board": "Refinish Hours on Board",
"total_amount_on_board": "Dollars on Board",
"total_jobs_on_board": "Jobs on Board",
"hours": "Hours",
"currency_symbol": "$"
}
},
"profile": {
"errors": {

View File

@@ -2722,27 +2722,52 @@
"purchases_by_vendor_summary": ""
}
},
"production": {
"actions": {
"addcolumns": "",
"bodypriority-clear": "",
"bodypriority-set": "",
"detailpriority-clear": "",
"detailpriority-set": "",
"paintpriority-clear": "",
"paintpriority-set": "",
"remove": "",
"removecolumn": "",
"saveconfig": "",
"suspend": "",
"unsuspend": ""
},
"errors": {
"boardupdate": "",
"removing": "",
"settings": ""
},
"labels": {
"production": {
"options": {
"small": "",
"medium": "",
"large": "",
"vertical": "",
"horizontal": ""
},
"settings": {
"layout": "",
"information": "",
"statistics_title": "",
"board_settings": "",
"statistics": {
"total_hours_in_production": "",
"total_lab_in_production": "",
"total_lar_in_production": "",
"total_amount_in_production": "",
"jobs_in_production": "",
"total_hours_on_board": "",
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": ""
}
},
"actions": {
"addcolumns": "",
"bodypriority-clear": "",
"bodypriority-set": "",
"detailpriority-clear": "",
"detailpriority-set": "",
"paintpriority-clear": "",
"paintpriority-set": "",
"remove": "",
"removecolumn": "",
"saveconfig": "",
"suspend": "",
"unsuspend": ""
},
"errors": {
"boardupdate": "",
"removing": "",
"settings": ""
},
"labels": {
"kiosk_mode": "",
"on": "",
"off": "",
@@ -2754,48 +2779,62 @@
"card_size": "",
"model_info": "",
"actual_in": "",
"alert": "",
"alertoff": "",
"alerton": "",
"ats": "",
"bodyhours": "",
"bodypriority": "",
"bodyshop": {
"labels": {
"qbo_departmentid": "",
"qbo_usa": ""
}
},
"cardcolor": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
"employeesearch": "",
"ins_co_nm": "",
"jobdetail": "",
"laborhrs": "",
"legend": "",
"note": "",
"ownr_nm": "",
"paintpriority": "",
"partsstatus": "",
"production_note": "",
"refinishhours": "",
"scheduled_completion": "",
"selectview": "",
"stickyheader": "",
"sublets": "",
"totalhours": "",
"touchtime": "",
"viewname": ""
},
"successes": {
"removed": ""
}
},
"alert": "",
"alertoff": "",
"alerton": "",
"ats": "",
"bodyhours": "",
"bodypriority": "",
"bodyshop": {
"labels": {
"qbo_departmentid": "",
"qbo_usa": ""
}
},
"cardcolor": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
"employeesearch": "",
"ins_co_nm": "",
"jobdetail": "",
"laborhrs": "",
"legend": "",
"note": "",
"ownr_nm": "",
"paintpriority": "",
"partsstatus": "",
"production_note": "",
"refinishhours": "",
"scheduled_completion": "",
"selectview": "",
"stickyheader": "",
"sublets": "",
"totalhours": "",
"touchtime": "",
"viewname": ""
},
"successes": {
"removed": ""
},
"statistics": {
"total_hours_in_production": "",
"total_lab_in_production": "",
"total_lar_in_production": "",
"total_amount_in_production": "",
"jobs_in_production": "",
"total_hours_on_board": "",
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
"hours": "",
"currency_symbol": ""
}
},
"profile": {
"errors": {
"state": "Error al leer el estado de la página. Porfavor refresca."

View File

@@ -2722,72 +2722,119 @@
"purchases_by_vendor_summary": ""
}
},
"production": {
"actions": {
"addcolumns": "",
"bodypriority-clear": "",
"bodypriority-set": "",
"detailpriority-clear": "",
"detailpriority-set": "",
"paintpriority-clear": "",
"paintpriority-set": "",
"remove": "",
"removecolumn": "",
"saveconfig": "",
"suspend": "",
"unsuspend": ""
},
"errors": {
"boardupdate": "",
"removing": "",
"settings": ""
},
"labels": {
"production": {
"options": {
"small": "",
"medium": "",
"large": "",
"vertical": "",
"horizontal": ""
},
"settings": {
"layout": "",
"information": "",
"statistics_title": "",
"board_settings": "",
"statistics": {
"total_hours_in_production": "",
"total_lab_in_production": "",
"total_lar_in_production": "",
"total_amount_in_production": "",
"jobs_in_production": "",
"total_hours_on_board": "",
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": ""
}
},
"actions": {
"addcolumns": "",
"bodypriority-clear": "",
"bodypriority-set": "",
"detailpriority-clear": "",
"detailpriority-set": "",
"paintpriority-clear": "",
"paintpriority-set": "",
"remove": "",
"removecolumn": "",
"saveconfig": "",
"suspend": "",
"unsuspend": ""
},
"errors": {
"boardupdate": "",
"removing": "",
"settings": ""
},
"labels": {
"kiosk_mode": "",
"on": "",
"off": "",
"wide": "",
"tall": "",
"vertical": "",
"horizontal": "",
"orientation": "",
"card_size": "",
"model_info": "",
"actual_in": "",
"alert": "",
"alertoff": "",
"alerton": "",
"ats": "",
"bodyhours": "",
"bodypriority": "",
"bodyshop": {
"labels": {
"qbo_departmentid": "",
"qbo_usa": ""
}
},
"cardcolor": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
"employeesearch": "",
"ins_co_nm": "",
"jobdetail": "",
"laborhrs": "",
"legend": "",
"note": "",
"ownr_nm": "",
"paintpriority": "",
"partsstatus": "",
"production_note": "",
"refinishhours": "",
"scheduled_completion": "",
"selectview": "",
"stickyheader": "",
"sublets": "",
"totalhours": "",
"touchtime": "",
"viewname": ""
},
"successes": {
"removed": ""
}
},
"alert": "",
"alertoff": "",
"alerton": "",
"ats": "",
"bodyhours": "",
"bodypriority": "",
"bodyshop": {
"labels": {
"qbo_departmentid": "",
"qbo_usa": ""
}
},
"cardcolor": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
"employeesearch": "",
"ins_co_nm": "",
"jobdetail": "",
"laborhrs": "",
"legend": "",
"note": "",
"ownr_nm": "",
"paintpriority": "",
"partsstatus": "",
"production_note": "",
"refinishhours": "",
"scheduled_completion": "",
"selectview": "",
"stickyheader": "",
"sublets": "",
"totalhours": "",
"touchtime": "",
"viewname": ""
},
"successes": {
"removed": ""
},
"statistics": {
"total_hours_in_production": "",
"total_lab_in_production": "",
"total_lar_in_production": "",
"total_amount_in_production": "",
"jobs_in_production": "",
"total_hours_on_board": "",
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
"hours": "",
"currency_symbol": ""
}
},
"profile": {
"errors": {
"state": "Erreur lors de la lecture de l'état de la page. Rafraichissez, s'il vous plait."