BOD-5 BOD-36 #comment Added Audit Trail List to jobs and created Audit List view component + queries
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<babeledit_project be_version="2.6.1" version="1.2">
|
<babeledit_project version="1.2" be_version="2.6.1">
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
BabelEdit project file
|
BabelEdit project file
|
||||||
@@ -639,6 +639,100 @@
|
|||||||
</folder_node>
|
</folder_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
|
<folder_node>
|
||||||
|
<name>audit</name>
|
||||||
|
<children>
|
||||||
|
<folder_node>
|
||||||
|
<name>fields</name>
|
||||||
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>created</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>operation</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>useremail</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>values</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>
|
||||||
|
</children>
|
||||||
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
<name>bodyshop</name>
|
<name>bodyshop</name>
|
||||||
<children>
|
<children>
|
||||||
@@ -2224,6 +2318,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>vendorname</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>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
@@ -5061,6 +5176,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>audit</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>available_new_jobs</name>
|
<name>available_new_jobs</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Table } from "antd";
|
||||||
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
|
||||||
|
|
||||||
|
export default function AuditTrailListComponent({ loading, data }) {
|
||||||
|
const [state, setState] = useState({
|
||||||
|
sortedInfo: {},
|
||||||
|
filteredInfo: {}
|
||||||
|
});
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("audit.fields.created"),
|
||||||
|
dataIndex: " created",
|
||||||
|
key: " created",
|
||||||
|
|
||||||
|
render: (text, record) => (
|
||||||
|
<DateTimeFormatter>{record.created}</DateTimeFormatter>
|
||||||
|
),
|
||||||
|
sorter: (a, b) => a.created - b.created,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "created" && state.sortedInfo.order
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("audit.fields.operation"),
|
||||||
|
dataIndex: "operation",
|
||||||
|
key: "operation",
|
||||||
|
sorter: (a, b) => alphaSort(a.operation, b.operation),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "operation" && state.sortedInfo.order
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("audit.fields.values"),
|
||||||
|
dataIndex: " old_val",
|
||||||
|
key: " old_val",
|
||||||
|
render: (text, record) => (
|
||||||
|
<AuditTrailValuesComponent
|
||||||
|
oldV={record.old_val}
|
||||||
|
newV={record.new_val}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("audit.fields.useremail"),
|
||||||
|
dataIndex: "useremail",
|
||||||
|
key: "useremail",
|
||||||
|
sorter: (a, b) => alphaSort(a.useremail, b.useremail),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "useremail" && state.sortedInfo.order
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const formItemLayout = {
|
||||||
|
labelCol: {
|
||||||
|
xs: { span: 12 },
|
||||||
|
sm: { span: 5 }
|
||||||
|
},
|
||||||
|
wrapperCol: {
|
||||||
|
xs: { span: 24 },
|
||||||
|
sm: { span: 12 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
{...formItemLayout}
|
||||||
|
loading={loading}
|
||||||
|
size="small"
|
||||||
|
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||||
|
columns={columns.map(item => ({ ...item }))}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={data}
|
||||||
|
onChange={handleTableChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import AuditTrailListComponent from "./audit-trail-list.component";
|
||||||
|
import { useQuery } from "react-apollo";
|
||||||
|
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||||
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
|
||||||
|
export default function AuditTrailListContainer({ recordId }) {
|
||||||
|
const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, {
|
||||||
|
variables: { id: recordId },
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{error ? (
|
||||||
|
<AlertComponent type="error" message={error.message} />
|
||||||
|
) : (
|
||||||
|
<AuditTrailListComponent
|
||||||
|
loading={loading}
|
||||||
|
data={data ? data.audit_trail : null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { List } from "antd";
|
||||||
|
import Icon from "@ant-design/icons";
|
||||||
|
import { FaArrowRight } from "react-icons/fa";
|
||||||
|
export default function AuditTrailValuesComponent({ oldV, newV }) {
|
||||||
|
return (
|
||||||
|
<List bordered size="small">
|
||||||
|
{Object.keys(oldV).map((key, idx) => (
|
||||||
|
<List.Item key={idx} value={key}>
|
||||||
|
{key}: {oldV[key]} <Icon component={FaArrowRight} /> {newV[key]}
|
||||||
|
</List.Item>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
client/src/graphql/audit_trail.queries.js
Normal file
18
client/src/graphql/audit_trail.queries.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { gql } from "apollo-boost";
|
||||||
|
|
||||||
|
export const QUERY_AUDIT_TRAIL = gql`
|
||||||
|
query QUERY_AUDIT_TRAIL($id: uuid!) {
|
||||||
|
audit_trail(where: {recordid: {_eq: $id}}) {
|
||||||
|
useremail
|
||||||
|
tabname
|
||||||
|
schemaname
|
||||||
|
recordid
|
||||||
|
operation
|
||||||
|
old_val
|
||||||
|
new_val
|
||||||
|
id
|
||||||
|
created
|
||||||
|
bodyshopid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -13,7 +13,8 @@ import {
|
|||||||
FaHardHat,
|
FaHardHat,
|
||||||
FaInfo,
|
FaInfo,
|
||||||
FaRegStickyNote,
|
FaRegStickyNote,
|
||||||
FaShieldAlt
|
FaShieldAlt,
|
||||||
|
FaHistory
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
//import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
//import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
||||||
//import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
|
//import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
|
||||||
@@ -70,6 +71,9 @@ const EnterInvoiceModalContainer = lazy(() =>
|
|||||||
const JobsDetailPliContainer = lazy(() =>
|
const JobsDetailPliContainer = lazy(() =>
|
||||||
import("../../components/jobs-detail-pli/jobs-detail-pli.container")
|
import("../../components/jobs-detail-pli/jobs-detail-pli.container")
|
||||||
);
|
);
|
||||||
|
const JobsDetailAuditContainer = lazy(() =>
|
||||||
|
import("../../components/audit-trail-list/audit-trail-list.container")
|
||||||
|
);
|
||||||
|
|
||||||
export default function JobsDetailPage({
|
export default function JobsDetailPage({
|
||||||
job,
|
job,
|
||||||
@@ -120,7 +124,7 @@ export default function JobsDetailPage({
|
|||||||
|
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFieldsChange={(a, b) => console.log("a,b", a, b)}
|
//onFieldsChange={(a, b) => console.log("a,b", a, b)}
|
||||||
name="JobDetailForm"
|
name="JobDetailForm"
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
{...formItemLayout}
|
{...formItemLayout}
|
||||||
@@ -262,6 +266,21 @@ export default function JobsDetailPage({
|
|||||||
>
|
>
|
||||||
<JobNotesContainer jobId={job.id} />
|
<JobNotesContainer jobId={job.id} />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
|
||||||
|
|
||||||
|
<Tabs.TabPane
|
||||||
|
tab={
|
||||||
|
<span>
|
||||||
|
<Icon component={FaHistory} />
|
||||||
|
{t("jobs.labels.audit")}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="audit"
|
||||||
|
>
|
||||||
|
<JobsDetailAuditContainer recordId={job.id }/>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
|
||||||
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Form>
|
</Form>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -56,6 +56,14 @@
|
|||||||
"actions": "Actions"
|
"actions": "Actions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"audit": {
|
||||||
|
"fields": {
|
||||||
|
"created": "Time",
|
||||||
|
"operation": "Operation",
|
||||||
|
"useremail": "User",
|
||||||
|
"values": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"bodyshop": {
|
"bodyshop": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"loading": "Unable to load shop details. Please call technical support."
|
"loading": "Unable to load shop details. Please call technical support."
|
||||||
@@ -178,7 +186,8 @@
|
|||||||
"is_credit_memo": "Credit Memo?",
|
"is_credit_memo": "Credit Memo?",
|
||||||
"ro_number": "RO Number",
|
"ro_number": "RO Number",
|
||||||
"total": "Invoice Total",
|
"total": "Invoice Total",
|
||||||
"vendor": "Vendor"
|
"vendor": "Vendor",
|
||||||
|
"vendorname": "Vendor Name"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
@@ -337,6 +346,7 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"appointmentconfirmation": "Send confirmation to customer?",
|
"appointmentconfirmation": "Send confirmation to customer?",
|
||||||
|
"audit": "Audit Trail",
|
||||||
"available_new_jobs": "",
|
"available_new_jobs": "",
|
||||||
"cards": {
|
"cards": {
|
||||||
"appraiser": "Appraiser",
|
"appraiser": "Appraiser",
|
||||||
|
|||||||
@@ -56,6 +56,14 @@
|
|||||||
"actions": "Comportamiento"
|
"actions": "Comportamiento"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"audit": {
|
||||||
|
"fields": {
|
||||||
|
"created": "",
|
||||||
|
"operation": "",
|
||||||
|
"useremail": "",
|
||||||
|
"values": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"bodyshop": {
|
"bodyshop": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico."
|
"loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico."
|
||||||
@@ -178,7 +186,8 @@
|
|||||||
"is_credit_memo": "",
|
"is_credit_memo": "",
|
||||||
"ro_number": "",
|
"ro_number": "",
|
||||||
"total": "",
|
"total": "",
|
||||||
"vendor": ""
|
"vendor": "",
|
||||||
|
"vendorname": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"actions": "",
|
"actions": "",
|
||||||
@@ -337,6 +346,7 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"appointmentconfirmation": "¿Enviar confirmación al cliente?",
|
"appointmentconfirmation": "¿Enviar confirmación al cliente?",
|
||||||
|
"audit": "",
|
||||||
"available_new_jobs": "",
|
"available_new_jobs": "",
|
||||||
"cards": {
|
"cards": {
|
||||||
"appraiser": "Tasador",
|
"appraiser": "Tasador",
|
||||||
|
|||||||
@@ -56,6 +56,14 @@
|
|||||||
"actions": "actes"
|
"actions": "actes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"audit": {
|
||||||
|
"fields": {
|
||||||
|
"created": "",
|
||||||
|
"operation": "",
|
||||||
|
"useremail": "",
|
||||||
|
"values": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"bodyshop": {
|
"bodyshop": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique."
|
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique."
|
||||||
@@ -178,7 +186,8 @@
|
|||||||
"is_credit_memo": "",
|
"is_credit_memo": "",
|
||||||
"ro_number": "",
|
"ro_number": "",
|
||||||
"total": "",
|
"total": "",
|
||||||
"vendor": ""
|
"vendor": "",
|
||||||
|
"vendorname": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"actions": "",
|
"actions": "",
|
||||||
@@ -337,6 +346,7 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"appointmentconfirmation": "Envoyer une confirmation au client?",
|
"appointmentconfirmation": "Envoyer une confirmation au client?",
|
||||||
|
"audit": "",
|
||||||
"available_new_jobs": "",
|
"available_new_jobs": "",
|
||||||
"cards": {
|
"cards": {
|
||||||
"appraiser": "Expert",
|
"appraiser": "Expert",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var JSZip = require("jszip");
|
//var JSZip = require("jszip");
|
||||||
const axios = require("axios"); // to get the images
|
const axios = require("axios"); // to get the images
|
||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
|
|
||||||
@@ -6,19 +6,19 @@ module.exports.downloadImages = async function(req, res) {
|
|||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
console.log("[IMAGES] Incoming Images to Download", req.body);
|
console.log("[IMAGES] Incoming Images to Download", req.body);
|
||||||
}
|
}
|
||||||
const zip = new JSZip();
|
// const zip = new JSZip();
|
||||||
//res.json({ success: true });
|
// //res.json({ success: true });
|
||||||
req.body.images.forEach(i => {
|
// req.body.images.forEach(i => {
|
||||||
axios.get(i).then(r => zip.file(r));
|
// axios.get(i).then(r => zip.file(r));
|
||||||
// const buffer = await response.buffer();
|
// // const buffer = await response.buffer();
|
||||||
// zip.file(file, buffer);
|
// // zip.file(file, buffer);
|
||||||
});
|
// });
|
||||||
// // Set the name of the zip file in the download
|
// // // Set the name of the zip file in the download
|
||||||
res.setHeader("Content-Type", "application/zip");
|
// res.setHeader("Content-Type", "application/zip");
|
||||||
|
|
||||||
zip.generateAsync({ type: "nodebuffer" }).then(
|
// zip.generateAsync({ type: "nodebuffer" }).then(
|
||||||
function(content) {
|
// function(content) {
|
||||||
res.send(content);
|
// res.send(content);
|
||||||
}.bind(res)
|
// }.bind(res)
|
||||||
);
|
// );
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user