Added separate documents display for non-images. BOD-420

This commit is contained in:
Patrick Fic
2020-10-02 13:35:29 -07:00
parent e2c3d7f4de
commit 1dea9deca3
10 changed files with 232 additions and 88 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.7.1" version="1.2"> <babeledit_project version="1.2" be_version="2.7.1">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -8470,6 +8470,48 @@
<folder_node> <folder_node>
<name>labels</name> <name>labels</name>
<children> <children>
<concept_node>
<name>confirmdelete</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>doctype</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>upload</name> <name>upload</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -16624,6 +16666,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>documents-images</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>documents-other</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>duplicateconfirm</name> <name>duplicateconfirm</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -35,7 +35,7 @@ export function DocumentsUploadComponent({
callback: callbackAfterUpload, callback: callbackAfterUpload,
}) })
} }
accept="audio/*,video/*,image/*" accept="audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx"
showUploadList={false} showUploadList={false}
> >
<Button type="primary"> <Button type="primary">

View File

@@ -17,9 +17,7 @@ export const handleUpload = (ev, context) => {
const { onError, onSuccess, onProgress } = ev; const { onError, onSuccess, onProgress } = ev;
const { bodyshop, jobId } = context; const { bodyshop, jobId } = context;
//If PDF, upload directly.
//If JPEG, resize and upload.
//TODO If this is just an invoice job? Where to put it?
let key = `${bodyshop.id}/${jobId}/${ev.file.name.replace(/\.[^/.]+$/, "")}`; let key = `${bodyshop.id}/${jobId}/${ev.file.name.replace(/\.[^/.]+$/, "")}`;
uploadToCloudinary( uploadToCloudinary(
key, key,
@@ -30,41 +28,6 @@ export const handleUpload = (ev, context) => {
onProgress, onProgress,
context context
); );
// if (ev.file.type.includes("image")) {
// Resizer.imageFileResizer(
// ev.file,
// 2500,
// 2500,
// "JPEG",
// 75,
// 0,
// (uri) => {
// let file = new File([uri], ev.file.name, {});
// file.uid = ev.file.uid;
// uploadToCloudinary(
// key,
// file.type,
// file,
// onError,
// onSuccess,
// onProgress,
// context
// );
// },
// "blob"
// );
// } else {
// uploadToCloudinary(
// key,
// ev.file.type,
// ev.file,
// onError,
// onSuccess,
// onProgress,
// context
// );
// }
}; };
export const uploadToCloudinary = async ( export const uploadToCloudinary = async (
@@ -84,12 +47,11 @@ export const uploadToCloudinary = async (
let tags = `${bodyshop.textid},${ let tags = `${bodyshop.textid},${
tagsArray ? tagsArray.map((tag) => `${tag},`) : "" tagsArray ? tagsArray.map((tag) => `${tag},`) : ""
}`; }`;
let eager = process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS; // let eager = process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS;
//Get the signed url. //Get the signed url.
const signedURLResponse = await axios.post("/media/sign", { const signedURLResponse = await axios.post("/media/sign", {
// eager: eager,
public_id: public_id, public_id: public_id,
tags: tags, tags: tags,
timestamp: timestamp, timestamp: timestamp,
@@ -117,15 +79,9 @@ export const uploadToCloudinary = async (
}; };
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
//formData.append("eager", eager);
// if (fileType.includes("image")) {
console.log("Applying lower quality transforms."); console.log("Applying lower quality transforms.");
formData.append("upload_preset", "incoming_upload"); formData.append("upload_preset", "incoming_upload");
// formData.append("quality", "auto");
// formData.append("width", "500");
// formData.append("height", "500");
// formData.append("crop", "limit");
// }
formData.append("api_key", process.env.REACT_APP_CLOUDINARY_API_KEY); formData.append("api_key", process.env.REACT_APP_CLOUDINARY_API_KEY);
formData.append("public_id", public_id); formData.append("public_id", public_id);
formData.append("tags", tags); formData.append("tags", tags);

View File

@@ -6,7 +6,10 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
export default function JobsDocumentsDownloadButton({ galleryImages }) { export default function JobsDocumentsDownloadButton({ galleryImages }) {
const { t } = useTranslation(); const { t } = useTranslation();
const imagesToDownload = galleryImages.filter((image) => image.isSelected); const imagesToDownload = [
...galleryImages.images.filter((image) => image.isSelected),
...galleryImages.other.filter((image) => image.isSelected),
];
const handleDownload = () => { const handleDownload = () => {
logImEXEvent("jobs_documents_download"); logImEXEvent("jobs_documents_download");

View File

@@ -1,6 +1,7 @@
import { Space } from "antd"; import { Collapse, Space } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Gallery from "react-grid-gallery"; import Gallery from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import DocumentsUploadComponent from "../documents-upload/documents-upload.component"; import DocumentsUploadComponent from "../documents-upload/documents-upload.component";
import JobsDocumentsDownloadButton from "./jobs-document-gallery.download.component"; import JobsDocumentsDownloadButton from "./jobs-document-gallery.download.component";
import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component"; import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component";
@@ -12,27 +13,55 @@ function JobsDocumentsComponent({
billId, billId,
billsCallback, billsCallback,
}) { }) {
const [galleryImages, setgalleryImages] = useState([]); const [galleryImages, setgalleryImages] = useState({ images: [], other: [] });
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
setgalleryImages( let documents = data.reduce(
data.reduce((acc, value) => { (acc, value) => {
acc.push({ if (value.type.includes("image")) {
src: `${process.env.REACT_APP_CLOUDINARY_IMAGE_ENDPOINT}/${value.key}`, acc.images.push({
thumbnail: `${process.env.REACT_APP_CLOUDINARY_IMAGE_ENDPOINT}/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`, src: `${process.env.REACT_APP_CLOUDINARY_IMAGE_ENDPOINT}/${value.key}`,
tags: value.type.includes("pdf") thumbnail: `${process.env.REACT_APP_CLOUDINARY_IMAGE_ENDPOINT}/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`,
? [{ value: "PDF", title: "PDF" }] thumbnailHeight: 225,
: [], thumbnailWidth: 225,
thumbnailHeight: 200, isSelected: false,
thumbnailWidth: 200, key: value.key,
isSelected: false, id: value.id,
key: value.key, });
id: value.id, } else {
}); acc.other.push({
src: `${process.env.REACT_APP_CLOUDINARY_IMAGE_ENDPOINT}/${value.key}`,
thumbnail: `${process.env.REACT_APP_CLOUDINARY_IMAGE_ENDPOINT}/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`,
tags: [
{ value: "PDF", title: t("documents.labels.doctype") },
...(value.bill
? [
{
value: value.bill.vendor.name,
title: t("vendors.fields.name"),
},
{ value: value.bill.date, title: t("bills.fields.date") },
{
value: value.bill.invoice_number,
title: t("bills.fields.invoice_number"),
},
]
: []),
],
thumbnailHeight: 225,
thumbnailWidth: 225,
isSelected: false,
key: value.key,
id: value.id,
});
}
return acc; return acc;
}, []) },
{ images: [], other: [] }
); );
}, [data, setgalleryImages]); setgalleryImages(documents);
}, [data, setgalleryImages, t]);
return ( return (
<div className="clearfix"> <div className="clearfix">
@@ -49,17 +78,55 @@ function JobsDocumentsComponent({
deletionCallback={billsCallback || refetch} deletionCallback={billsCallback || refetch}
/> />
</Space> </Space>
<Collapse
<Gallery style={{ marginTop: "2rem" }}
images={galleryImages} defaultActiveKey={["images", "other"]}
onSelectImage={(index, image) => { bordered="false"
setgalleryImages( >
galleryImages.map((g, idx) => <Collapse.Panel key="images" header={t("jobs.labels.documents-images")}>
index === idx ? { ...g, isSelected: !g.isSelected } : g <Gallery
) images={galleryImages.images}
); backdropClosesModal={true}
}} onClickImage={(props) => {
/> window.open(
props.target.src,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
onSelectImage={(index, image) => {
setgalleryImages({
...galleryImages,
images: galleryImages.images.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g
),
});
}}
/>
</Collapse.Panel>
<Collapse.Panel key="other" header={t("jobs.labels.documents-other")}>
<Gallery
images={galleryImages.other}
backdropClosesModal={true}
enableLightbox={false}
onClickThumbnail={(index) => {
window.open(
galleryImages.other[index].src,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
onSelectImage={(index) => {
setgalleryImages({
...galleryImages,
other: galleryImages.other.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g
),
});
}}
/>
</Collapse.Panel>
</Collapse>
</div> </div>
); );
} }

View File

@@ -1,10 +1,11 @@
import { Button, notification } from "antd"; import { Button, notification, Popconfirm } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import axios from "axios"; import axios from "axios";
import { useMutation } from "@apollo/react-hooks"; import { useMutation } from "@apollo/react-hooks";
import { DELETE_DOCUMENT } from "../../graphql/documents.queries"; import { DELETE_DOCUMENT } from "../../graphql/documents.queries";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { QuestionCircleOutlined } from "@ant-design/icons";
import { axiosAuthInterceptorId } from "../../App/App.container"; import { axiosAuthInterceptorId } from "../../App/App.container";
//Context: currentUserEmail, bodyshop, jobid, invoiceid //Context: currentUserEmail, bodyshop, jobid, invoiceid
@@ -18,7 +19,10 @@ export default function JobsDocumentsDeleteButton({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [deleteDocument] = useMutation(DELETE_DOCUMENT); const [deleteDocument] = useMutation(DELETE_DOCUMENT);
const imagesToDelete = galleryImages.filter((image) => image.isSelected); const imagesToDelete = [
...galleryImages.images.filter((image) => image.isSelected),
...galleryImages.other.filter((image) => image.isSelected),
];
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleDelete = () => { const handleDelete = () => {
logImEXEvent("job_documents_delete", { count: imagesToDelete.length }); logImEXEvent("job_documents_delete", { count: imagesToDelete.length });
@@ -76,12 +80,18 @@ export default function JobsDocumentsDeleteButton({
}; };
return ( return (
<Button <Popconfirm
disabled={imagesToDelete.length < 1} disabled={imagesToDelete.length < 1}
loading={loading} icon={<QuestionCircleOutlined style={{ color: "red" }} />}
onClick={handleDelete} onConfirm={handleDelete}
title={t("documents.labels.confirmdelete")}
okText={t("general.actions.delete")}
okButtonProps={{ type: "danger" }}
cancelText={t("general.actions.cancel")}
> >
{t("documents.actions.delete")} <Button disabled={imagesToDelete.length < 1} loading={loading}>
</Button> {t("documents.actions.delete")}
</Button>
</Popconfirm>
); );
} }

View File

@@ -2,11 +2,23 @@ import gql from "graphql-tag";
export const GET_DOCUMENTS_BY_JOB = gql` export const GET_DOCUMENTS_BY_JOB = gql`
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) { query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
documents(where: { jobid: { _eq: $jobId } }) { documents(
where: { jobid: { _eq: $jobId } }
order_by: { updated_at: desc }
) {
id id
name name
key key
type type
bill {
id
invoice_number
date
vendor {
id
name
}
}
} }
} }
`; `;

View File

@@ -561,6 +561,8 @@
"nodocuments": "There are no documents." "nodocuments": "There are no documents."
}, },
"labels": { "labels": {
"confirmdelete": "Are you sure you want to delete these documents. This CANNOT be undone.",
"doctype": "Document Type",
"upload": "Upload" "upload": "Upload"
}, },
"successes": { "successes": {
@@ -1018,6 +1020,8 @@
}, },
"difference": "Difference", "difference": "Difference",
"documents": "Documents", "documents": "Documents",
"documents-images": "Images",
"documents-other": "Other Documents",
"duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.", "duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.",
"employeeassignments": "Employee Assignments", "employeeassignments": "Employee Assignments",
"existing_jobs": "Existing Jobs", "existing_jobs": "Existing Jobs",

View File

@@ -561,6 +561,8 @@
"nodocuments": "No hay documentos" "nodocuments": "No hay documentos"
}, },
"labels": { "labels": {
"confirmdelete": "",
"doctype": "",
"upload": "Subir" "upload": "Subir"
}, },
"successes": { "successes": {
@@ -1018,6 +1020,8 @@
}, },
"difference": "", "difference": "",
"documents": "documentos", "documents": "documentos",
"documents-images": "",
"documents-other": "",
"duplicateconfirm": "", "duplicateconfirm": "",
"employeeassignments": "", "employeeassignments": "",
"existing_jobs": "Empleos existentes", "existing_jobs": "Empleos existentes",

View File

@@ -561,6 +561,8 @@
"nodocuments": "Il n'y a pas de documents." "nodocuments": "Il n'y a pas de documents."
}, },
"labels": { "labels": {
"confirmdelete": "",
"doctype": "",
"upload": "Télécharger" "upload": "Télécharger"
}, },
"successes": { "successes": {
@@ -1018,6 +1020,8 @@
}, },
"difference": "", "difference": "",
"documents": "Les documents", "documents": "Les documents",
"documents-images": "",
"documents-other": "",
"duplicateconfirm": "", "duplicateconfirm": "",
"employeeassignments": "", "employeeassignments": "",
"existing_jobs": "Emplois existants", "existing_jobs": "Emplois existants",