BOD-5 BOD-36 #comment Added Audit Trail List to jobs and created Audit List view component + queries

This commit is contained in:
Patrick Fic
2020-03-10 11:02:28 -07:00
parent d1cfd9bacf
commit 05d7aa7000
10 changed files with 345 additions and 21 deletions

View File

@@ -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
@@ -639,6 +639,100 @@
</folder_node>
</children>
</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>
<name>bodyshop</name>
<children>
@@ -2224,6 +2318,27 @@
</translation>
</translations>
</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>
</folder_node>
<folder_node>
@@ -5061,6 +5176,27 @@
</translation>
</translations>
</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>
<name>available_new_jobs</name>
<definition_loaded>false</definition_loaded>

View File

@@ -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}
/>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View 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
}
}
`;

View File

@@ -13,7 +13,8 @@ import {
FaHardHat,
FaInfo,
FaRegStickyNote,
FaShieldAlt
FaShieldAlt,
FaHistory
} from "react-icons/fa";
//import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
//import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
@@ -70,6 +71,9 @@ const EnterInvoiceModalContainer = lazy(() =>
const JobsDetailPliContainer = lazy(() =>
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({
job,
@@ -120,7 +124,7 @@ export default function JobsDetailPage({
<Form
form={form}
onFieldsChange={(a, b) => console.log("a,b", a, b)}
//onFieldsChange={(a, b) => console.log("a,b", a, b)}
name="JobDetailForm"
onFinish={handleFinish}
{...formItemLayout}
@@ -262,6 +266,21 @@ export default function JobsDetailPage({
>
<JobNotesContainer jobId={job.id} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<Icon component={FaHistory} />
{t("jobs.labels.audit")}
</span>
}
key="audit"
>
<JobsDetailAuditContainer recordId={job.id }/>
</Tabs.TabPane>
</Tabs>
</Form>
</Suspense>

View File

@@ -56,6 +56,14 @@
"actions": "Actions"
}
},
"audit": {
"fields": {
"created": "Time",
"operation": "Operation",
"useremail": "User",
"values": ""
}
},
"bodyshop": {
"errors": {
"loading": "Unable to load shop details. Please call technical support."
@@ -178,7 +186,8 @@
"is_credit_memo": "Credit Memo?",
"ro_number": "RO Number",
"total": "Invoice Total",
"vendor": "Vendor"
"vendor": "Vendor",
"vendorname": "Vendor Name"
},
"labels": {
"actions": "Actions",
@@ -337,6 +346,7 @@
},
"labels": {
"appointmentconfirmation": "Send confirmation to customer?",
"audit": "Audit Trail",
"available_new_jobs": "",
"cards": {
"appraiser": "Appraiser",

View File

@@ -56,6 +56,14 @@
"actions": "Comportamiento"
}
},
"audit": {
"fields": {
"created": "",
"operation": "",
"useremail": "",
"values": ""
}
},
"bodyshop": {
"errors": {
"loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico."
@@ -178,7 +186,8 @@
"is_credit_memo": "",
"ro_number": "",
"total": "",
"vendor": ""
"vendor": "",
"vendorname": ""
},
"labels": {
"actions": "",
@@ -337,6 +346,7 @@
},
"labels": {
"appointmentconfirmation": "¿Enviar confirmación al cliente?",
"audit": "",
"available_new_jobs": "",
"cards": {
"appraiser": "Tasador",

View File

@@ -56,6 +56,14 @@
"actions": "actes"
}
},
"audit": {
"fields": {
"created": "",
"operation": "",
"useremail": "",
"values": ""
}
},
"bodyshop": {
"errors": {
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique."
@@ -178,7 +186,8 @@
"is_credit_memo": "",
"ro_number": "",
"total": "",
"vendor": ""
"vendor": "",
"vendorname": ""
},
"labels": {
"actions": "",
@@ -337,6 +346,7 @@
},
"labels": {
"appointmentconfirmation": "Envoyer une confirmation au client?",
"audit": "",
"available_new_jobs": "",
"cards": {
"appraiser": "Expert",

View File

@@ -1,4 +1,4 @@
var JSZip = require("jszip");
//var JSZip = require("jszip");
const axios = require("axios"); // to get the images
require("dotenv").config();
@@ -6,19 +6,19 @@ module.exports.downloadImages = async function(req, res) {
if (process.env.NODE_ENV !== "production") {
console.log("[IMAGES] Incoming Images to Download", req.body);
}
const zip = new JSZip();
//res.json({ success: true });
req.body.images.forEach(i => {
axios.get(i).then(r => zip.file(r));
// const buffer = await response.buffer();
// zip.file(file, buffer);
});
// // Set the name of the zip file in the download
res.setHeader("Content-Type", "application/zip");
// const zip = new JSZip();
// //res.json({ success: true });
// req.body.images.forEach(i => {
// axios.get(i).then(r => zip.file(r));
// // const buffer = await response.buffer();
// // zip.file(file, buffer);
// });
// // // Set the name of the zip file in the download
// res.setHeader("Content-Type", "application/zip");
zip.generateAsync({ type: "nodebuffer" }).then(
function(content) {
res.send(content);
}.bind(res)
);
// zip.generateAsync({ type: "nodebuffer" }).then(
// function(content) {
// res.send(content);
// }.bind(res)
// );
};