feature/IO-2681-Share-To-Teams-Button - checkpoint
This commit is contained in:
@@ -31,6 +31,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 SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
import ShareToTeamsButton from "../share-to-teams/share-to-teams-button.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -130,10 +131,10 @@ export function JobsDetailHeaderActions({
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
const {
|
||||
treatments: { ImEXPay }
|
||||
treatments: { ImEXPay, Share_To_Teams }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["ImEXPay"],
|
||||
names: ["ImEXPay", "Share_To_Teams"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
|
||||
@@ -962,6 +963,16 @@ export function JobsDetailHeaderActions({
|
||||
}
|
||||
);
|
||||
|
||||
// TODO REMEMBER TO REVERT
|
||||
// if (Share_To_Teams?.treatment === "on") {
|
||||
if (true) {
|
||||
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",
|
||||
|
||||
@@ -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-button.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
|
||||
|
||||
@@ -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-button.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>
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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-button.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"
|
||||
? [
|
||||
|
||||
135
client/src/components/share-to-teams/share-to-teams-button.jsx
Normal file
135
client/src/components/share-to-teams/share-to-teams-button.jsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import React, { useMemo } from "react";
|
||||
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.
|
||||
* @param {Object} [props.additionalProps] - Additional props to pass to the rendered component.
|
||||
* @returns {React.ReactElement} A button or text link for sharing to Microsoft Teams.
|
||||
*/
|
||||
const ShareToTeamsButton = ({
|
||||
bodyshop,
|
||||
messageTextOverride,
|
||||
urlOverride,
|
||||
pageTitleOverride,
|
||||
noIcon = false,
|
||||
noIconStyle = {},
|
||||
buttonStyle = {},
|
||||
buttonIconStyle = {},
|
||||
linkText,
|
||||
...additionalProps
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
treatments: { Share_To_Teams }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Share_To_Teams"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const currentUrl = useMemo(
|
||||
() =>
|
||||
urlOverride ||
|
||||
encodeURIComponent(`${window.location.origin}${location.pathname}${location.search}${location.hash}`),
|
||||
[urlOverride, location]
|
||||
);
|
||||
|
||||
const pageTitle = useMemo(
|
||||
() =>
|
||||
pageTitleOverride ||
|
||||
encodeURIComponent(typeof document !== "undefined" ? document.title : t("general.actions.sharetoteams")),
|
||||
[pageTitleOverride, t]
|
||||
);
|
||||
|
||||
const messageText = useMemo(
|
||||
() => messageTextOverride || encodeURIComponent(t("general.actions.sharetoteams")),
|
||||
[messageTextOverride, t]
|
||||
);
|
||||
|
||||
// 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} {...additionalProps}>
|
||||
{!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}
|
||||
{...additionalProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ShareToTeamsButton.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]),
|
||||
additionalProps: PropTypes.object
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(ShareToTeamsButton);
|
||||
@@ -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-button.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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,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,10 +255,11 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")}
|
||||
title={view ? t("tasks.actions.view") : existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")}
|
||||
open={open}
|
||||
okText={t("general.actions.save")}
|
||||
width="50%"
|
||||
cancelText={!isTouched ? t("general.actions.ok") : t("general.actions.cancel")}
|
||||
onOk={() => {
|
||||
removeTaskIdFromUrl();
|
||||
form.submit();
|
||||
@@ -283,6 +284,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}
|
||||
|
||||
@@ -46,7 +46,8 @@ export function AllTasksPageContainer({ setBreadcrumbs, setSelectedHeader, setTa
|
||||
if (taskId) {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
taskId
|
||||
taskId,
|
||||
view: true
|
||||
}
|
||||
});
|
||||
urlParams.delete("taskid");
|
||||
|
||||
@@ -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.",
|
||||
@@ -3182,7 +3184,8 @@
|
||||
"tasks": {
|
||||
"actions": {
|
||||
"edit": "Edit Task",
|
||||
"new": "New Task"
|
||||
"new": "New Task",
|
||||
"view": "View Task"
|
||||
},
|
||||
"buttons": {
|
||||
"allTasks": "All",
|
||||
|
||||
Reference in New Issue
Block a user