IO-2206 WIP Time Ticket Approval Queue.
This commit is contained in:
@@ -6963,6 +6963,53 @@
|
|||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
|
<folder_node>
|
||||||
|
<name>ttapprovals</name>
|
||||||
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>approve</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>view</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
</children>
|
||||||
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
<name>users</name>
|
<name>users</name>
|
||||||
<children>
|
<children>
|
||||||
@@ -16464,6 +16511,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>excel</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>exceptiontitle</name>
|
<name>exceptiontitle</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -32429,6 +32497,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>ttapprovals</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>vehicles</name>
|
<name>vehicles</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -37714,6 +37803,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>dms_posting_sheet</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>envelope_return_address</name>
|
<name>envelope_return_address</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -41367,6 +41477,48 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>exported_gsr_by_ro</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>exported_gsr_by_ro_labor</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>gsr_by_atp</name>
|
<name>gsr_by_atp</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -46228,6 +46380,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>ttapprovals</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>vehicle-details</name>
|
<name>vehicle-details</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -47154,6 +47327,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>ttapprovals</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>vehicledetail</name>
|
<name>vehicledetail</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -246,6 +246,11 @@ function Header({
|
|||||||
{t("menus.header.timetickets")}
|
{t("menus.header.timetickets")}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
<Menu.Item key="ttapprovals" icon={<FieldTimeOutlined />}>
|
||||||
|
<Link to="/manage/ttapprovals">
|
||||||
|
{t("menus.header.ttapprovals")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key="entertimetickets"
|
key="entertimetickets"
|
||||||
icon={<Icon component={GiPlayerTime} />}
|
icon={<Icon component={GiPlayerTime} />}
|
||||||
|
|||||||
@@ -532,6 +532,30 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
|||||||
>
|
>
|
||||||
<InputNumber />
|
<InputNumber />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.rbac.ttapprovals.view")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
name={["md_rbac", "ttapprovals:view"]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
name={["md_rbac", "ttapprovals:approve"]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.rbac.shop.vendors")}
|
label={t("bodyshop.fields.rbac.shop.vendors")}
|
||||||
rules={[
|
rules={[
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
|||||||
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
|
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
|
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
|
||||||
|
import { INSERT_NEW_TT_APPROVALS } from "../../graphql/tt-approvals.queries";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
timeTicketTasksModal: selectTimeTicketTasks,
|
timeTicketTasksModal: selectTimeTicketTasks,
|
||||||
@@ -42,6 +43,7 @@ export function TimeTickeTaskModalContainer({
|
|||||||
});
|
});
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [insertTimeTickets] = useMutation(INSERT_NEW_TIME_TICKET);
|
const [insertTimeTickets] = useMutation(INSERT_NEW_TIME_TICKET);
|
||||||
|
const [insertTimeTicketApproval] = useMutation(INSERT_NEW_TT_APPROVALS);
|
||||||
const [queryJobInfo, { called, loading, data: lineTicketData }] =
|
const [queryJobInfo, { called, loading, data: lineTicketData }] =
|
||||||
useLazyQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
|
useLazyQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
@@ -49,31 +51,52 @@ export function TimeTickeTaskModalContainer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function handleFinish(values) {
|
async function handleFinish(values) {
|
||||||
console.log(
|
|
||||||
"🚀 ~ file: time-ticket-task-modal.container.jsx:52 ~ handleFinish ~ values:",
|
|
||||||
values
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
const result = await insertTimeTickets({
|
if (true) {
|
||||||
variables: {
|
const result = await insertTimeTicketApproval({
|
||||||
timeTicketInput: values.timetickets.map((ticket) =>
|
variables: {
|
||||||
_.omit(ticket, "pay")
|
timeTicketInput: values.timetickets.map((ticket) => ({
|
||||||
),
|
..._.omit(ticket, "pay"),
|
||||||
},
|
bodyshopid: bodyshop.id,
|
||||||
});
|
})),
|
||||||
if (result.errors) {
|
},
|
||||||
notification.open({
|
|
||||||
type: "error",
|
|
||||||
message: t("timetickets.errors.creating", {
|
|
||||||
message: JSON.stringify(result.errors),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (result.errors) {
|
||||||
|
notification.open({
|
||||||
|
type: "error",
|
||||||
|
message: t("timetickets.errors.creating", {
|
||||||
|
message: JSON.stringify(result.errors),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification.open({
|
||||||
|
type: "success",
|
||||||
|
message: t("timetickets.successes.created"),
|
||||||
|
});
|
||||||
|
toggleModalVisible();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
notification.open({
|
const result = await insertTimeTickets({
|
||||||
type: "success",
|
variables: {
|
||||||
message: t("timetickets.successes.created"),
|
timeTicketInput: values.timetickets.map((ticket) =>
|
||||||
|
_.omit(ticket, "pay")
|
||||||
|
),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
toggleModalVisible();
|
if (result.errors) {
|
||||||
|
notification.open({
|
||||||
|
type: "error",
|
||||||
|
message: t("timetickets.errors.creating", {
|
||||||
|
message: JSON.stringify(result.errors),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification.open({
|
||||||
|
type: "success",
|
||||||
|
message: t("timetickets.successes.created"),
|
||||||
|
});
|
||||||
|
toggleModalVisible();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
import { EditFilled } from "@ant-design/icons";
|
||||||
|
import { Button, Card, Space, Table, Tag } from "antd";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
|
import moment from "moment";
|
||||||
|
import React, { useMemo, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
import {
|
||||||
|
selectAuthLevel,
|
||||||
|
selectBodyshop,
|
||||||
|
} from "../../redux/user/user.selectors";
|
||||||
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import { onlyUnique } from "../../utils/arrayHelper";
|
||||||
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
|
import RbacWrapper, {
|
||||||
|
HasRbacAccess,
|
||||||
|
} from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
|
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||||
|
import queryString from "query-string";
|
||||||
|
import TtApproveButtonComponent from "../tt-approve-button/tt-approve-button.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
authLevel: selectAuthLevel,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setTimeTicketTaskContext: (context) =>
|
||||||
|
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TtApprovalsListComponent);
|
||||||
|
|
||||||
|
export function TtApprovalsListComponent({
|
||||||
|
bodyshop,
|
||||||
|
setTimeTicketTaskContext,
|
||||||
|
authLevel,
|
||||||
|
disabled,
|
||||||
|
loading,
|
||||||
|
tt_approval_queue,
|
||||||
|
total,
|
||||||
|
refetch,
|
||||||
|
techConsole,
|
||||||
|
jobId,
|
||||||
|
extra,
|
||||||
|
}) {
|
||||||
|
const [state, setState] = useState({
|
||||||
|
sortedInfo: {},
|
||||||
|
filteredInfo: { text: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const history = useHistory();
|
||||||
|
const search = queryString.parse(useLocation().search);
|
||||||
|
const { page } = search;
|
||||||
|
const [selectedTickets, setSelectedTickets] = useState([]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.date"),
|
||||||
|
dataIndex: "date",
|
||||||
|
key: "date",
|
||||||
|
sorter: (a, b) => dateSort(a.date, b.date),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.employee"),
|
||||||
|
dataIndex: "employeeid",
|
||||||
|
key: "employeeid",
|
||||||
|
sorter: (a, b) => alphaSort(a.employee.last_name, b.employee.last_name),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
||||||
|
render: (text, record) =>
|
||||||
|
`${record.employee.first_name} ${record.employee.last_name}`,
|
||||||
|
filters:
|
||||||
|
tt_approval_queue
|
||||||
|
.map((l) => l.employeeid)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: (() => {
|
||||||
|
const emp = bodyshop.employees.find((e) => e.id === s);
|
||||||
|
|
||||||
|
return `${emp?.first_name} ${emp?.last_name}`;
|
||||||
|
})(), //
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
}) || [],
|
||||||
|
onFilter: (value, record) => value.includes(record.employeeid),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.cost_center"),
|
||||||
|
dataIndex: "cost_center",
|
||||||
|
key: "cost_center",
|
||||||
|
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
|
||||||
|
render: (text, record) =>
|
||||||
|
record.cost_center === "timetickets.labels.shift"
|
||||||
|
? t(record.cost_center)
|
||||||
|
: record.cost_center,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
|
||||||
|
filters:
|
||||||
|
tt_approval_queue
|
||||||
|
.map((l) => l.cost_center)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s === "timetickets.labels.shift" ? t(s) : s, //|| "No Status*",
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
}) || [],
|
||||||
|
onFilter: (value, record) => value.includes(record.cost_center),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.ro_number"),
|
||||||
|
dataIndex: "ro_number",
|
||||||
|
key: "ro_number",
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
|
render: (text, record) =>
|
||||||
|
record.job && (
|
||||||
|
<Link to={"/manage/jobs/" + record.job.id}>
|
||||||
|
<Space wrap>
|
||||||
|
{record.job.ro_number || "N/A"}
|
||||||
|
<Tag>{record.job.status}</Tag>
|
||||||
|
</Space>
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.productivehrs"),
|
||||||
|
dataIndex: "productivehrs",
|
||||||
|
key: "productivehrs",
|
||||||
|
sorter: (a, b) => a.productivehrs - b.productivehrs,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "productivehrs" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.actualhrs"),
|
||||||
|
dataIndex: "actualhrs",
|
||||||
|
key: "actualhrs",
|
||||||
|
sorter: (a, b) => a.actualhrs - b.actualhrs,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "actualhrs" && state.sortedInfo.order,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.memo"),
|
||||||
|
dataIndex: "memo",
|
||||||
|
key: "memo",
|
||||||
|
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
||||||
|
render: (text, record) =>
|
||||||
|
record.clockon || record.clockoff ? t(record.memo) : record.memo,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.clockon"),
|
||||||
|
dataIndex: "clockon",
|
||||||
|
key: "clockon",
|
||||||
|
|
||||||
|
render: (text, record) => (
|
||||||
|
<DateTimeFormatter>{record.clockon}</DateTimeFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Pay",
|
||||||
|
dataIndex: "pay",
|
||||||
|
key: "pay",
|
||||||
|
render: (text, record) =>
|
||||||
|
Dinero({ amount: Math.round(record.rate * 100) })
|
||||||
|
.multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||||
|
.toFormat("$0.00"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
|
search.page = pagination.current;
|
||||||
|
if (sorter && sorter.column && sorter.column.sortObject) {
|
||||||
|
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));
|
||||||
|
} else {
|
||||||
|
delete search.searchObj;
|
||||||
|
search.sortcolumn = sorter.order ? sorter.columnKey : null;
|
||||||
|
search.sortorder = sorter.order;
|
||||||
|
}
|
||||||
|
|
||||||
|
search.sort = JSON.stringify({ [sorter.columnKey]: sorter.order });
|
||||||
|
history.push({ search: queryString.stringify(search) });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={t("timetickets.labels.timetickets")}
|
||||||
|
extra={
|
||||||
|
<Space wrap>
|
||||||
|
{extra}
|
||||||
|
<TtApproveButtonComponent
|
||||||
|
selectedTickets={selectedTickets}
|
||||||
|
disabled={selectedTickets.length === 0}
|
||||||
|
completedCallback={setSelectedTickets}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
loading={loading}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="id"
|
||||||
|
scroll={{
|
||||||
|
x: true,
|
||||||
|
}}
|
||||||
|
pagination={{
|
||||||
|
position: "top",
|
||||||
|
pageSize: 25,
|
||||||
|
current: parseInt(page || 1),
|
||||||
|
total: total,
|
||||||
|
}}
|
||||||
|
dataSource={tt_approval_queue}
|
||||||
|
onChange={handleTableChange}
|
||||||
|
rowSelection={{
|
||||||
|
onSelectAll: (selected, selectedRows) =>
|
||||||
|
setSelectedTickets(selectedRows.map((i) => i.id)),
|
||||||
|
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
||||||
|
setSelectedTickets(selectedRows.map((i) => i.id));
|
||||||
|
},
|
||||||
|
selectedRowKeys: selectedTickets,
|
||||||
|
type: "checkbox",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { useQuery } from "@apollo/client";
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
||||||
|
import {
|
||||||
|
setBreadcrumbs,
|
||||||
|
setSelectedHeader,
|
||||||
|
} from "../../redux/application/application.actions";
|
||||||
|
import TtApprovalsListComponent from "./tt-approvals-list.component";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import queryString from "query-string";
|
||||||
|
import { QUERY_ALL_TT_APPROVALS_PAGINATED } from "../../graphql/tt-approvals.queries";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||||
|
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function TimeTicketsContainer({
|
||||||
|
bodyshop,
|
||||||
|
setBreadcrumbs,
|
||||||
|
setSelectedHeader,
|
||||||
|
}) {
|
||||||
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
|
const { page, sortcolumn, sortorder, search, searchObj } = searchParams;
|
||||||
|
|
||||||
|
const { loading, error, data } = useQuery(QUERY_ALL_TT_APPROVALS_PAGINATED, {
|
||||||
|
fetchPolicy: "network-only",
|
||||||
|
nextFetchPolicy: "network-only",
|
||||||
|
variables: {
|
||||||
|
search: search || "",
|
||||||
|
offset: page ? (page - 1) * 25 : 0,
|
||||||
|
limit: 25,
|
||||||
|
order: [
|
||||||
|
searchObj
|
||||||
|
? JSON.parse(searchObj)
|
||||||
|
: {
|
||||||
|
[sortcolumn || "date"]: sortorder
|
||||||
|
? sortorder === "descend"
|
||||||
|
? "desc"
|
||||||
|
: "asc"
|
||||||
|
: "desc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TtApprovalsListComponent
|
||||||
|
loading={loading}
|
||||||
|
tt_approval_queue={data ? data.tt_approval_queue : []}
|
||||||
|
total={data ? data.tt_approval_queue_aggregate.aggregate.count : 0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TimeTicketsContainer);
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { useApolloClient } from "@apollo/client";
|
||||||
|
import { Button } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import {
|
||||||
|
QUERY_TT_APPROVALS_BY_IDS,
|
||||||
|
UPDATE_TT_BY_APPROVAL,
|
||||||
|
} from "../../graphql/tt-approvals.queries";
|
||||||
|
import {
|
||||||
|
selectBodyshop,
|
||||||
|
selectCurrentUser,
|
||||||
|
} from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
currentUser: selectCurrentUser,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function TtApproveButton({
|
||||||
|
bodyshop,
|
||||||
|
currentUser,
|
||||||
|
selectedTickets,
|
||||||
|
disabled,
|
||||||
|
loadingCallback,
|
||||||
|
completedCallback,
|
||||||
|
refetch,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const client = useApolloClient();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleQbxml = async () => {
|
||||||
|
const { data } = await client.query({
|
||||||
|
query: QUERY_TT_APPROVALS_BY_IDS,
|
||||||
|
variables: { ids: selectedTickets },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: insertData } = await client.query({
|
||||||
|
query: UPDATE_TT_BY_APPROVAL,
|
||||||
|
variables: {
|
||||||
|
ttApprovalUpdates: data.map((tta) => ({
|
||||||
|
_set: {},
|
||||||
|
where: { id: { _eq: tta.id } },
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// if (!!completedCallback) completedCallback([]);
|
||||||
|
// if (!!loadingCallback) loadingCallback(false);
|
||||||
|
|
||||||
|
// setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||||
|
{t("tt_approvals.actions.approveselected")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null)(TtApproveButton);
|
||||||
|
|
||||||
|
const generateGqlUpdate = (ttapproval) => {
|
||||||
|
return;
|
||||||
|
};
|
||||||
93
client/src/graphql/tt-approvals.queries.js
Normal file
93
client/src/graphql/tt-approvals.queries.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { gql } from "@apollo/client";
|
||||||
|
|
||||||
|
export const QUERY_ALL_TT_APPROVALS_PAGINATED = gql`
|
||||||
|
query QUERY_ALL_TT_APPROVALS_PAGINATED(
|
||||||
|
$offset: Int
|
||||||
|
$limit: Int
|
||||||
|
$order: [tt_approval_queue_order_by!]!
|
||||||
|
) {
|
||||||
|
tt_approval_queue(offset: $offset, limit: $limit, order_by: $order) {
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
bodyshopid
|
||||||
|
employeeid
|
||||||
|
employee {
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
id
|
||||||
|
}
|
||||||
|
job {
|
||||||
|
ro_number
|
||||||
|
status
|
||||||
|
id
|
||||||
|
}
|
||||||
|
employeeid
|
||||||
|
actualhrs
|
||||||
|
productivehrs
|
||||||
|
ciecacode
|
||||||
|
cost_center
|
||||||
|
date
|
||||||
|
rate
|
||||||
|
}
|
||||||
|
tt_approval_queue_aggregate {
|
||||||
|
aggregate {
|
||||||
|
count(distinct: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const INSERT_NEW_TT_APPROVALS = gql`
|
||||||
|
mutation INSERT_NEW_TT_APPROVALS(
|
||||||
|
$timeTicketInput: [tt_approval_queue_insert_input!]!
|
||||||
|
) {
|
||||||
|
insert_tt_approval_queue(objects: $timeTicketInput) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
employeeid
|
||||||
|
productivehrs
|
||||||
|
actualhrs
|
||||||
|
ciecacode
|
||||||
|
date
|
||||||
|
memo
|
||||||
|
flat_rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const QUERY_TT_APPROVALS_BY_IDS = gql`
|
||||||
|
query QUERY_TT_APPROVALS_BY_IDS($ids: [uuid!]!) {
|
||||||
|
tt_approval_queue(where: { id: { _in: $ids } }) {
|
||||||
|
id
|
||||||
|
productivehrs
|
||||||
|
actualhrs
|
||||||
|
rate
|
||||||
|
memo
|
||||||
|
jobid
|
||||||
|
flat_rate
|
||||||
|
employeeid
|
||||||
|
date
|
||||||
|
ciecacode
|
||||||
|
bodyshopid
|
||||||
|
cost_center
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const UPDATE_TT_BY_APPROVAL = gql`
|
||||||
|
mutation UPDATE_TT_BY_APPROVAL(
|
||||||
|
$ttApprovalUpdates: [tt_approval_queue_updates!]!
|
||||||
|
) {
|
||||||
|
update_tt_approval_queue_many(updates: $ttApprovalUpdates) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
approved_at
|
||||||
|
approved_by
|
||||||
|
timeticket {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -170,6 +170,9 @@ const Dms = lazy(() => import("../dms/dms.container"));
|
|||||||
const DmsPayables = lazy(() =>
|
const DmsPayables = lazy(() =>
|
||||||
import("../dms-payables/dms-payables.container")
|
import("../dms-payables/dms-payables.container")
|
||||||
);
|
);
|
||||||
|
const TtApprovals = lazy(() =>
|
||||||
|
import("../tt-approvals/tt-approvals.page.container")
|
||||||
|
);
|
||||||
|
|
||||||
const { Content, Footer } = Layout;
|
const { Content, Footer } = Layout;
|
||||||
|
|
||||||
@@ -370,6 +373,7 @@ export function Manage({ match, conflict, bodyshop }) {
|
|||||||
path={`${match.path}/accounting/exportlogs`}
|
path={`${match.path}/accounting/exportlogs`}
|
||||||
component={ExportLogs}
|
component={ExportLogs}
|
||||||
/>
|
/>
|
||||||
|
<Route exact path={`${match.path}/ttapprovals`} component={TtApprovals} />
|
||||||
<Route exact path={`${match.path}/partsqueue`} component={PartsQueue} />
|
<Route exact path={`${match.path}/partsqueue`} component={PartsQueue} />
|
||||||
<Route exact path={`${match.path}/phonebook`} component={Phonebook} />
|
<Route exact path={`${match.path}/phonebook`} component={Phonebook} />
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||||
|
import TtApprovalsList from "../../components/tt-approvals-list/tt-approvals-list.container";
|
||||||
|
import {
|
||||||
|
setBreadcrumbs,
|
||||||
|
setSelectedHeader,
|
||||||
|
} from "../../redux/application/application.actions";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||||
|
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function TtApprovalsPage({ setBreadcrumbs, setSelectedHeader }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = t("titles.ttapprovals");
|
||||||
|
setSelectedHeader("ttapprovals");
|
||||||
|
setBreadcrumbs([
|
||||||
|
{
|
||||||
|
link: "/manage/ttapprovals",
|
||||||
|
label: t("titles.bc.ttapprovals"),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RbacWrapper action="ttapprovals:view">
|
||||||
|
<TtApprovalsList />
|
||||||
|
</RbacWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(TtApprovalsPage);
|
||||||
@@ -439,6 +439,10 @@
|
|||||||
"list": "Time Tickets -> List",
|
"list": "Time Tickets -> List",
|
||||||
"shiftedit": "Time Tickets -> Shift Edit"
|
"shiftedit": "Time Tickets -> Shift Edit"
|
||||||
},
|
},
|
||||||
|
"ttapprovals": {
|
||||||
|
"approve": "Time Ticket Approval -> Approve",
|
||||||
|
"view": "Time Ticket Approval -> View"
|
||||||
|
},
|
||||||
"users": {
|
"users": {
|
||||||
"editaccess": "Users -> Edit access"
|
"editaccess": "Users -> Edit access"
|
||||||
}
|
}
|
||||||
@@ -1902,6 +1906,7 @@
|
|||||||
"shop_vendors": "Vendors",
|
"shop_vendors": "Vendors",
|
||||||
"temporarydocs": "Temporary Documents",
|
"temporarydocs": "Temporary Documents",
|
||||||
"timetickets": "Time Tickets",
|
"timetickets": "Time Tickets",
|
||||||
|
"ttapprovals": "Time Ticket Approvals",
|
||||||
"vehicles": "Vehicles"
|
"vehicles": "Vehicles"
|
||||||
},
|
},
|
||||||
"jobsactions": {
|
"jobsactions": {
|
||||||
@@ -2733,6 +2738,7 @@
|
|||||||
"shop-vendors": "Vendors",
|
"shop-vendors": "Vendors",
|
||||||
"temporarydocs": "Temporary Documents",
|
"temporarydocs": "Temporary Documents",
|
||||||
"timetickets": "Time Tickets",
|
"timetickets": "Time Tickets",
|
||||||
|
"ttapprovals": "Time Ticket Approvals",
|
||||||
"vehicle-details": "Vehicle: {{vehicle}}",
|
"vehicle-details": "Vehicle: {{vehicle}}",
|
||||||
"vehicles": "Vehicles"
|
"vehicles": "Vehicles"
|
||||||
},
|
},
|
||||||
@@ -2778,6 +2784,7 @@
|
|||||||
"shop_vendors": "Vendors | $t(titles.app)",
|
"shop_vendors": "Vendors | $t(titles.app)",
|
||||||
"temporarydocs": "Temporary Documents | $t(titles.app)",
|
"temporarydocs": "Temporary Documents | $t(titles.app)",
|
||||||
"timetickets": "Time Tickets | $t(titles.app)",
|
"timetickets": "Time Tickets | $t(titles.app)",
|
||||||
|
"ttapprovals": "Time Ticket Approvals | $t(titles.app)",
|
||||||
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)",
|
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)",
|
||||||
"vehicles": "All Vehicles | $t(titles.app)"
|
"vehicles": "All Vehicles | $t(titles.app)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -439,6 +439,10 @@
|
|||||||
"list": "",
|
"list": "",
|
||||||
"shiftedit": ""
|
"shiftedit": ""
|
||||||
},
|
},
|
||||||
|
"ttapprovals": {
|
||||||
|
"approve": "",
|
||||||
|
"view": ""
|
||||||
|
},
|
||||||
"users": {
|
"users": {
|
||||||
"editaccess": ""
|
"editaccess": ""
|
||||||
}
|
}
|
||||||
@@ -1902,6 +1906,7 @@
|
|||||||
"shop_vendors": "Vendedores",
|
"shop_vendors": "Vendedores",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
|
"ttapprovals": "",
|
||||||
"vehicles": "Vehículos"
|
"vehicles": "Vehículos"
|
||||||
},
|
},
|
||||||
"jobsactions": {
|
"jobsactions": {
|
||||||
@@ -2733,6 +2738,7 @@
|
|||||||
"shop-vendors": "",
|
"shop-vendors": "",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
|
"ttapprovals": "",
|
||||||
"vehicle-details": "",
|
"vehicle-details": "",
|
||||||
"vehicles": ""
|
"vehicles": ""
|
||||||
},
|
},
|
||||||
@@ -2778,6 +2784,7 @@
|
|||||||
"shop_vendors": "Vendedores | $t(titles.app)",
|
"shop_vendors": "Vendedores | $t(titles.app)",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
|
"ttapprovals": "",
|
||||||
"vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)",
|
"vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)",
|
||||||
"vehicles": "Todos los vehiculos | $t(titles.app)"
|
"vehicles": "Todos los vehiculos | $t(titles.app)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -439,6 +439,10 @@
|
|||||||
"list": "",
|
"list": "",
|
||||||
"shiftedit": ""
|
"shiftedit": ""
|
||||||
},
|
},
|
||||||
|
"ttapprovals": {
|
||||||
|
"approve": "",
|
||||||
|
"view": ""
|
||||||
|
},
|
||||||
"users": {
|
"users": {
|
||||||
"editaccess": ""
|
"editaccess": ""
|
||||||
}
|
}
|
||||||
@@ -1902,6 +1906,7 @@
|
|||||||
"shop_vendors": "Vendeurs",
|
"shop_vendors": "Vendeurs",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
|
"ttapprovals": "",
|
||||||
"vehicles": "Véhicules"
|
"vehicles": "Véhicules"
|
||||||
},
|
},
|
||||||
"jobsactions": {
|
"jobsactions": {
|
||||||
@@ -2733,6 +2738,7 @@
|
|||||||
"shop-vendors": "",
|
"shop-vendors": "",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
|
"ttapprovals": "",
|
||||||
"vehicle-details": "",
|
"vehicle-details": "",
|
||||||
"vehicles": ""
|
"vehicles": ""
|
||||||
},
|
},
|
||||||
@@ -2778,6 +2784,7 @@
|
|||||||
"shop_vendors": "Vendeurs | $t(titles.app)",
|
"shop_vendors": "Vendeurs | $t(titles.app)",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
|
"ttapprovals": "",
|
||||||
"vehicledetail": "Détails du véhicule {{vehicle} | $t(titles.app)",
|
"vehicledetail": "Détails du véhicule {{vehicle} | $t(titles.app)",
|
||||||
"vehicles": "Tous les véhicules | $t(titles.app)"
|
"vehicles": "Tous les véhicules | $t(titles.app)"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user