Compare commits

..

19 Commits

Author SHA1 Message Date
Patrick Fic
0fd8bcb1b1 IO-3076 Add cron trigger for RO usage report. 2025-02-04 08:28:06 -08:00
Dave Richer
bd6f300c8d Merged in feature/IO-2970-Production-Board-Unassigned-Filter (pull request #2096)
feature/IO-2970-Production-Board-Unassigned-Filter - Implementation
2025-01-31 18:24:50 +00:00
Dave Richer
ac2fbaf6f7 feature/IO-2970-Production-Board-Unassigned-Filter - Implementation 2025-01-31 13:23:36 -05:00
Allan Carr
f409acc7fd Merged in feature/IO-3116-Production-Flag-Translation (pull request #2093)
IO-3116 Production Flag Translation

Approved-by: Dave Richer
2025-01-31 15:59:15 +00:00
Allan Carr
06dcb20b2b Merged in feature/IO-3074-Mark-as-PST-Exempt-Job-Create (pull request #2094)
IO-3074 Mark as PST Exempt in Manaul Job Creation

Approved-by: Dave Richer
2025-01-31 15:58:42 +00:00
Allan Carr
f4fed0db9d IO-3074 Mark as PST Exempt in Manaul Job Creation
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-01-30 15:25:39 -08:00
Allan Carr
8430f500ef IO-3116 Production Flag Translation
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-01-30 14:30:47 -08:00
Dave Richer
c8f5c3ed9e release/2025-01-31 - Fix unused import 2025-01-30 15:36:47 -05:00
Dave Richer
312795618e Merged in feature/IO-2681-Share-To-Teams-Button (pull request #2090)
Feature/IO-2681 Share To Teams Button
2025-01-30 20:17:24 +00:00
Dave Richer
35b5645d6f feature/IO-2681-Share-To-Teams-Button - Missing translation 2025-01-30 15:16:56 -05:00
Dave Richer
a49d845f50 feature/IO-2681-Share-To-Teams-Button - Merge release 2025-01-30 15:13:48 -05:00
Dave Richer
9d44540ca8 feature/IO-2681-Share-To-Teams-Button - Final revisions. 2025-01-30 15:06:52 -05:00
Allan Carr
cc95d3bd44 Merged in feature/IO-3115-Print-Center-on-Job-Close (pull request #2088)
IO-3115 Print Center on Job Close

Approved-by: Dave Richer
2025-01-30 17:12:11 +00:00
Allan Carr
648c47cde2 IO-3115 Change Icon to internal button prop
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-01-30 09:12:58 -08:00
Allan Carr
17cf6e7696 Merged in feature/IO-1582-Temp-Docs-to-Exported-Files (pull request #2087)
IO-1582 Temp Docs to Exported/Invoiced Files

Approved-by: Dave Richer
2025-01-30 16:42:54 +00:00
Allan Carr
7326ffbae6 Merged in feature/IO-3114-Quick-Intake (pull request #2086)
Feature/IO-3114 Quick Intake

Approved-by: Dave Richer
2025-01-30 16:42:36 +00:00
Allan Carr
b2f73c4fba IO-3115 Print Center on Job Close
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-01-29 16:55:31 -08:00
Allan Carr
6628d43e12 IO-1582 Temp Docs to Exported/Invoiced Files
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-01-29 15:43:38 -08:00
Dave Richer
a064b8e07e feature/IO-2681-Share-To-Teams-Button - checkpoint 2025-01-29 18:19:34 -05:00
21 changed files with 315 additions and 75 deletions

View File

@@ -1,26 +1,23 @@
import { Collapse, Form, Input, InputNumber, Select, Space, Switch } 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 InstanceRenderManager from "../../utils/instanceRenderMgr";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
import JobsDetailChangeFilehandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
import JobsDetailRatesLabor from "../jobs-detail-rates/jobs-detail-rates.labor.component";
import JobsDetailRatesMaterials from "../jobs-detail-rates/jobs-detail-rates.materials.component";
import JobsDetailRatesOther from "../jobs-detail-rates/jobs-detail-rates.other.component";
import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -199,7 +196,9 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
</Collapse.Panel>
<Collapse.Panel forceRender key="financial" header={t("menus.jobsdetail.financials")}>
<JobsDetailRatesChangeButton form={form} />
<JobsMarkPstExempt form={form} />
{InstanceRenderManager({
imex: <JobsMarkPstExempt form={form} />
})}
<LayoutFormRow>
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
<CurrencyInput min={0} />
@@ -246,7 +245,6 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
</LayoutFormRow>
)
})}
<LayoutFormRow>
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
<CurrencyInput />

View File

@@ -32,6 +32,7 @@ import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -133,10 +134,10 @@ export function JobsDetailHeaderActions({
const notification = useNotification();
const {
treatments: { ImEXPay }
treatments: { ImEXPay, Share_To_Teams }
} = useSplitTreatments({
attributes: {},
names: ["ImEXPay"],
names: ["ImEXPay", "Share_To_Teams"],
splitKey: bodyshop && bodyshop.imexshopid
});
@@ -971,6 +972,14 @@ export function JobsDetailHeaderActions({
}
);
if (Share_To_Teams?.treatment === "on") {
menuItems.push({
key: "sharetoteams",
id: "job-actions-sharetoteams",
label: <ShareToTeamsButton noIcon={true} urlOverride={`${window.location.origin}${window.location.pathname}`} />
});
}
menuItems.push({
key: "exportcustdata",
id: "job-actions-exportcustdata",

View File

@@ -1,14 +1,14 @@
import { useApolloClient } from "@apollo/client";
import { Button, Form, Popover, Space } from "antd";
import axios from "axios";
import React, { useMemo, useState } from "react";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobSearchSelect from "../job-search-select/job-search-select.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -134,7 +134,7 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages, callback
]}
name={"jobid"}
>
<JobSearchSelect />
<JobSearchSelect notExported={false} notInvoiced={false} />
</Form.Item>
</Form>
<Space>

View File

@@ -1,5 +1,5 @@
import { Button, Form, Popover, Space } from "antd";
import React, { useState } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -60,7 +60,7 @@ export function JobsDocumentsLocalGalleryReassign({ bodyshop, jobid, allMedia, g
]}
name={"jobid"}
>
<JobSearchSelect />
<JobSearchSelect notExported={false} notInvoiced={false}/>
</Form.Item>
</Form>
<Space>

View File

@@ -19,6 +19,7 @@ import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component";
import PartsOrderDrawer from "./parts-order-list-table-drawer.component";
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -66,19 +67,21 @@ export function PartsOrderListTableComponent({
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery;
// label: <ShareToTeamsButton noIcon={true} urlOverride={`${window.location.origin}${window.location.pathname}`} />
const recordActions = (record, showView = false) => (
<Space direction="horizontal" wrap>
<ShareToTeamsButton
linkText={""}
urlOverride={`${window.location.origin}/manage/jobs/${job.id}?partsorderid=${record.id}&tab=partssublet `}
/>
{showView && (
<Button
icon={<EyeFilled />}
onClick={() => {
handleOnRowClick(record);
}}
>
<EyeFilled />
</Button>
/>
)}
<Button
disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid}
onClick={() => {
@@ -106,6 +109,7 @@ export function PartsOrderListTableComponent({
</Button>
<Button
title={t("tasks.buttons.create")}
icon={<FaTasks />}
onClick={() => {
setTaskUpsertContext({
context: {
@@ -114,9 +118,7 @@ export function PartsOrderListTableComponent({
}
});
}}
>
<FaTasks />
</Button>
/>
<Popconfirm
title={t("parts_orders.labels.confirmdelete")}
disabled={jobRO}
@@ -137,9 +139,7 @@ export function PartsOrderListTableComponent({
});
}}
>
<Button disabled={jobRO}>
<DeleteFilled />
</Button>
<Button disabled={jobRO} icon={<DeleteFilled />} />
</Popconfirm>
<Button

View File

@@ -1,9 +1,14 @@
import { Button, Input, Space, Spin } from "antd";
import React, { useState } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { ExclamationCircleFilled, ExclamationCircleOutlined } from "@ant-design/icons";
import {
ExclamationCircleFilled,
ExclamationCircleOutlined,
UserDeleteOutlined,
UsergroupDeleteOutlined
} from "@ant-design/icons";
import { selectBodyshop } from "../../redux/user/user.selectors";
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
@@ -20,6 +25,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardFilte
export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) {
const { t } = useTranslation();
const [alertFilter, setAlertFilter] = useState(false);
const [unassignedFilter, setUnassignedFilter] = useState(false);
const toggleAlertFilter = () => {
const newAlertFilter = !alertFilter;
@@ -27,6 +33,12 @@ export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading })
setFilter({ ...filter, alert: newAlertFilter });
};
const toggleUnassignedFilter = () => {
const newUnassignedFilter = !unassignedFilter;
setUnassignedFilter(newUnassignedFilter);
setFilter({ ...filter, unassigned: newUnassignedFilter });
};
return (
<Space wrap>
{loading && <Spin />}
@@ -52,6 +64,13 @@ export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading })
>
{t("production.labels.alerts")}
</Button>
<Button
type={unassignedFilter ? "primary" : "default"}
onClick={toggleUnassignedFilter}
icon={unassignedFilter ? <UserDeleteOutlined /> : <UsergroupDeleteOutlined />}
>
{t("production.labels.unassigned")}
</Button>
</Space>
);
}

View File

@@ -20,6 +20,8 @@ import dayjs from "../../utils/day";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
import { SiMicrosoftteams } from "react-icons/si";
const cardColor = (ssbuckets, totalHrs) => {
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
@@ -417,9 +419,20 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
title={!isBodyEmpty ? headerContent : null}
extra={
!isBodyEmpty && (
<Link to={{ search: `?selected=${card.id}` }}>
<EyeFilled />
</Link>
<Space>
<ShareToTeamsButton
noIcon={true}
linkText={
<div className="share-to-teams-badge">
<SiMicrosoftteams />
</div>
}
urlOverride={`${window.location.origin}/manage/jobs/${card.id}`}
/>
<Link to={{ search: `?selected=${card.id}` }}>
<EyeFilled />
</Link>
</Space>
)
}
>

View File

@@ -10,6 +10,16 @@
.height-preserving-container {
}
.share-to-teams-badge {
background-color: #cccccc;
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.react-trello-column-header {
font-weight: bold;
cursor: pointer;

View File

@@ -29,7 +29,7 @@ const sortByParentId = (arr) => {
// Function to create board data based on statuses and jobs, with optional filtering
export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
const { search, employeeId, alert } = filter;
const { search, employeeId, alert, unassigned } = filter;
const lanes = statuses.map((status) => ({
id: status,
@@ -40,6 +40,13 @@ export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
let filteredJobs =
(search === "" || !search) && !employeeId ? data : data.filter((job) => checkFilter(search, employeeId, job));
// Apply "Unassigned" filter
if (unassigned) {
filteredJobs = filteredJobs.filter(
(job) => !job.employee_body && !job.employee_prep && !job.employee_refinish && !job.employee_csr
);
}
// Filter jobs by selectedMdInsCos if it has values
if (cardSettings?.selectedMdInsCos?.length > 0) {
filteredJobs = filteredJobs.filter((job) => cardSettings.selectedMdInsCos.includes(job.ins_co_nm));

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Col, Form, Popover, Row, Tabs } from "antd";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
import { defaultKanbanSettings, mergeWithDefaults } from "./defaultKanbanSettings.js";
@@ -11,6 +11,7 @@ import FilterSettings from "./FilterSettings.jsx";
import PropTypes from "prop-types";
import { isFunction } from "lodash";
import { useNotification } from "../../../contexts/Notifications/notificationContext.jsx";
import { SettingOutlined } from "@ant-design/icons";
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
const [form] = Form.useForm();
@@ -153,7 +154,7 @@ function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bod
return (
<Popover content={overlay} open={open} placement="topRight">
<Button loading={loading} onClick={() => setOpen(!open)}>
<Button icon={<SettingOutlined />} loading={loading} onClick={() => setOpen(!open)}>
{t("production.settings.board_settings")}
</Button>
</Popover>

View File

@@ -27,6 +27,7 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c
import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
const getEmployeeName = (employeeId, employees) => {
const employee = employees.find((e) => e.id === employeeId);
@@ -41,7 +42,17 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
dataIndex: "viewdetail",
key: "viewdetail",
ellipsis: true,
render: (text, record) => <Link to={{ search: `?selected=${record.id}` }}>{i18n.t("general.labels.view")}</Link>
render: (text, record) => (
<Space>
<Link to={{ search: `?selected=${record.id}` }}>{i18n.t("general.labels.view")}</Link>
<ShareToTeamsButton
noIcon={true}
linkText={"Share"}
noIconStyle={{ color: "#1890ff" }}
urlOverride={`${window.location.origin}/manage/jobs/${record.id}`}
/>
</Space>
)
},
...(Enhanced_Payroll.treatment === "on"
? [

View File

@@ -1,5 +1,5 @@
import { Button, Dropdown } from "antd";
import React, { useState } from "react";
import { useState } from "react";
import { TemplateList } from "../../utils/TemplateConstants";
import { useTranslation } from "react-i18next";
import { GenerateDocument } from "../../utils/RenderTemplate";
@@ -7,6 +7,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { PrinterFilled } from "@ant-design/icons";
const ProdTemplates = TemplateList("production");
const { production_by_technician_one, production_by_category_one, production_by_repair_status_one } =
@@ -123,7 +124,9 @@ export function ProductionListPrint({ bodyshop }) {
return (
<Dropdown trigger="click" menu={menu}>
<Button loading={loading}>{t("general.labels.print")}</Button>
<Button icon={<PrinterFilled />} loading={loading}>
{t("general.labels.print")}
</Button>
</Dropdown>
);
}

View File

@@ -0,0 +1,119 @@
import PropTypes from "prop-types";
import { Button } from "antd";
import { useLocation } from "react-router-dom";
import { SiMicrosoftteams } from "react-icons/si";
import { useTranslation } from "react-i18next";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
/**
* ShareToTeamsButton component for sharing content to Microsoft Teams via an HTTP link.
*
* This component creates a button or link that opens the Microsoft Teams share dialog with
* the provided URL, title, and message text through query parameters. The popup window is centered on the screen.
*
* @param {Object} props - The component's props.
* @param {string} [props.messageTextOverride] - Custom message text for sharing.
* @param {string} [props.urlOverride] - Custom URL to share instead of the current page's URL.
* @param {string} [props.pageTitleOverride] - Custom title for the shared page.
* @param {boolean} [props.noIcon=false] - If true, renders as a simple text link instead of a button with an icon.
* @param {Object} [props.noIconStyle={}] - Style object for the text link when noIcon is true.
* @param {Object} [props.buttonStyle={}] - Style object for the Ant Design button.
* @param {Object} [props.buttonIconStyle={}] - Style object for the icon within the button.
* @param {string} [props.linkText] - Text to display on the button or link.
* @returns {React.ReactElement} A button or text link for sharing to Microsoft Teams.
*/
const ShareToTeamsComponent = ({
bodyshop,
messageTextOverride,
urlOverride,
pageTitleOverride,
noIcon = false,
noIconStyle = {},
buttonStyle = {},
buttonIconStyle = {},
linkText
}) => {
const location = useLocation();
const { t } = useTranslation();
const {
treatments: { Share_To_Teams }
} = useSplitTreatments({
attributes: {},
names: ["Share_To_Teams"],
splitKey: bodyshop && bodyshop.imexshopid
});
const currentUrl =
urlOverride ||
encodeURIComponent(`${window.location.origin}${location.pathname}${location.search}${location.hash}`);
const pageTitle =
pageTitleOverride ||
encodeURIComponent(typeof document !== "undefined" ? document.title : t("general.actions.sharetoteams"));
const messageText = messageTextOverride || encodeURIComponent(t("general.actions.sharetoteams"));
// Construct the Teams share URL with parameters
const teamsShareUrl = `https://teams.microsoft.com/share?href=${currentUrl}&preText=${messageText}&title=${pageTitle}`;
// Function to open the centered share link in a new window/tab
const handleShare = () => {
const screenWidth = window.screen.width;
const screenHeight = window.screen.height;
const windowWidth = 600;
const windowHeight = 400;
const left = screenWidth / 2 - windowWidth / 2;
const top = screenHeight / 2 - windowHeight / 2;
const windowFeatures = `width=${windowWidth},height=${windowHeight},left=${left},top=${top}`;
// noinspection JSIgnoredPromiseFromCall
window.open(teamsShareUrl, "_blank", windowFeatures);
};
if (Share_To_Teams?.treatment !== "on") {
return null;
}
if (noIcon) {
return (
<div style={{ cursor: "pointer", ...noIconStyle }} onClick={handleShare}>
{!linkText ? t("general.actions.sharetoteams") : linkText}
</div>
);
}
return (
<Button
style={{
backgroundColor: "#6264A7",
borderColor: "#6264A7",
color: "#FFFFFF",
...buttonStyle
}}
icon={<SiMicrosoftteams style={{ color: "#FFFFFF", ...buttonIconStyle }} />}
onClick={handleShare}
title={linkText === null ? t("general.actions.sharetoteams") : linkText}
/>
);
};
ShareToTeamsComponent.propTypes = {
messageTextOverride: PropTypes.string,
urlOverride: PropTypes.string,
pageTitleOverride: PropTypes.string,
noIcon: PropTypes.bool,
noIconStyle: PropTypes.object,
buttonStyle: PropTypes.object,
buttonIconStyle: PropTypes.object,
linkText: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
};
export default connect(mapStateToProps)(ShareToTeamsComponent);

View File

@@ -18,6 +18,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { pageLimit } from "../../utils/config";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
import dayjs from "../../utils/day";
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
/**
* Task List Component
@@ -266,8 +267,13 @@ function TaskListComponent({
width: "8%",
render: (text, record) => (
<Space direction="horizontal">
<ShareToTeamsButton
linkText=""
urlOverride={`https://localhost:3000/manage/tasks/alltasks?taskid=${record.id}`}
/>
<Button
title={t("tasks.buttons.edit")}
icon={<EditFilled />}
onClick={() => {
setTaskUpsertContext({
context: {
@@ -276,18 +282,18 @@ function TaskListComponent({
}
});
}}
>
<EditFilled />
</Button>
/>
<Button
title={t("tasks.buttons.complete")}
onClick={() => toggleCompletedStatus(record.id, record.completed)}
>
{record.completed ? <CheckCircleOutlined /> : <CheckCircleFilled />}
</Button>
<Button title={t("tasks.buttons.delete")} onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
{record.deleted ? <DeleteOutlined /> : <DeleteFilled />}
</Button>
icon={record.completed ? <CheckCircleOutlined /> : <CheckCircleFilled />}
/>
<Button
title={t("tasks.buttons.delete")}
onClick={() => toggleDeletedStatus(record.id, record.deleted)}
icon={record.deleted ? <DeleteOutlined /> : <DeleteFilled />}
/>
</Space>
)
}

View File

@@ -1,6 +1,6 @@
import { useMutation, useQuery } from "@apollo/client";
import { Form, Modal } from "antd";
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -35,7 +35,7 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
const [updateTask] = useMutation(MUTATION_UPDATE_TASK);
const { open, context } = taskUpsert;
const { jobid, joblineid, billid, partsorderid, taskId, existingTask, query } = context;
const { jobid, joblineid, billid, partsorderid, taskId, existingTask, query, view } = context;
const [form] = Form.useForm();
const [selectedJobId, setSelectedJobId] = useState(null);
const [selectedJobDetails, setSelectedJobDetails] = useState(null);
@@ -255,16 +255,17 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
}
};
const taskTitle = useMemo(() => {
return existingTask ? t("tasks.actions.edit") : t("tasks.actions.new");
}, [existingTask, t]);
return (
<Modal
title={<span id="task-upsert-modal-title">{taskTitle}</span>}
title={
<span id="task-upsert-modal-title">
{view ? t("tasks.actions.view") : existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")}
</span>
}
open={open}
okText={t("general.actions.save")}
width="50%"
cancelText={!isTouched ? t("general.actions.ok") : t("general.actions.cancel")}
onOk={() => {
removeTaskIdFromUrl();
form.submit();
@@ -289,6 +290,7 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
loading={loading || (taskId && taskLoading)}
error={error}
data={data}
view={view}
existingTask={existingTask || taskData?.tasks_by_pk}
selectedJobId={selectedJobId}
setSelectedJobId={setSelectedJobId}

View File

@@ -1,4 +1,5 @@
import { DeleteFilled } from "@ant-design/icons";
import { DeleteFilled, PrinterFilled } from "@ant-design/icons";
import { PageHeader } from "@ant-design/pro-layout";
import { useApolloClient, useMutation } from "@apollo/client";
import {
Alert,
@@ -16,32 +17,31 @@ import {
Switch,
Typography
} from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import React, { useState } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
// import { useNavigate } from 'react-router-dom';
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import Dinero from "dinero.js";
import dayjs from "../../utils/day";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component";
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../../components/form-items-formatted/currency-form-item.component";
import JobCloseRoGuardContainer from "../../components/job-close-ro-guard/job-close-ro-guard.container";
import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component";
import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component";
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions.js";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import JobCloseRoGuardContainer from "../../components/job-close-ro-guard/job-close-ro-guard.container";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import dayjs from "../../utils/day";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -49,10 +49,17 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
setPrintCenterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "printCenter"
})
)
});
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, setPrintCenterContext }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const client = useApolloClient();
@@ -171,7 +178,6 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
extra={
<Space>
<JobsCloseAutoAllocate joblines={job.joblines} form={form} disabled={!!job.date_exported || jobRO} />
<Popconfirm
onConfirm={() => form.submit()}
disabled={jobRO}
@@ -188,6 +194,21 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
<Button disabled={job.date_exported || !jobRO}>{t("jobs.actions.sendtodms")}</Button>
</Link>
)}
<Button
onClick={() => {
setPrintCenterContext({
context: {
id: job.id,
job: job,
type: "job"
}
});
}}
key="printing"
icon={<PrinterFilled />}
>
{t("jobs.actions.printCenter")}
</Button>
<JobsScoreboardAdd job={job} disabled={false} />
</Space>
}

View File

@@ -46,7 +46,8 @@ export function AllTasksPageContainer({ setBreadcrumbs, setSelectedHeader, setTa
if (taskId) {
setTaskUpsertContext({
context: {
taskId
taskId,
view: true
}
});
urlParams.delete("taskid");

View File

@@ -1193,7 +1193,9 @@
"submit": "Submit",
"tryagain": "Try Again",
"view": "View",
"viewreleasenotes": "See What's Changed"
"viewreleasenotes": "See What's Changed",
"sharetoteams": "Share to Teams",
"ok": "Ok"
},
"errors": {
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
@@ -1525,7 +1527,7 @@
"addDocuments": "Add Job Documents",
"addNote": "Add Note",
"addtopartsqueue": "Add to Parts Queue",
"addtoproduction": "Add In Production Flag",
"addtoproduction": "Add to Production",
"addtoscoreboard": "Add to Scoreboard",
"allocate": "Allocate",
"autoallocate": "Auto Allocate",
@@ -1568,7 +1570,7 @@
"printCenter": "Print Center",
"recalculate": "Recalculate",
"reconcile": "Reconcile",
"removefromproduction": "Remove In Production Flag",
"removefromproduction": "Remove from Production",
"schedule": "Schedule",
"sendcsi": "Send CSI",
"sendpartspricechange": "Send Parts Price Change",
@@ -2857,7 +2859,8 @@
"touchtime": "T/T",
"vertical": "Vertical",
"viewname": "View Name",
"wide": "Wide"
"wide": "Wide",
"unassigned": "Unassigned"
},
"options": {
"horizontal": "Horizontal",
@@ -3183,7 +3186,8 @@
"tasks": {
"actions": {
"edit": "Edit Task",
"new": "New Task"
"new": "New Task",
"view": "View Task"
},
"buttons": {
"allTasks": "All",

View File

@@ -1193,7 +1193,9 @@
"submit": "",
"tryagain": "",
"view": "",
"viewreleasenotes": ""
"viewreleasenotes": "",
"sharetoteams": "",
"ok": ""
},
"errors": {
"fcm": "",
@@ -2857,7 +2859,8 @@
"touchtime": "",
"vertical": "",
"viewname": "",
"wide": ""
"wide": "",
"unassigned": ""
},
"options": {
"horizontal": "",
@@ -3183,7 +3186,8 @@
"tasks": {
"actions": {
"edit": "",
"new": ""
"new": "",
"view": ""
},
"buttons": {
"allTasks": "",

View File

@@ -1193,7 +1193,9 @@
"submit": "",
"tryagain": "",
"view": "",
"viewreleasenotes": ""
"viewreleasenotes": "",
"sharetoteams": "",
"ok": ""
},
"errors": {
"fcm": "",
@@ -2857,7 +2859,8 @@
"touchtime": "",
"vertical": "",
"viewname": "",
"wide": ""
"wide": "",
"unassigned": ""
},
"options": {
"horizontal": "",
@@ -3183,7 +3186,8 @@
"tasks": {
"actions": {
"edit": "",
"new": ""
"new": "",
"view": ""
},
"buttons": {
"allTasks": "",

View File

@@ -39,3 +39,11 @@
headers:
- name: event-secret
value_from_env: EVENT_SECRET
- name: Rome Usage Report
webhook: '{{HASURA_API_URL}}/data/usagereport'
schedule: 0 12 * * 5
include_in_metadata: true
payload: {}
headers:
- name: x-imex-auth
value_from_env: DATAPUMP_AUTH