Compare commits

..

2 Commits

Author SHA1 Message Date
Allan Carr
dafe9de753 IO-3450 Grammer Correction
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-11-25 18:10:10 -08:00
Allan Carr
78a8474a24 IO-3450 Additional Crisp Segments
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-11-25 17:56:19 -08:00
8 changed files with 95 additions and 99 deletions

View File

@@ -35,14 +35,16 @@ export function JobsDocumentsImgproxyDownloadButton({ galleryImages, identifier,
...galleryImages.other.filter((image) => image.isSelected)
];
const downloadProgress = ({ loaded }) => {
setDownload((currentDownloadState) => ({
downloaded: loaded ?? 0,
speed: (loaded ?? 0) - (currentDownloadState?.downloaded ?? 0)
}));
};
function downloadProgress(progressEvent) {
setDownload((currentDownloadState) => {
return {
downloaded: progressEvent.loaded || 0,
speed: (progressEvent.loaded || 0) - ((currentDownloadState && currentDownloadState.downloaded) || 0)
};
});
}
const standardMediaDownload = (bufferData) => {
function standardMediaDownload(bufferData) {
try {
const a = document.createElement("a");
const url = window.URL.createObjectURL(new Blob([bufferData]));
@@ -53,26 +55,29 @@ export function JobsDocumentsImgproxyDownloadButton({ galleryImages, identifier,
setLoading(false);
setDownload(null);
}
};
}
const handleDownload = async () => {
logImEXEvent("jobs_documents_download");
setLoading(true);
try {
const { data } = await axios({
const response = await axios({
url: "/media/imgproxy/download",
method: "POST",
responseType: "blob",
data: { jobId, documentids: imagesToDownload.map((_) => _.id) },
onDownloadProgress: downloadProgress
});
// Use the response data (Blob) to trigger download
standardMediaDownload(data);
} catch {
// handle error (optional)
} finally {
setLoading(false);
setDownload(null);
// Use the response data (Blob) to trigger download
standardMediaDownload(response.data);
} catch {
setLoading(false);
setDownload(null);
// handle error (optional)
}
};

View File

@@ -76,14 +76,14 @@ function JobsDocumentsImgproxyComponent({
<SyncOutlined />
</Button>
<JobsDocumentsGallerySelectAllComponent galleryImages={galleryImages} setGalleryImages={setGalleryImages} />
{!billId && (
<JobsDocumentsGalleryReassign galleryImages={galleryImages} callback={fetchThumbnails || refetch} />
)}
<JobsDocumentsDownloadButton galleryImages={galleryImages} identifier={downloadIdentifier} jobId={jobId} />
<JobsDocumentsDeleteButton
galleryImages={galleryImages}
deletionCallback={billsCallback || fetchThumbnails || refetch}
/>
{!billId && (
<JobsDocumentsGalleryReassign galleryImages={galleryImages} callback={fetchThumbnails || refetch} />
)}
</Space>
</Col>
{!hasMediaAccess && (

View File

@@ -67,7 +67,7 @@ export default function JobsDocumentsImgproxyDeleteButton({ galleryImages, delet
okButtonProps={{ danger: true }}
cancelText={t("general.actions.cancel")}
>
<Button danger disabled={imagesToDelete.length < 1} loading={loading}>
<Button disabled={imagesToDelete.length < 1} loading={loading}>
{t("documents.actions.delete")}
</Button>
</Popconfirm>

View File

@@ -107,8 +107,8 @@ export function JobsDocumentsLocalGallery({
<a href={CreateExplorerLinkForJob({ jobid: job.id })}>
<Button>{t("documents.labels.openinexplorer")}</Button>
</a>
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
<JobsDocumentsLocalGalleryReassign jobid={job.id} />
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
<JobsLocalGalleryDownloadButton job={job} />
<JobsDocumentsLocalDeleteButton jobid={job.id} />
</Space>

View File

@@ -28,8 +28,6 @@ export function JobsDocumentsLocalDeleteButton({ bodyshop, getJobMedia, allMedia
const [loading, setLoading] = useState(false);
const imagesToDelete = (allMedia?.[jobid] || []).filter((i) => i.isSelected);
const handleDelete = async () => {
logImEXEvent("job_documents_delete");
setLoading(true);
@@ -38,7 +36,7 @@ export function JobsDocumentsLocalDeleteButton({ bodyshop, getJobMedia, allMedia
`${bodyshop.localmediaserverhttp}/jobs/delete`,
{
jobid: jobid,
files: imagesToDelete.map((i) => i.filename)
files: (allMedia?.[jobid] || []).filter((i) => i.isSelected).map((i) => i.filename)
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
@@ -62,17 +60,14 @@ export function JobsDocumentsLocalDeleteButton({ bodyshop, getJobMedia, allMedia
return (
<Popconfirm
disabled={imagesToDelete.length < 1}
icon={<QuestionCircleOutlined style={{ color: "red" }} />}
onConfirm={handleDelete}
title={t("documents.labels.confirmdelete")}
okText={t("general.actions.delete")}
okButtonProps={{ danger: true }}
okButtonProps={{ type: "danger" }}
cancelText={t("general.actions.cancel")}
>
<Button danger disabled={imagesToDelete.length < 1} loading={loading}>
{t("documents.actions.delete")}
</Button>
<Button loading={loading}>{t("documents.actions.delete")}</Button>
</Popconfirm>
);
}

View File

@@ -1,8 +1,8 @@
import { Button, Space } from "antd";
import { Button } from "antd";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import cleanAxios from "../../utils/CleanAxios";
import formatBytes from "../../utils/formatbytes";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectAllMedia } from "../../redux/media/media.selectors";
@@ -19,63 +19,45 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobsLocalGalleryDown
export function JobsLocalGalleryDownloadButton({ bodyshop, allMedia, job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [download, setDownload] = useState(false);
const [download, setDownload] = useState(null);
const imagesToDownload = (allMedia?.[job.id] || []).filter((i) => i.isSelected);
const downloadProgress = ({ loaded }) => {
setDownload((currentDownloadState) => ({
downloaded: loaded || 0,
speed: (loaded || 0) - (currentDownloadState?.downloaded || 0)
}));
};
const standardMediaDownload = (bufferData, filename) => {
try {
const a = document.createElement("a");
const url = window.URL.createObjectURL(new Blob([bufferData]));
a.href = url;
a.download = `${filename}.zip`;
a.click();
} catch {
setLoading(false);
setDownload(null);
}
};
function downloadProgress(progressEvent) {
setDownload((currentDownloadState) => {
return {
downloaded: progressEvent.loaded || 0,
speed: (progressEvent.loaded || 0) - (currentDownloadState?.downloaded || 0)
};
});
}
const handleDownload = async () => {
const { localmediaserverhttp, localmediatoken } = bodyshop;
const { id, ro_number } = job;
setLoading(true);
try {
const response = await cleanAxios.post(
`${localmediaserverhttp}/jobs/download`,
{
jobid: id,
files: imagesToDownload.map((i) => i.filename)
},
{
headers: { ims_token: localmediatoken },
responseType: "arraybuffer",
onDownloadProgress: downloadProgress
}
);
standardMediaDownload(response.data, ro_number);
} catch {
// handle error (optional)
} finally {
setLoading(false);
setDownload(null);
}
const theDownloadedZip = await cleanAxios.post(
`${bodyshop.localmediaserverhttp}/jobs/download`,
{
jobid: job.id,
files: (allMedia?.[job.id] || []).filter((i) => i.isSelected).map((i) => i.filename)
},
{
headers: { ims_token: bodyshop.localmediatoken },
responseType: "arraybuffer",
onDownloadProgress: downloadProgress
}
);
setDownload(null);
standardMediaDownload(theDownloadedZip.data, job.ro_number);
};
return (
<Button disabled={imagesToDownload < 1} loading={download || loading} onClick={handleDownload}>
<Space>
<span>{t("documents.actions.download")}</span>
{download && <span>{`(${formatBytes(download.downloaded)} @ ${formatBytes(download.speed)} / second)`}</span>}
</Space>
<Button loading={!!download} onClick={handleDownload}>
{t("documents.actions.download")}
</Button>
);
}
function standardMediaDownload(bufferData, filename) {
const a = document.createElement("a");
const url = window.URL.createObjectURL(new Blob([bufferData]));
a.href = url;
a.download = `${filename}.zip`;
a.click();
}

View File

@@ -50,7 +50,7 @@ import {
} from "./user.actions";
import UserActionTypes from "./user.types";
//import * as amplitude from '@amplitude/analytics-browser';
import posthog from 'posthog-js';
import posthog from "posthog-js";
const fpPromise = FingerprintJS.load();
@@ -269,11 +269,11 @@ export function* signInSuccessSaga({ payload }) {
instanceSeg,
...(isParts
? [
InstanceRenderManager({
imex: "ImexPartsManagement",
rome: "RomePartsManagement"
})
]
InstanceRenderManager({
imex: "ImexPartsManagement",
rome: "RomePartsManagement"
})
]
: [])
];
window.$crisp.push(["set", "session:segments", [segs]]);
@@ -375,17 +375,31 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
const isParts = yield select((state) => state.application.isPartsEntry === true);
const instanceSeg = InstanceRenderManager({ imex: "imex", rome: "rome" });
let featureSegments;
if (payload.features?.allAccess === true) {
featureSegments = ["allAccess"];
} else {
const featureKeys = Object.keys(payload.features).filter(
(key) =>
payload.features[key] === true ||
(typeof payload.features[key] === "string" && !isNaN(Date.parse(payload.features[key])))
);
featureSegments = ["basic", ...featureKeys];
}
const featureSegments =
payload.features?.allAccess === true
? ["allAccess"]
: [
"basic",
...Object.keys(payload.features).filter(
(key) =>
payload.features[key] === true ||
(typeof payload.features[key] === "string" && !isNaN(Date.parse(payload.features[key])))
)
];
const additionalSegments = [
payload.cdk_dealerid && "CDK",
payload.pbs_serialnumber && "PBS",
// payload.rr_dealerid && "Reynolds",
payload.accountingconfig.qbo === true && "QBO",
payload.accountingconfig.qbo === false &&
!payload.cdk_dealerid &&
!payload.pbs_serialnumber &&
// !payload.rr_dealerid &&
"QBD"
].filter(Boolean);
featureSegments.push(...additionalSegments);
const regionSeg = payload.region_config ? `region:${payload.region_config}` : null;
const segments = [instanceSeg, ...(regionSeg ? [regionSeg] : []), ...featureSegments];

View File

@@ -81,8 +81,8 @@ const alternateTransportChangedBuilder = (data) => {
* @returns {{app: {jobId, jobRoNumber: *, bodyShopId: *, key: string, body: string, variables: Object, recipients: *[]}, email: {jobId, jobRoNumber: *, bodyShopName: *, body: string, recipients: *[]}, fcm: {recipients: *[]}}}
*/
const billPostedBuilder = (data) => {
const facing = data?.data?.isinhouse ? "in-house" : "vendor";
const body = `An ${facing} ${data?.data?.is_credit_memo ? "credit memo" : "bill"} has been posted.`.trim();
const facing = data?.data?.isinhouse ? "An In House" : "A Vendor";
const body = `${facing} ${data?.data?.is_credit_memo ? "credit memo" : "bill"} has been posted.`.trim();
return buildNotification(data, "notifications.job.billPosted", body, {
isInHouse: data?.data?.isinhouse,