@@ -47,6 +47,74 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>errors</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>deleting</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>saving</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>validation</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>
|
||||
<name>fields</name>
|
||||
<children>
|
||||
@@ -73,6 +141,53 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>successes</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>deleted</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>save</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>
|
||||
@@ -722,6 +837,63 @@
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>emails</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>errors</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>notsent</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>
|
||||
<name>successes</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>sent</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>employees</name>
|
||||
<children>
|
||||
@@ -1578,6 +1750,79 @@
|
||||
<folder_node>
|
||||
<name>joblines</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>actions</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>new</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>
|
||||
<name>errors</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>creating</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>updating</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>
|
||||
<name>fields</name>
|
||||
<children>
|
||||
@@ -1644,6 +1889,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>line_ind</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>mod_lb_hrs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -1665,6 +1931,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>mod_lbr_ty</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>oem_partno</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -1686,6 +1973,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>op_code_desc</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>part_type</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -1707,6 +2015,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>status</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>unq_seq</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -1730,6 +2059,100 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>edit</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>new</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>
|
||||
<name>successes</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>updated</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>
|
||||
@@ -5173,6 +5596,58 @@
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>messaging</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>messaging</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>typeamessage</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>notes</name>
|
||||
<children>
|
||||
@@ -5979,6 +6454,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>lineremarks</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>
|
||||
@@ -6005,6 +6501,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>inthisorder</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>orderhistory</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>print</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
37
client/src/components/_test/test.component.jsx
Normal file
37
client/src/components/_test/test.component.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import T from "../../emails/parts-order/parts-order.email";
|
||||
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../emails/parts-order/parts-order.query";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setEmailOptions: e => dispatch(setEmailOptions(e))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(function Test({ setEmailOptions }) {
|
||||
return (
|
||||
<button
|
||||
onClick={() =>
|
||||
setEmailOptions({
|
||||
messageOptions: {
|
||||
from: { name: "Kavia Autobdoy", address: "noreply@bodyshop.app" },
|
||||
to: "patrickwf@gmail.com",
|
||||
replyTo: "snaptsoft@gmail.com"
|
||||
},
|
||||
template: T,
|
||||
queryConfig: [
|
||||
REPORT_QUERY_PARTS_ORDER_BY_PK,
|
||||
{ variables: { id: "46f3aa34-c3bd-46c8-83fc-c93b7ce84f46" } }
|
||||
]
|
||||
})
|
||||
}>
|
||||
Set email config.
|
||||
</button>
|
||||
);
|
||||
});
|
||||
@@ -20,14 +20,19 @@ export default function AllocationsAssignmentContainer({
|
||||
const [insertAllocation] = useMutation(INSERT_ALLOCATION);
|
||||
|
||||
const handleAssignment = () => {
|
||||
insertAllocation({ variables: { alloc: { ...assignment } } }).then(r => {
|
||||
notification["success"]({
|
||||
message: t("employees.successes.save")
|
||||
insertAllocation({ variables: { alloc: { ...assignment } } })
|
||||
.then(r => {
|
||||
notification["success"]({
|
||||
message: t("allocations.successes.save")
|
||||
});
|
||||
visibilityState[1](false);
|
||||
if (refetch) refetch();
|
||||
})
|
||||
.catch(error => {
|
||||
notification["error"]({
|
||||
message: t("employees.errors.saving", { message: error.message })
|
||||
});
|
||||
});
|
||||
//TODO Better way to reset the field decorators?
|
||||
visibilityState[1](false);
|
||||
if (refetch) refetch();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Button, Popover, Select } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(function AllocationsBulkAssignmentComponent({
|
||||
disabled,
|
||||
bodyshop,
|
||||
handleAssignment,
|
||||
assignment,
|
||||
setAssignment,
|
||||
visibilityState
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onChange = e => {
|
||||
console.log("e", e);
|
||||
setAssignment({ ...assignment, employeeid: e });
|
||||
};
|
||||
|
||||
const [visibility, setVisibility] = visibilityState;
|
||||
|
||||
const popContent = (
|
||||
<div>
|
||||
<Select
|
||||
showSearch
|
||||
style={{ width: 200 }}
|
||||
placeholder='Select a person'
|
||||
optionFilterProp='children'
|
||||
onChange={onChange}
|
||||
filterOption={(input, option) =>
|
||||
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}>
|
||||
{bodyshop.employees.map(emp => (
|
||||
<Select.Option value={emp.id} key={emp.id}>
|
||||
{`${emp.first_name} ${emp.last_name}`}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<Button
|
||||
type='primary'
|
||||
disabled={!assignment.employeeid}
|
||||
onClick={handleAssignment}>
|
||||
Assign
|
||||
</Button>
|
||||
<Button onClick={() => setVisibility(false)}>Close</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={popContent} visible={visibility}>
|
||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||
{t("allocations.actions.assign")}
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import React, { useState } from "react";
|
||||
import AllocationsBulkAssignment from "./allocations-bulk-assignment.component";
|
||||
import { useMutation } from "react-apollo";
|
||||
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { notification } from "antd";
|
||||
|
||||
export default function AllocationsBulkAssignmentContainer({
|
||||
jobLines,
|
||||
refetch
|
||||
}) {
|
||||
const visibilityState = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [assignment, setAssignment] = useState({
|
||||
employeeid: null
|
||||
});
|
||||
const [insertAllocation] = useMutation(INSERT_ALLOCATION);
|
||||
|
||||
const handleAssignment = () => {
|
||||
const allocs = jobLines.reduce((acc, value) => {
|
||||
acc.push({
|
||||
joblineid: value.id,
|
||||
hours: parseFloat(value.mod_lb_hrs) || 0,
|
||||
employeeid: assignment.employeeid
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
insertAllocation({ variables: { alloc: allocs } }).then(r => {
|
||||
notification["success"]({
|
||||
message: t("employees.successes.save")
|
||||
});
|
||||
visibilityState[1](false);
|
||||
if (refetch) refetch();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AllocationsBulkAssignment
|
||||
disabled={jobLines.length > 0 ? false : true}
|
||||
handleAssignment={handleAssignment}
|
||||
assignment={assignment}
|
||||
setAssignment={setAssignment}
|
||||
visibilityState={visibilityState}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Icon } from "antd";
|
||||
import React from "react";
|
||||
import { MdRemoveCircleOutline } from "react-icons/md";
|
||||
|
||||
export default function AllocationsLabelComponent({ allocation, handleClick }) {
|
||||
return (
|
||||
<div style={{ display: "flex" }}>
|
||||
<span>
|
||||
{`${allocation.employee.first_name || ""} ${allocation.employee
|
||||
.last_name || ""} (${allocation.hours || ""})`}
|
||||
</span>
|
||||
<Icon
|
||||
style={{ color: "red", padding: "0px 4px" }}
|
||||
component={MdRemoveCircleOutline}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "react-apollo";
|
||||
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
|
||||
import AllocationsLabelComponent from "./allocations-employee-label.component";
|
||||
import { notification } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function AllocationsLabelContainer({ allocation, refetch }) {
|
||||
const [deleteAllocation] = useMutation(DELETE_ALLOCATION);
|
||||
const { t } = useTranslation();
|
||||
const handleClick = e => {
|
||||
e.preventDefault();
|
||||
deleteAllocation({ variables: { id: allocation.id } })
|
||||
.then(r => {
|
||||
notification["success"]({
|
||||
message: t("allocations.successes.deleted")
|
||||
});
|
||||
if (refetch) refetch();
|
||||
})
|
||||
.catch(error => {
|
||||
notification["error"]({ message: t("allocations.errors.deleting") });
|
||||
});
|
||||
};
|
||||
return (
|
||||
<AllocationsLabelComponent
|
||||
allocation={allocation}
|
||||
handleClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { Button, Card, Input, Icon } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import twilio from "twilio";
|
||||
import {
|
||||
closeConversation,
|
||||
toggleConversationVisible
|
||||
} from "../../redux/messaging/messaging.actions";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import "./chat-conversation.styles.scss"; //https://bootsnipp.com/snippets/exR5v
|
||||
import { MdSend } from "react-icons/md";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const client = twilio(
|
||||
"ACf1b1aaf0e04740828b49b6e58467d787",
|
||||
"0bea5e29a6d77593183ab1caa01d23de"
|
||||
);
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleConversationVisible: conversationId =>
|
||||
dispatch(toggleConversationVisible(conversationId)),
|
||||
closeConversation: phone => dispatch(closeConversation(phone))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(function ChatConversationComponent({
|
||||
conversation,
|
||||
toggleConversationVisible,
|
||||
closeConversation
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [messages, setMessages] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
client.messages.list({ limit: 20 }, (error, items) => {
|
||||
setMessages(
|
||||
items.reduce((acc, value) => {
|
||||
acc.push({
|
||||
sid: value.sid,
|
||||
direction: value.direction,
|
||||
body: value.body
|
||||
});
|
||||
return acc;
|
||||
}, [])
|
||||
);
|
||||
});
|
||||
return () => {};
|
||||
}, [setMessages]);
|
||||
return (
|
||||
<div>
|
||||
<Card
|
||||
title={
|
||||
conversation.open ? (
|
||||
<div style={{ display: "flex" }}>
|
||||
<div
|
||||
onClick={() => toggleConversationVisible(conversation.phone)}
|
||||
>
|
||||
<PhoneFormatter>{conversation.phone}</PhoneFormatter>
|
||||
</div>
|
||||
<Button
|
||||
type="danger"
|
||||
shape="circle-outline"
|
||||
onClick={() => closeConversation(conversation.phone)}
|
||||
>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
style={{
|
||||
width: conversation.open ? "400px" : "175px",
|
||||
margin: "0px 10px"
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
{conversation.open ? (
|
||||
<div>
|
||||
<div className="messages" style={{ height: "400px" }}>
|
||||
<ul>
|
||||
{messages.map(item => (
|
||||
<li
|
||||
key={item.sid}
|
||||
className={`${
|
||||
item.direction === "inbound" ? "sent" : "replies"
|
||||
}`}
|
||||
>
|
||||
<p> {item.body}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<Input.Search
|
||||
placeholder={t("messaging.labels.typeamessage")}
|
||||
enterButton={<Icon component={MdSend} />}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: "flex" }}>
|
||||
<div onClick={() => toggleConversationVisible(conversation.phone)}>
|
||||
<PhoneFormatter>{conversation.phone}</PhoneFormatter>
|
||||
</div>
|
||||
<Button
|
||||
type="dashed"
|
||||
shape="circle-outline"
|
||||
onClick={() => closeConversation(conversation.phone)}
|
||||
>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import ChatConversationComponent from "./chat-conversation.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(function ChatConversationContainer({ conversation }) {
|
||||
return <ChatConversationComponent conversation={conversation} />;
|
||||
});
|
||||
@@ -20,7 +20,7 @@
|
||||
.messages ul li {
|
||||
display: inline-block;
|
||||
clear: both;
|
||||
float: left;
|
||||
//float: left;
|
||||
margin: 5px 15px 5px 15px;
|
||||
width: calc(100% - 25px);
|
||||
font-size: 0.9em;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { openConversation } from "../../redux/messaging/messaging.actions";
|
||||
import { Icon } from "antd";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
openConversation: phone => dispatch(openConversation(phone))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(function ChatOpenButton({ openConversation, phone }) {
|
||||
return (
|
||||
<Icon
|
||||
style={{ margin: 4 }}
|
||||
type="message"
|
||||
onClick={() => openConversation(phone)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Badge, Card, Icon } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function ChatWindowComponent({
|
||||
chatVisible,
|
||||
toggleChatVisible
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<Badge count={5}>
|
||||
<Card
|
||||
onClick={() => toggleChatVisible()}
|
||||
style={{
|
||||
width: chatVisible ? "300px" : "125px",
|
||||
margin: "0px 10px"
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
{chatVisible ? (
|
||||
<div className="messages" style={{ height: "400px" }}>
|
||||
List of chats here.
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<Icon type="message" />
|
||||
<strong style={{ paddingLeft: "10px" }}>
|
||||
{t("messaging.labels.messaging")}
|
||||
</strong>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Affix, Badge } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
|
||||
import {
|
||||
selectChatVisible,
|
||||
selectConversations
|
||||
} from "../../redux/messaging/messaging.selectors";
|
||||
import ChatConversationContainer from "../chat-conversation/chat-conversation.container";
|
||||
import ChatOverlayComponent from "./chat-overlay.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
chatVisible: selectChatVisible,
|
||||
conversations: selectConversations
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleChatVisible: () => dispatch(toggleChatVisible())
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(function ChatWindowContainer({
|
||||
chatVisible,
|
||||
toggleChatVisible,
|
||||
conversations
|
||||
}) {
|
||||
return (
|
||||
<Affix offsetBottom={0}>
|
||||
<div>
|
||||
<Badge count={10}>
|
||||
<ChatOverlayComponent
|
||||
chatVisible={chatVisible}
|
||||
toggleChatVisible={toggleChatVisible}
|
||||
/>
|
||||
</Badge>
|
||||
{conversations
|
||||
? conversations.map((conversation, idx) => (
|
||||
<Badge key={idx} count={5}>
|
||||
<ChatConversationContainer conversation={conversation} />
|
||||
</Badge>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</Affix>
|
||||
);
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
import { Card } from "antd";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "./chat-window.styles.scss"; //https://bootsnipp.com/snippets/exR5v
|
||||
import twilio from "twilio";
|
||||
// const client = require("twilio")(
|
||||
// "ACf1b1aaf0e04740828b49b6e58467d787",
|
||||
// "0bea5e29a6d77593183ab1caa01d23de"
|
||||
// );
|
||||
const client = twilio(
|
||||
"ACf1b1aaf0e04740828b49b6e58467d787",
|
||||
"0bea5e29a6d77593183ab1caa01d23de"
|
||||
);
|
||||
export default function ChatWindowComponent({ toggleChatVisible }) {
|
||||
const [conversations, setConversations] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
client.messages.list({ limit: 20 }, (error, items) => {
|
||||
setConversations(
|
||||
items.reduce((acc, value) => {
|
||||
acc.push({
|
||||
sid: value.sid,
|
||||
direction: value.direction,
|
||||
body: value.body
|
||||
});
|
||||
return acc;
|
||||
}, [])
|
||||
);
|
||||
});
|
||||
return () => {};
|
||||
}, [setConversations]);
|
||||
|
||||
console.log(conversations);
|
||||
return (
|
||||
<Card style={{ width: "400px" }}>
|
||||
<div>
|
||||
<button onClick={() => toggleChatVisible()}>X</button>
|
||||
<div className='messages' style={{ height: "400px" }}>
|
||||
<ul>
|
||||
{conversations.map(item => (
|
||||
<li
|
||||
key={item.sid}
|
||||
className={`${
|
||||
item.direction === "inbound" ? "replies" : "sent"
|
||||
}`}>
|
||||
<p> {item.body}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Affix, Button, Badge } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
|
||||
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
|
||||
import ChatWindowComponent from "./chat-window.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
chatVisible: selectChatVisible
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleChatVisible: () => dispatch(toggleChatVisible())
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(function ChatWindowContainer({ chatVisible, toggleChatVisible }) {
|
||||
return (
|
||||
<Affix offsetBottom={25}>
|
||||
{chatVisible ? (
|
||||
<ChatWindowComponent toggleChatVisible={toggleChatVisible} />
|
||||
) : (
|
||||
<Badge count={5}>
|
||||
<Button
|
||||
type='primary'
|
||||
shape='circle'
|
||||
icon='message'
|
||||
onClick={() => toggleChatVisible()}
|
||||
/>
|
||||
</Badge>
|
||||
)}
|
||||
</Affix>
|
||||
);
|
||||
});
|
||||
@@ -4,41 +4,34 @@ import CKEditor from "@ckeditor/ckeditor5-react";
|
||||
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
|
||||
|
||||
export default function SendEmailButtonComponent({
|
||||
emailConfig,
|
||||
messageOptions,
|
||||
handleConfigChange,
|
||||
handleHtmlChange
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
THis is where the text editing will happen To
|
||||
<Input
|
||||
defaultValue={emailConfig.to}
|
||||
defaultValue={messageOptions.to}
|
||||
onChange={handleConfigChange}
|
||||
name='to'
|
||||
/>
|
||||
CC
|
||||
<Input
|
||||
defaultValue={emailConfig.cc}
|
||||
defaultValue={messageOptions.cc}
|
||||
onChange={handleConfigChange}
|
||||
name='cc'
|
||||
/>
|
||||
Subject
|
||||
<Input
|
||||
defaultValue={emailConfig.subject}
|
||||
defaultValue={messageOptions.subject}
|
||||
onChange={handleConfigChange}
|
||||
name='subject'
|
||||
/>
|
||||
<CKEditor
|
||||
editor={ClassicEditor}
|
||||
data={emailConfig.html}
|
||||
onInit={editor => {
|
||||
// You can store the "editor" and use when it is needed.
|
||||
console.log("Editor is ready to use!", editor);
|
||||
}}
|
||||
data={messageOptions.html}
|
||||
onChange={(event, editor) => {
|
||||
const data = editor.getData();
|
||||
console.log({ event, editor, data });
|
||||
handleHtmlChange(data);
|
||||
handleHtmlChange(editor.getData());
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
104
client/src/components/email-overlay/email-overlay.container.jsx
Normal file
104
client/src/components/email-overlay/email-overlay.container.jsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Button, Modal, notification } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useLazyQuery } from "react-apollo";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { toggleEmailOverlayVisible } from "../../redux/email/email.actions";
|
||||
import { selectEmailConfig, selectEmailVisible } from "../../redux/email/email.selectors.js";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import EmailOverlayComponent from "./email-overlay.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
modalVisible: selectEmailVisible,
|
||||
emailConfig: selectEmailConfig
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible())
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(function SendEmail({ emailConfig, modalVisible, toggleEmailOverlayVisible }) {
|
||||
const { t } = useTranslation();
|
||||
const [messageOptions, setMessageOptions] = useState(
|
||||
emailConfig.messageOptions
|
||||
);
|
||||
useEffect(() => {
|
||||
setMessageOptions(emailConfig.messageOptions);
|
||||
}, [setMessageOptions, emailConfig.messageOptions]);
|
||||
|
||||
const [executeQuery, { called, loading, data }] = useLazyQuery(
|
||||
emailConfig.queryConfig[0],
|
||||
{
|
||||
...emailConfig.queryConfig[1],
|
||||
fetchPolicy: "network-only"
|
||||
}
|
||||
);
|
||||
|
||||
if (
|
||||
emailConfig.queryConfig[0] &&
|
||||
emailConfig.queryConfig[1] &&
|
||||
modalVisible &&
|
||||
!called
|
||||
) {
|
||||
executeQuery();
|
||||
}
|
||||
|
||||
if (data && !messageOptions.html && emailConfig.template) {
|
||||
setMessageOptions({
|
||||
...messageOptions,
|
||||
html: ReactDOMServer.renderToStaticMarkup(
|
||||
<emailConfig.template data={data} />
|
||||
)
|
||||
//html: renderEmail(<emailConfig.template data={data} />)
|
||||
});
|
||||
}
|
||||
|
||||
const handleOk = () => {
|
||||
//sendEmail(messageOptions);
|
||||
axios
|
||||
.post("/sendemail", messageOptions)
|
||||
.then(response => {
|
||||
console.log(JSON.stringify(response));
|
||||
notification["success"]({ message: t("emails.successes.sent") });
|
||||
toggleEmailOverlayVisible();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(JSON.stringify(error));
|
||||
notification["error"]({
|
||||
message: t("emails.errors.notsent", { message: error.message })
|
||||
});
|
||||
});
|
||||
};
|
||||
const handleConfigChange = event => {
|
||||
const { name, value } = event.target;
|
||||
setMessageOptions({ ...messageOptions, [name]: value });
|
||||
};
|
||||
const handleHtmlChange = text => {
|
||||
setMessageOptions({ ...messageOptions, html: text });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
destroyOnClose={true}
|
||||
visible={modalVisible}
|
||||
width={"80%"}
|
||||
onOk={handleOk}
|
||||
onCancel={() => toggleEmailOverlayVisible()}>
|
||||
<LoadingSpinner loading={loading}>
|
||||
<EmailOverlayComponent
|
||||
handleConfigChange={handleConfigChange}
|
||||
messageOptions={messageOptions}
|
||||
handleHtmlChange={handleHtmlChange}
|
||||
/>
|
||||
</LoadingSpinner>
|
||||
</Modal>
|
||||
|
||||
<Button onClick={() => toggleEmailOverlayVisible()}>Show</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -10,7 +10,7 @@ export default function ResetForm({ resetFields }) {
|
||||
message={
|
||||
<div>
|
||||
{t("general.messages.unsavedchanges")}
|
||||
<Button onClick={() => resetFields()}>
|
||||
<Button style={{ marginLeft: "20px" }} onClick={() => resetFields()}>
|
||||
{t("general.actions.reset")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -15,107 +15,168 @@ export default ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
//TODO Add
|
||||
|
||||
return (
|
||||
<Row type='flex' justify='space-around' align='middle'>
|
||||
<Row type="flex" justify="space-around" align="middle">
|
||||
{logo ? (
|
||||
<Col span={4}>
|
||||
<img alt='Shop Logo' src={logo} style={{ height: "40px" }} />
|
||||
<Col span={3}>
|
||||
<img alt="Shop Logo" src={logo} style={{ height: "40px" }} />
|
||||
</Col>
|
||||
) : null}
|
||||
<Col span={14}>
|
||||
<Menu
|
||||
theme='dark'
|
||||
className='header'
|
||||
selectedKeys={selectedNavItem}
|
||||
mode='horizontal'
|
||||
onClick={handleMenuClick}>
|
||||
<Menu.Item key='home'>
|
||||
<Link to='/manage'>
|
||||
<Icon type='home' />
|
||||
{t("menus.header.home")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu title={t("menus.header.jobs")}>
|
||||
<Menu.Item key='schedule'>
|
||||
<Link to='/manage/schedule'>
|
||||
<Icon type='calendar' />
|
||||
{t("menus.header.schedule")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key='activejobs'>
|
||||
<Link to='/manage/jobs'>{t("menus.header.activejobs")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key='availablejobs'>
|
||||
<Link to='/manage/available'>
|
||||
{t("menus.header.availablejobs")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
{landingHeader ? (
|
||||
<Menu
|
||||
theme="dark"
|
||||
className="header"
|
||||
selectedKeys={selectedNavItem}
|
||||
mode="horizontal"
|
||||
onClick={handleMenuClick}
|
||||
>
|
||||
<ManageSignInButton />
|
||||
|
||||
<Menu.SubMenu title={t("menus.header.customers")}>
|
||||
<Menu.Item key='owners'>
|
||||
<Link to='/manage/owners'>
|
||||
<Icon type='team' />
|
||||
{t("menus.header.owners")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key='vehicles'>
|
||||
<Link to='/manage/vehicles'>
|
||||
<Icon type='car' />
|
||||
{t("menus.header.vehicles")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu title={t("menus.header.shop")}>
|
||||
<Menu.Item key='shop'>
|
||||
<Link to='/manage/shop'>{t("menus.header.shop_config")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key='shop-vendors'>
|
||||
<Link to='/manage/shop/vendors'>
|
||||
{t("menus.header.shop_vendors")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
|
||||
<Menu.SubMenu
|
||||
title={
|
||||
<div>
|
||||
<Avatar
|
||||
size='medium'
|
||||
alt='Avatar'
|
||||
src={currentUser.photoURL ? currentUser.photoURL : UserImage}
|
||||
style={{ margin: "10px" }}
|
||||
/>
|
||||
{currentUser.displayName || t("general.labels.unknown")}
|
||||
</div>
|
||||
}>
|
||||
<Menu.Item onClick={signOutStart()}>
|
||||
{t("user.actions.signout")}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Link to='/manage/profile'>{t("menus.currentuser.profile")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu
|
||||
title={
|
||||
<span>
|
||||
<Icon type='global' />
|
||||
<span>{t("menus.currentuser.languageselector")}</span>
|
||||
</span>
|
||||
}>
|
||||
<Menu.Item actiontype='lang-select' key='en_us'>
|
||||
{t("general.languages.english")}
|
||||
<div>
|
||||
<Avatar
|
||||
size="medium"
|
||||
alt="Avatar"
|
||||
src={
|
||||
currentUser.photoURL ? currentUser.photoURL : UserImage
|
||||
}
|
||||
style={{ margin: "10px" }}
|
||||
/>
|
||||
{currentUser.displayName || t("general.labels.unknown")}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Menu.Item onClick={signOutStart()}>
|
||||
{t("user.actions.signout")}
|
||||
</Menu.Item>
|
||||
<Menu.Item actiontype='lang-select' key='fr'>
|
||||
{t("general.languages.french")}
|
||||
<Menu.Item>
|
||||
<Link to="/manage/profile">
|
||||
{t("menus.currentuser.profile")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item actiontype='lang-select' key='es'>
|
||||
{t("general.languages.spanish")}
|
||||
<Menu.SubMenu
|
||||
title={
|
||||
<span>
|
||||
<Icon type="global" />
|
||||
<span>{t("menus.currentuser.languageselector")}</span>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Menu.Item actiontype="lang-select" key="en_us">
|
||||
{t("general.languages.english")}
|
||||
</Menu.Item>
|
||||
<Menu.Item actiontype="lang-select" key="fr">
|
||||
{t("general.languages.french")}
|
||||
</Menu.Item>
|
||||
<Menu.Item actiontype="lang-select" key="es">
|
||||
{t("general.languages.spanish")}
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
</Menu.SubMenu>
|
||||
</Menu>
|
||||
) : (
|
||||
<Menu
|
||||
theme="dark"
|
||||
className="header"
|
||||
selectedKeys={selectedNavItem}
|
||||
mode="horizontal"
|
||||
onClick={handleMenuClick}
|
||||
>
|
||||
<Menu.Item key="home">
|
||||
<Link to="/manage">
|
||||
<Icon type="home" />
|
||||
{t("menus.header.home")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu title={t("menus.header.jobs")}>
|
||||
<Menu.Item key="schedule">
|
||||
<Link to="/manage/schedule">
|
||||
<Icon type="calendar" />
|
||||
{t("menus.header.schedule")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="activejobs">
|
||||
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="availablejobs">
|
||||
<Link to="/manage/available">
|
||||
{t("menus.header.availablejobs")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
</Menu.SubMenu>
|
||||
</Menu>
|
||||
<Menu.SubMenu title={t("menus.header.customers")}>
|
||||
<Menu.Item key="owners">
|
||||
<Link to="/manage/owners">
|
||||
<Icon type="team" />
|
||||
{t("menus.header.owners")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="vehicles">
|
||||
<Link to="/manage/vehicles">
|
||||
<Icon type="car" />
|
||||
{t("menus.header.vehicles")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu title={t("menus.header.shop")}>
|
||||
<Menu.Item key="shop">
|
||||
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="shop-vendors">
|
||||
<Link to="/manage/shop/vendors">
|
||||
{t("menus.header.shop_vendors")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
|
||||
<Menu.SubMenu
|
||||
title={
|
||||
<div>
|
||||
<Avatar
|
||||
size="medium"
|
||||
alt="Avatar"
|
||||
src={
|
||||
currentUser.photoURL ? currentUser.photoURL : UserImage
|
||||
}
|
||||
style={{ margin: "10px" }}
|
||||
/>
|
||||
{currentUser.displayName || t("general.labels.unknown")}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Menu.Item onClick={signOutStart()}>
|
||||
{t("user.actions.signout")}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Link to="/manage/profile">
|
||||
{t("menus.currentuser.profile")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu
|
||||
title={
|
||||
<span>
|
||||
<Icon type="global" />
|
||||
<span>{t("menus.currentuser.languageselector")}</span>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Menu.Item actiontype="lang-select" key="en_us">
|
||||
{t("general.languages.english")}
|
||||
</Menu.Item>
|
||||
<Menu.Item actiontype="lang-select" key="fr">
|
||||
{t("general.languages.french")}
|
||||
</Menu.Item>
|
||||
<Menu.Item actiontype="lang-select" key="es">
|
||||
{t("general.languages.spanish")}
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
</Menu.SubMenu>
|
||||
</Menu>
|
||||
)}
|
||||
</Col>
|
||||
<Col span={4}>{!landingHeader ? null : <ManageSignInButton />}</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Modal, Form, Input, InputNumber } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ResetForm from "../form-items-formatted/reset-form-item.component";
|
||||
|
||||
export default function InvoiceEnterModalComponent({
|
||||
visible,
|
||||
invoice,
|
||||
handleCancel,
|
||||
handleSubmit,
|
||||
form
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { getFieldDecorator, isFieldsTouched, resetFields } = form;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
invoice && invoice.id
|
||||
? t("invoice.labels.edit")
|
||||
: t("invoice.labels.new")
|
||||
}
|
||||
visible={visible}
|
||||
okText={t("general.labels.save")}
|
||||
onOk={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
|
||||
<Form onSubmit={handleSubmit} autoComplete={"off"}>
|
||||
{JSON.stringify(invoice)}
|
||||
{
|
||||
// <Form.Item label={t("joblines.fields.line_desc")}>
|
||||
// {getFieldDecorator("line_desc", {
|
||||
// initialValue: jobLine.line_desc
|
||||
// })(<Input name="line_desc" />)}
|
||||
// </Form.Item>
|
||||
// <Form.Item label={t("joblines.fields.oem_partno")}>
|
||||
// {getFieldDecorator("oem_partno", {
|
||||
// initialValue: jobLine.oem_partno
|
||||
// })(<Input name="oem_partno" />)}
|
||||
// </Form.Item>
|
||||
// <Form.Item label={t("joblines.fields.part_type")}>
|
||||
// {getFieldDecorator("part_type", {
|
||||
// initialValue: jobLine.part_type
|
||||
// })(<Input name="part_type" />)}
|
||||
// </Form.Item>
|
||||
// <Form.Item label={t("joblines.fields.mod_lbr_ty")}>
|
||||
// {getFieldDecorator("mod_lbr_ty", {
|
||||
// initialValue: jobLine.mod_lbr_ty
|
||||
// })(<Input name="mod_lbr_ty" />)}
|
||||
// </Form.Item>
|
||||
// <Form.Item label={t("joblines.fields.op_code_desc")}>
|
||||
// {getFieldDecorator("op_code_desc", {
|
||||
// initialValue: jobLine.op_code_desc
|
||||
// })(<Input name="op_code_desc" />)}
|
||||
// </Form.Item>
|
||||
// <Form.Item label={t("joblines.fields.mod_lb_hrs")}>
|
||||
// {getFieldDecorator("mod_lb_hrs", {
|
||||
// initialValue: jobLine.mod_lb_hrs
|
||||
// })(<InputNumber name="mod_lb_hrs" />)}
|
||||
// </Form.Item>
|
||||
// <Form.Item label={t("joblines.fields.act_price")}>
|
||||
// {getFieldDecorator("act_price", {
|
||||
// initialValue: jobLine.act_price
|
||||
// })(<InputNumber name="act_price" />)}
|
||||
// </Form.Item>
|
||||
}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Form, notification } from "antd";
|
||||
import React from "react";
|
||||
import { useMutation } from "react-apollo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
INSERT_NEW_JOB_LINE,
|
||||
UPDATE_JOB_LINE
|
||||
} from "../../graphql/jobs-lines.queries";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectInvoiceEnterModal } from "../../redux/modals/modals.selectors";
|
||||
import InvoiceEnterModalComponent from "./invoice-enter-modal.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
invoiceEnterModal: selectInvoiceEnterModal
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("invoiceEnter"))
|
||||
});
|
||||
|
||||
function InvoiceEnterModalContainer({
|
||||
invoiceEnterModal,
|
||||
toggleModalVisible,
|
||||
form
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
// const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
|
||||
// const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
|
||||
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
|
||||
form.validateFieldsAndScroll((err, values) => {
|
||||
if (err) {
|
||||
notification["error"]({
|
||||
message: t("invoices.errors.validation"),
|
||||
description: err.message
|
||||
});
|
||||
}
|
||||
if (!err) {
|
||||
alert("Closing this modal.");
|
||||
toggleModalVisible();
|
||||
// if (!jobLineEditModal.context.id) {
|
||||
// insertJobLine({
|
||||
// variables: {
|
||||
// lineInput: [{ jobid: jobLineEditModal.context.jobid, ...values }]
|
||||
// }
|
||||
// })
|
||||
// .then(r => {
|
||||
// if (jobLineEditModal.actions.refetch)
|
||||
// jobLineEditModal.actions.refetch();
|
||||
// toggleModalVisible();
|
||||
// notification["success"]({
|
||||
// message: t("joblines.successes.created")
|
||||
// });
|
||||
// })
|
||||
// .catch(error => {
|
||||
// notification["error"]({
|
||||
// message: t("joblines.errors.creating", {
|
||||
// message: error.message
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// } else {
|
||||
// updateJobLine({
|
||||
// variables: {
|
||||
// lineId: jobLineEditModal.context.id,
|
||||
// line: values
|
||||
// }
|
||||
// })
|
||||
// .then(r => {
|
||||
// notification["success"]({
|
||||
// message: t("joblines.successes.updated")
|
||||
// });
|
||||
// })
|
||||
// .catch(error => {
|
||||
// notification["success"]({
|
||||
// message: t("joblines.errors.updating", {
|
||||
// message: error.message
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// if (jobLineEditModal.actions.refetch)
|
||||
// jobLineEditModal.actions.refetch();
|
||||
// toggleModalVisible();
|
||||
// }
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
toggleModalVisible();
|
||||
};
|
||||
|
||||
return (
|
||||
<InvoiceEnterModalComponent
|
||||
visible={invoiceEnterModal.visible}
|
||||
invoice={invoiceEnterModal.context}
|
||||
handleSubmit={handleSubmit}
|
||||
handleCancel={handleCancel}
|
||||
form={form}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(
|
||||
Form.create({ name: "InvoiceEnterModalContainer" })(
|
||||
InvoiceEnterModalContainer
|
||||
)
|
||||
);
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Button, Input, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
//import EditableCell from "./job-lines-cell.component";
|
||||
import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
|
||||
import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
|
||||
import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
|
||||
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||
|
||||
export default function JobLinesComponent({
|
||||
@@ -15,7 +17,8 @@ export default function JobLinesComponent({
|
||||
selectedLines,
|
||||
setSelectedLines,
|
||||
partsOrderModalVisible,
|
||||
jobId
|
||||
jobId,
|
||||
setJobLineEditContext
|
||||
}) {
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {}
|
||||
@@ -44,7 +47,8 @@ export default function JobLinesComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
editable: true
|
||||
editable: true,
|
||||
width: "20%"
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.oem_partno"),
|
||||
@@ -59,7 +63,7 @@ export default function JobLinesComponent({
|
||||
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
editable: true,
|
||||
width: "20%",
|
||||
width: "10%",
|
||||
render: (text, record) => (
|
||||
<span>
|
||||
{record.oem_partno ? record.oem_partno : record.op_code_desc}
|
||||
@@ -75,7 +79,15 @@ export default function JobLinesComponent({
|
||||
state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
editable: true,
|
||||
width: "10%"
|
||||
width: "7%"
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.line_ind"),
|
||||
dataIndex: "line_ind",
|
||||
key: "line_ind",
|
||||
sorter: (a, b) => alphaSort(a.line_ind, b.line_ind),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.db_price"),
|
||||
@@ -85,7 +97,7 @@ export default function JobLinesComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "db_price" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
width: "10%",
|
||||
width: "8%",
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
|
||||
)
|
||||
@@ -98,7 +110,7 @@ export default function JobLinesComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
width: "10%",
|
||||
width: "8%",
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||
)
|
||||
@@ -111,21 +123,39 @@ export default function JobLinesComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("allocations.fields.employee"),
|
||||
dataIndex: "employee",
|
||||
key: "employee",
|
||||
sorter: (a, b) => a.act_price - b.act_price, //TODO Fix employee sorting.
|
||||
width: "10%",
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
a.allocations[0] &&
|
||||
a.allocations[0].employee.first_name +
|
||||
a.allocations[0].employee.last_name,
|
||||
b.allocations[0] &&
|
||||
b.allocations[0].employee.first_name +
|
||||
b.allocations[0].employee.last_name
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<span>
|
||||
{record.allocations && record.allocations.length > 0
|
||||
? record.allocations.map(item => (
|
||||
<div
|
||||
key={
|
||||
item.id
|
||||
}>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div>
|
||||
<AllocationsEmployeeLabelContainer
|
||||
key={item.id}
|
||||
refetch={refetch}
|
||||
allocation={item}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
<AllocationsAssignmentContainer
|
||||
@@ -143,7 +173,16 @@ export default function JobLinesComponent({
|
||||
key: "actions",
|
||||
render: (text, record) => (
|
||||
<span>
|
||||
<Button>{t("general.actions.edit")}</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setJobLineEditContext({
|
||||
actions: { refetch: refetch },
|
||||
context: record
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("general.actions.edit")}
|
||||
</Button>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -169,6 +208,7 @@ export default function JobLinesComponent({
|
||||
<PartsOrderModalContainer
|
||||
partsOrderModalVisible={partsOrderModalVisible}
|
||||
linesToOrder={selectedLines}
|
||||
refetch={refetch}
|
||||
jobId={jobId}
|
||||
/>
|
||||
|
||||
@@ -185,23 +225,55 @@ export default function JobLinesComponent({
|
||||
/>
|
||||
<Button
|
||||
disabled={selectedLines.length > 0 ? false : true}
|
||||
onClick={() => setPartsModalVisible(true)}>
|
||||
onClick={() => setPartsModalVisible(true)}
|
||||
>
|
||||
{t("parts.actions.order")}
|
||||
</Button>
|
||||
<AllocationsBulkAssignmentContainer
|
||||
jobLines={selectedLines}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setJobLineEditContext({
|
||||
actions: { refetch: refetch },
|
||||
context: { jobid: jobId }
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("joblines.actions.new")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
{...formItemLayout}
|
||||
loading={loading}
|
||||
size='small'
|
||||
pagination={{ position: "bottom", defaultPageSize: 50 }}
|
||||
size="small"
|
||||
expandedRowRender={record => (
|
||||
<div style={{ margin: 0 }}>
|
||||
<strong>{t("parts_orders.labels.orderhistory")}</strong>
|
||||
{record.parts_order_lines.map(item => (
|
||||
<div key={item.id}>
|
||||
{`${item.parts_order.order_number || ""} from `}
|
||||
<Link to={`/manage/shop/vendors/${item.parts_order.vendor.id}`}>
|
||||
{item.parts_order.vendor.name || ""}
|
||||
</Link>
|
||||
{` on ${item.parts_order.order_date || ""}`}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||
rowSelection={{
|
||||
// selectedRowKeys: selectedLines,
|
||||
onSelectAll: (selected, selectedRows, changeRows) => {
|
||||
setSelectedLines(selectedRows);
|
||||
},
|
||||
onSelect: (record, selected, selectedRows, nativeEvent) =>
|
||||
setSelectedLines(selectedRows)
|
||||
}}
|
||||
columns={columns.map(item => ({ ...item }))}
|
||||
rowKey='id'
|
||||
rowKey="id"
|
||||
dataSource={jobLines}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
|
||||
@@ -5,9 +5,16 @@ import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobLinesComponent from "./job-lines.component";
|
||||
|
||||
//export default Form.create({ name: "JobsDetailJobLines" })(
|
||||
|
||||
export default function JobLinesContainer({ jobId }) {
|
||||
import { connect } from "react-redux";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setJobLineEditContext: context =>
|
||||
dispatch(setModalContext({ context: context, modal: "jobLineEdit" }))
|
||||
});
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(function JobLinesContainer({ jobId, setJobLineEditContext }) {
|
||||
const { loading, error, data, refetch } = useQuery(GET_JOB_LINES_BY_PK, {
|
||||
variables: { id: jobId },
|
||||
fetchPolicy: "network-only"
|
||||
@@ -16,7 +23,8 @@ export default function JobLinesContainer({ jobId }) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [selectedLines, setSelectedLines] = useState([]);
|
||||
const partsOrderModalVisible = useState(false);
|
||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<JobLinesComponent
|
||||
@@ -58,7 +66,7 @@ export default function JobLinesContainer({ jobId }) {
|
||||
setSelectedLines={setSelectedLines}
|
||||
partsOrderModalVisible={partsOrderModalVisible}
|
||||
jobId={jobId}
|
||||
setJobLineEditContext={setJobLineEditContext}
|
||||
/>
|
||||
);
|
||||
}
|
||||
//);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Modal, Form, Input, InputNumber } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ResetForm from "../form-items-formatted/reset-form-item.component";
|
||||
|
||||
export default function JobLinesUpsertModalComponent({
|
||||
visible,
|
||||
jobLine,
|
||||
handleOk,
|
||||
handleCancel,
|
||||
handleSubmit,
|
||||
form
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { getFieldDecorator, isFieldsTouched, resetFields } = form;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
jobLine && jobLine.id
|
||||
? t("joblines.labels.edit")
|
||||
: t("joblines.labels.new")
|
||||
}
|
||||
visible={visible}
|
||||
okText={t("general.labels.save")}
|
||||
onOk={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
{isFieldsTouched() ? <ResetForm resetFields={resetFields} /> : null}
|
||||
<Form onSubmit={handleSubmit} autoComplete={"off"}>
|
||||
<Form.Item label={t("joblines.fields.line_desc")}>
|
||||
{getFieldDecorator("line_desc", {
|
||||
initialValue: jobLine.line_desc
|
||||
})(<Input name="line_desc" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("joblines.fields.oem_partno")}>
|
||||
{getFieldDecorator("oem_partno", {
|
||||
initialValue: jobLine.oem_partno
|
||||
})(<Input name="oem_partno" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("joblines.fields.part_type")}>
|
||||
{getFieldDecorator("part_type", {
|
||||
initialValue: jobLine.part_type
|
||||
})(<Input name="part_type" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("joblines.fields.mod_lbr_ty")}>
|
||||
{getFieldDecorator("mod_lbr_ty", {
|
||||
initialValue: jobLine.mod_lbr_ty
|
||||
})(<Input name="mod_lbr_ty" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("joblines.fields.op_code_desc")}>
|
||||
{getFieldDecorator("op_code_desc", {
|
||||
initialValue: jobLine.op_code_desc
|
||||
})(<Input name="op_code_desc" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("joblines.fields.mod_lb_hrs")}>
|
||||
{getFieldDecorator("mod_lb_hrs", {
|
||||
initialValue: jobLine.mod_lb_hrs
|
||||
})(<InputNumber name="mod_lb_hrs" />)}
|
||||
</Form.Item>
|
||||
<Form.Item label={t("joblines.fields.act_price")}>
|
||||
{getFieldDecorator("act_price", {
|
||||
initialValue: jobLine.act_price
|
||||
})(<InputNumber name="act_price" />)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Form, notification } from "antd";
|
||||
import React from "react";
|
||||
import { useMutation } from "react-apollo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
INSERT_NEW_JOB_LINE,
|
||||
UPDATE_JOB_LINE
|
||||
} from "../../graphql/jobs-lines.queries";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
||||
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobLineEditModal: selectJobLineEditModal
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit"))
|
||||
});
|
||||
|
||||
function JobLinesUpsertModalContainer({
|
||||
jobLineEditModal,
|
||||
toggleModalVisible,
|
||||
form
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
|
||||
const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
|
||||
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
|
||||
form.validateFieldsAndScroll((err, values) => {
|
||||
if (err) {
|
||||
notification["error"]({
|
||||
message: t("joblines.errors.validation"),
|
||||
description: err.message
|
||||
});
|
||||
}
|
||||
if (!err) {
|
||||
if (!jobLineEditModal.context.id) {
|
||||
insertJobLine({
|
||||
variables: {
|
||||
lineInput: [{ jobid: jobLineEditModal.context.jobid, ...values }]
|
||||
}
|
||||
})
|
||||
.then(r => {
|
||||
if (jobLineEditModal.actions.refetch)
|
||||
jobLineEditModal.actions.refetch();
|
||||
toggleModalVisible();
|
||||
notification["success"]({
|
||||
message: t("joblines.successes.created")
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
notification["error"]({
|
||||
message: t("joblines.errors.creating", {
|
||||
message: error.message
|
||||
})
|
||||
});
|
||||
});
|
||||
} else {
|
||||
updateJobLine({
|
||||
variables: {
|
||||
lineId: jobLineEditModal.context.id,
|
||||
line: values
|
||||
}
|
||||
})
|
||||
.then(r => {
|
||||
notification["success"]({
|
||||
message: t("joblines.successes.updated")
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
notification["success"]({
|
||||
message: t("joblines.errors.updating", {
|
||||
message: error.message
|
||||
})
|
||||
});
|
||||
});
|
||||
if (jobLineEditModal.actions.refetch)
|
||||
jobLineEditModal.actions.refetch();
|
||||
toggleModalVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
toggleModalVisible();
|
||||
};
|
||||
|
||||
return (
|
||||
<JobLinesUpdsertModal
|
||||
visible={jobLineEditModal.visible}
|
||||
jobLine={jobLineEditModal.context}
|
||||
handleSubmit={handleSubmit}
|
||||
handleCancel={handleCancel}
|
||||
form={form}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(
|
||||
Form.create({ name: "JobsDetailPageContainer" })(JobLinesUpsertModalContainer)
|
||||
);
|
||||
@@ -54,12 +54,13 @@ export default withRouter(function JobsAvailableSupplementContainer({
|
||||
//create upsert job
|
||||
let supp = estData.data.available_jobs_by_pk.est_data;
|
||||
delete supp.joblines;
|
||||
//TODO How to update the estimate lines.
|
||||
delete supp.owner;
|
||||
delete supp.vehicle;
|
||||
|
||||
if (!importOptions.overrideHeaders) {
|
||||
delete supp["ins_ea"];
|
||||
//Strip out the header options
|
||||
//TODO Remove all required fields.
|
||||
}
|
||||
|
||||
updateJob({
|
||||
@@ -101,12 +102,11 @@ export default withRouter(function JobsAvailableSupplementContainer({
|
||||
setSelectedJob(null);
|
||||
};
|
||||
|
||||
if (error) return <AlertComponent type="error" message={error.message} />;
|
||||
if (error) return <AlertComponent type='error' message={error.message} />;
|
||||
return (
|
||||
<LoadingSpinner
|
||||
loading={insertLoading}
|
||||
message={t("jobs.labels.creating_new_job")}
|
||||
>
|
||||
message={t("jobs.labels.creating_new_job")}>
|
||||
<JobsAvailableSupplementComponent
|
||||
loading={loading}
|
||||
data={data}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
toggleModalVisible,
|
||||
setModalContext
|
||||
} from "../../redux/modals/modals.actions";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("invoiceEnter")),
|
||||
setInvoiceEnterContext: context =>
|
||||
dispatch(setModalContext({ context: context, modal: "invoiceEnter" }))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(function JobsDetailPliComponent({
|
||||
toggleModalVisible,
|
||||
setInvoiceEnterContext,
|
||||
job
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
onClick={() => {
|
||||
setInvoiceEnterContext({
|
||||
actions: { refetch: null },
|
||||
context: {
|
||||
job
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Enter Invoice
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
import JobsDetailPliComponent from "./jobs-detail-pli.component";
|
||||
|
||||
export default function JobsDetailPliContainer({ job }) {
|
||||
console.log("job", job);
|
||||
return <JobsDetailPliComponent job={job} />;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Input, Table, Icon, Button } from "antd";
|
||||
import { Button, Icon, Input, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, withRouter } from "react-router-dom";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||
|
||||
export default withRouter(function JobsList({
|
||||
searchTextState,
|
||||
@@ -78,13 +78,7 @@ export default withRouter(function JobsList({
|
||||
return record.ownr_ph1 ? (
|
||||
<span>
|
||||
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
||||
<Icon
|
||||
style={{ margin: 4 }}
|
||||
type='message'
|
||||
onClick={() => {
|
||||
alert("SMSing will happen here.");
|
||||
}}
|
||||
/>
|
||||
<StartChatButton phone={record.ownr_ph1} />
|
||||
</span>
|
||||
) : (
|
||||
t("general.labels.unknown")
|
||||
@@ -214,10 +208,10 @@ export default withRouter(function JobsList({
|
||||
return (
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button onClick={() => refetch()}>
|
||||
<Icon type='sync' />
|
||||
<Icon type="sync" />
|
||||
</Button>
|
||||
<Input.Search
|
||||
placeholder='Search...'
|
||||
placeholder="Search..."
|
||||
onChange={e => {
|
||||
setSearchText(e.target.value);
|
||||
}}
|
||||
@@ -226,10 +220,10 @@ export default withRouter(function JobsList({
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
size='small'
|
||||
size="small"
|
||||
pagination={{ position: "top" }}
|
||||
columns={columns.map(item => ({ ...item }))}
|
||||
rowKey='id'
|
||||
rowKey="id"
|
||||
dataSource={jobs}
|
||||
rowSelection={{ selectedRowKeys: [selectedJob] }}
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -5,12 +5,20 @@ import { useTranslation } from "react-i18next";
|
||||
import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries";
|
||||
import NoteUpsertModalComponent from "./note-upsert-modal.component";
|
||||
|
||||
export default function NoteUpsertModalContainer({
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
export default connect (mapStateToProps,null)( function NoteUpsertModalContainer({
|
||||
jobId,
|
||||
visible,
|
||||
changeVisibility,
|
||||
refetch,
|
||||
existingNote
|
||||
existingNote,currentUser
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
||||
@@ -33,7 +41,7 @@ export default function NoteUpsertModalContainer({
|
||||
insertNote({
|
||||
variables: {
|
||||
noteInput: [
|
||||
{ ...noteState, jobid: jobId, created_by: "patrick@bodyshop.app" } //TODO Fix the created by.
|
||||
{ ...noteState, jobid: jobId, created_by: currentUser.email }
|
||||
]
|
||||
}
|
||||
}).then(r => {
|
||||
@@ -73,3 +81,4 @@ export default function NoteUpsertModalContainer({
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { AutoComplete, DatePicker, Icon, Input, List, Radio } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { AutoComplete, Icon, DatePicker, Radio } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function PartsOrderModalComponent({
|
||||
vendorList,
|
||||
state,
|
||||
sendTypeState
|
||||
sendTypeState,
|
||||
orderLinesState
|
||||
}) {
|
||||
const [partsOrder, setPartsOrder] = state;
|
||||
const [sendType, setSendType] = sendTypeState;
|
||||
const orderLines = orderLinesState[0];
|
||||
const [vendorComplete, setVendorComplete] = useState(vendorList);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -22,8 +24,6 @@ export default function PartsOrderModalComponent({
|
||||
};
|
||||
|
||||
const handleSelect = (value, option) => {
|
||||
console.log("value", value);
|
||||
console.log("option", option);
|
||||
setPartsOrder({ ...partsOrder, vendorid: option.key });
|
||||
};
|
||||
|
||||
@@ -52,6 +52,33 @@ export default function PartsOrderModalComponent({
|
||||
}}
|
||||
/>
|
||||
|
||||
{t("parts_orders.labels.inthisorder")}
|
||||
|
||||
<List
|
||||
itemLayout='horizontal'
|
||||
dataSource={orderLines}
|
||||
renderItem={item => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<Input placeholder={t("parts_orders.fields.lineremarks")} />
|
||||
//TODO Editable table/adding line remarks to the order.
|
||||
]}>
|
||||
{
|
||||
// <List.Item.Meta
|
||||
// avatar={
|
||||
// <Avatar src='https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png' />
|
||||
// }
|
||||
// title={<a href='https://ant.design'>{item.name.last}</a>}
|
||||
// description='Ant Design, a design language for background applications, is refined by Ant UED Team'
|
||||
// />
|
||||
}
|
||||
<div>{`${item.line_desc}${
|
||||
item.oem_partno ? " | " + item.oem_partno : ""
|
||||
}`}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Radio.Group
|
||||
defaultValue={sendType}
|
||||
onChange={e => setSendType(e.target.value)}>
|
||||
|
||||
@@ -1,50 +1,105 @@
|
||||
import { Modal, notification } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useQuery, useMutation } from "react-apollo";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useMutation, useQuery } from "react-apollo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries";
|
||||
import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.queries";
|
||||
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
|
||||
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries";
|
||||
import {
|
||||
selectCurrentUser,
|
||||
selectBodyshop
|
||||
selectBodyshop,
|
||||
selectCurrentUser
|
||||
} from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import PartsOrderModalComponent from "./parts-order-modal.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
setEmailOptions,
|
||||
toggleEmailOverlayVisible
|
||||
} from "../../redux/email/email.actions";
|
||||
import PartsOrderEmailTemplate from "../../emails/parts-order/parts-order.email";
|
||||
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../emails/parts-order/parts-order.query";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setEmailOptions: e => dispatch(setEmailOptions(e)),
|
||||
toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible())
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
mapDispatchToProps
|
||||
)(function PartsOrderModalContainer({
|
||||
partsOrderModalVisible,
|
||||
linesToOrder,
|
||||
jobId,
|
||||
currentUser,
|
||||
bodyshop
|
||||
bodyshop,
|
||||
refetch,
|
||||
setEmailOptions,
|
||||
toggleEmailOverlayVisible
|
||||
}) {
|
||||
const [modalVisible, setModalVisible] = partsOrderModalVisible;
|
||||
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
|
||||
fetchPolicy: "network-only",
|
||||
skip: !modalVisible
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
|
||||
const sendTypeState = useState("e");
|
||||
const [modalVisible, setModalVisible] = partsOrderModalVisible;
|
||||
|
||||
//set order lines to be a version of the incoming lines.
|
||||
const orderLinesState = useState(
|
||||
linesToOrder.reduce((acc, value) => {
|
||||
acc.push({
|
||||
line_desc: value.line_desc,
|
||||
oem_partno: value.oem_partno,
|
||||
db_price: value.db_price,
|
||||
act_price: value.act_price,
|
||||
line_remarks: "Alalala",
|
||||
job_line_id: value.id,
|
||||
status: bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||
});
|
||||
return acc;
|
||||
}, [])
|
||||
);
|
||||
const [orderLines, setOrderLinesState] = orderLinesState;
|
||||
useEffect(() => {
|
||||
if (modalVisible)
|
||||
setOrderLinesState(
|
||||
linesToOrder.reduce((acc, value) => {
|
||||
acc.push({
|
||||
line_desc: value.line_desc,
|
||||
oem_partno: value.oem_partno,
|
||||
db_price: value.db_price,
|
||||
act_price: value.act_price,
|
||||
line_remarks: "Alalala",
|
||||
job_line_id: value.id,
|
||||
status: bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||
});
|
||||
return acc;
|
||||
}, [])
|
||||
);
|
||||
}, [
|
||||
modalVisible,
|
||||
setOrderLinesState,
|
||||
linesToOrder,
|
||||
bodyshop.md_order_statuses.default_ordered
|
||||
]);
|
||||
|
||||
const sendTypeState = useState("e");
|
||||
const sendType = sendTypeState[0];
|
||||
const partsOrderState = useState({
|
||||
vendorid: null,
|
||||
jobid: jobId,
|
||||
user_email: currentUser.email
|
||||
});
|
||||
|
||||
console.log("sendTypeState[0]", sendTypeState[0]);
|
||||
const partsOrder = partsOrderState[0];
|
||||
|
||||
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
|
||||
fetchPolicy: "network-only",
|
||||
skip: !modalVisible
|
||||
});
|
||||
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
|
||||
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
|
||||
|
||||
const handleOk = () => {
|
||||
insertPartOrder({
|
||||
variables: {
|
||||
@@ -53,29 +108,58 @@ export default connect(
|
||||
...partsOrder,
|
||||
status: bodyshop.md_order_statuses.default_ordered || "Ordered*",
|
||||
parts_order_lines: {
|
||||
data: linesToOrder.reduce((acc, value) => {
|
||||
acc.push({
|
||||
line_desc: value.line_desc,
|
||||
oem_partno: value.oem_partno,
|
||||
db_price: value.db_price,
|
||||
act_price: value.act_price,
|
||||
line_remarks: "Alalala",
|
||||
joblineid: value.joblineid,
|
||||
status:
|
||||
bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||
});
|
||||
return acc;
|
||||
}, [])
|
||||
data: orderLines
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
.then(r => {
|
||||
notification["success"]({
|
||||
message: t("parts_orders.successes.created")
|
||||
});
|
||||
setModalVisible(false);
|
||||
updateJobLines({
|
||||
variables: {
|
||||
ids: linesToOrder.map(item => item.id),
|
||||
status: bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
notification["success"]({
|
||||
message: t("parts_orders.successes.created")
|
||||
});
|
||||
if (refetch) refetch();
|
||||
setModalVisible(false);
|
||||
|
||||
if (sendType === "e") {
|
||||
//Show the email modal and set the data.
|
||||
|
||||
//TODO Remove some of the options below.
|
||||
setEmailOptions({
|
||||
messageOptions: {
|
||||
from: {
|
||||
name: "Kavia Autobdoy",
|
||||
address: "noreply@bodyshop.app"
|
||||
},
|
||||
to: "patrickwf@gmail.com",
|
||||
replyTo: "snaptsoft@gmail.com"
|
||||
},
|
||||
template: PartsOrderEmailTemplate,
|
||||
queryConfig: [
|
||||
REPORT_QUERY_PARTS_ORDER_BY_PK,
|
||||
{
|
||||
variables: {
|
||||
id: r.data.insert_parts_orders.returning[0].id
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
toggleEmailOverlayVisible();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
notification["error"]({
|
||||
message: t("parts_orders.errors.creating"),
|
||||
description: error.message
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
notification["error"]({
|
||||
@@ -96,6 +180,7 @@ export default connect(
|
||||
vendorList={(data && data.vendors) || []}
|
||||
state={partsOrderState}
|
||||
sendTypeState={sendTypeState}
|
||||
orderLinesState={orderLinesState}
|
||||
/>
|
||||
</LoadingSpinner>
|
||||
</Modal>
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { Button, Modal } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useQuery } from "react-apollo";
|
||||
//Message options has the to & from details
|
||||
//Query Config is what can get popped into UseQuery(QUERY_NAME, {variables: {vars}, fetchonly})
|
||||
//Template Which template should be used to send the email.
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { renderEmail } from "react-html-email";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import SendEmailButtonComponent from "./send-email-button.component";
|
||||
export default function SendEmail({
|
||||
MessageOptions,
|
||||
QueryConfig,
|
||||
Template,
|
||||
...otherProps
|
||||
}) {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [emailConfig, setEmailConfig] = useState({ ...MessageOptions });
|
||||
const [gqlQuery, vars] = QueryConfig;
|
||||
|
||||
const { loading, data } = useQuery(gqlQuery, {
|
||||
...vars,
|
||||
fetchPolicy: "network-only",
|
||||
skip: !modalVisible
|
||||
});
|
||||
if (data && !emailConfig.html) {
|
||||
console.log(ReactDOMServer.renderToStaticMarkup(<Template data={data} />));
|
||||
setEmailConfig({
|
||||
...emailConfig,
|
||||
//html: ReactDOMServer.renderToStaticMarkup(<Template data={data} />)
|
||||
html: renderEmail(<Template data={data} />)
|
||||
});
|
||||
}
|
||||
|
||||
const handleConfigChange = event => {
|
||||
const { name, value } = event.target;
|
||||
setEmailConfig({ ...emailConfig, [name]: value });
|
||||
};
|
||||
|
||||
const handleHtmlChange = text => {
|
||||
setEmailConfig({ ...emailConfig, html: text });
|
||||
};
|
||||
|
||||
const sendEmail = () => {
|
||||
axios.post("/sendemail", emailConfig).then(response => {
|
||||
alert(JSON.stringify(response));
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
destroyOnClose={true}
|
||||
visible={modalVisible}
|
||||
width={"80%"}
|
||||
onOk={sendEmail}
|
||||
onCancel={() => setModalVisible(false)}>
|
||||
<LoadingSpinner loading={loading}>
|
||||
<SendEmailButtonComponent
|
||||
handleConfigChange={handleConfigChange}
|
||||
emailConfig={emailConfig}
|
||||
handleHtmlChange={handleHtmlChange}
|
||||
/>
|
||||
</LoadingSpinner>
|
||||
</Modal>
|
||||
|
||||
<Button onClick={() => setModalVisible(true)}>
|
||||
{otherProps.children}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
function Cell({ children }) {
|
||||
function Column({ children }) {
|
||||
return <td>{children}</td>;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ function Row({ children }) {
|
||||
return (
|
||||
<tr>
|
||||
{React.Children.map(children, el => {
|
||||
if (el.type === Cell) return el;
|
||||
if (el.type === Column) return el;
|
||||
|
||||
return <td>{el}</td>;
|
||||
})}
|
||||
@@ -25,7 +25,7 @@ function Grid({ children }) {
|
||||
|
||||
if (el.type === Row) return el;
|
||||
|
||||
if (el.type === Cell) {
|
||||
if (el.type === Column) {
|
||||
return <tr>{el}</tr>;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,6 @@ function Grid({ children }) {
|
||||
}
|
||||
|
||||
Grid.Row = Row;
|
||||
Grid.Cell = Cell;
|
||||
Grid.Column = Column;
|
||||
|
||||
export default Grid;
|
||||
|
||||
23
client/src/emails/components/header/header.component.jsx
Normal file
23
client/src/emails/components/header/header.component.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Header({ bodyshop }) {
|
||||
return (
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img alt='' src={bodyshop.logo_img_path} />
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<strong>{`${bodyshop.shopname}`}</strong>
|
||||
</div>
|
||||
<div>{`${bodyshop.address1} ${bodyshop.address2} ${bodyshop.city} ${bodyshop.state} ${bodyshop.zip_post}`}</div>
|
||||
<div>
|
||||
<a href={`mailto:${bodyshop.email}`}>{bodyshop.email}</a>{" "}
|
||||
{` | ${bodyshop.ph1}`}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
@@ -1,30 +1,53 @@
|
||||
import React from "react";
|
||||
import { A, Box, Email, Item, Span } from "react-html-email";
|
||||
import styled from "styled-components";
|
||||
import Header from "../components/header/header.component";
|
||||
|
||||
const D = styled.div`
|
||||
table {
|
||||
font-family: arial, sans-serif;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function PartsOrderEmail({ data }) {
|
||||
console.log("Data", data);
|
||||
const order = data.parts_orders_by_pk;
|
||||
return (
|
||||
<Email title='Hello World!'>
|
||||
<Box>
|
||||
<Item align='center'>
|
||||
<Span fontSize={20}>
|
||||
This is an example email made with:
|
||||
<A href='https://github.com/chromakode/react-html-email'>
|
||||
react-html-email
|
||||
</A>
|
||||
.
|
||||
</Span>
|
||||
</Item>
|
||||
<Item align='center'>
|
||||
<Span fontSize={20}>
|
||||
This is an example email made with:
|
||||
<A href='https://github.com/chromakode/react-html-email'>
|
||||
react-html-email
|
||||
</A>
|
||||
.
|
||||
</Span>
|
||||
</Item>
|
||||
</Box>
|
||||
</Email>
|
||||
<D>
|
||||
<Header bodyshop={data.bodyshops[0]} />
|
||||
<table>
|
||||
<tr>
|
||||
<td>{`Deliver By: ${order.deliver_by}`}</td>
|
||||
<td>{`Ordered By: ${order.user_email}`}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Line Description</th>
|
||||
<th>Part #</th>
|
||||
<th>Price</th>
|
||||
<th>Line Remarks</th>
|
||||
</tr>
|
||||
{order.parts_order_lines.map(item => (
|
||||
<tr key={item.id}>
|
||||
<td>{item.line_desc}</td>
|
||||
<td>{item.oem_partno}</td>
|
||||
<td>{item.act_price}</td>
|
||||
<td>{item.line_remarks}</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
</D>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,3 +9,13 @@ export const INSERT_ALLOCATION = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_ALLOCATION = gql`
|
||||
mutation DELETE_ALLOCATION($id: uuid!) {
|
||||
delete_allocations(where: { id: { _eq: $id } }) {
|
||||
returning {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -18,6 +18,20 @@ export const GET_JOB_LINES_BY_PK = gql`
|
||||
lbr_op
|
||||
lbr_amt
|
||||
op_code_desc
|
||||
status
|
||||
parts_order_lines {
|
||||
id
|
||||
parts_order {
|
||||
id
|
||||
order_number
|
||||
order_date
|
||||
user_email
|
||||
vendor {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
allocations {
|
||||
id
|
||||
hours
|
||||
@@ -30,3 +44,30 @@ export const GET_JOB_LINES_BY_PK = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_JOB_LINE_STATUS = gql`
|
||||
mutation UPDATE_JOB_LINE_STATUS($ids: [uuid!]!, $status: String!) {
|
||||
update_joblines(where: { id: { _in: $ids } }, _set: { status: $status }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const INSERT_NEW_JOB_LINE = gql`
|
||||
mutation INSERT_NEW_JOB_LINE($lineInput: [joblines_insert_input!]!) {
|
||||
insert_joblines(objects: $lineInput) {
|
||||
returning {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_JOB_LINE = gql`
|
||||
mutation UPDATE_JOB_LINE($lineId: uuid!, $line: joblines_set_input!) {
|
||||
update_joblines(where: { id: { _eq: $lineId } }, _set: $line) {
|
||||
returning {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,18 +1,67 @@
|
||||
import { Form, Icon, Tabs } from "antd";
|
||||
import React, { useContext } from "react";
|
||||
import React, { lazy, Suspense, useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaHardHat, FaInfo, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
|
||||
import {
|
||||
FaHardHat,
|
||||
FaInfo,
|
||||
FaRegStickyNote,
|
||||
FaShieldAlt
|
||||
} from "react-icons/fa";
|
||||
import ResetForm from "../../components/form-items-formatted/reset-form-item.component";
|
||||
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
||||
import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
|
||||
import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
|
||||
import JobsDetailFinancials from "../../components/jobs-detail-financial/jobs-detail-financial.component";
|
||||
import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
|
||||
import JobsDetailInsurance from "../../components/jobs-detail-insurance/jobs-detail-insurance.component";
|
||||
import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container";
|
||||
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
||||
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
|
||||
//import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
||||
//import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
|
||||
//import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
|
||||
//import JobsDetailFinancials from "../../components/jobs-detail-financial/jobs-detail-financial.component";
|
||||
//import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
|
||||
//import JobsDetailInsurance from "../../components/jobs-detail-insurance/jobs-detail-insurance.component";
|
||||
//import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container";
|
||||
//import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
||||
//import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
|
||||
//import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
|
||||
//import EnterInvoiceModalContainer from "../../components/invoice-enter-modal/invoice-enter-modal.container";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import JobDetailFormContext from "./jobs-detail.page.context";
|
||||
import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail-pli.container";
|
||||
|
||||
const JobsLinesContainer = lazy(() =>
|
||||
import("../../components/job-detail-lines/job-lines.container")
|
||||
);
|
||||
const JobsDetailClaims = lazy(() =>
|
||||
import("../../components/jobs-detail-claims/jobs-detail-claims.component")
|
||||
);
|
||||
const JobsDetailDatesComponent = lazy(() =>
|
||||
import("../../components/jobs-detail-dates/jobs-detail-dates.component")
|
||||
);
|
||||
const JobsDetailFinancials = lazy(() =>
|
||||
import(
|
||||
"../../components/jobs-detail-financial/jobs-detail-financial.component"
|
||||
)
|
||||
);
|
||||
const JobsDetailHeader = lazy(() =>
|
||||
import("../../components/jobs-detail-header/jobs-detail-header.component")
|
||||
);
|
||||
const JobsDetailInsurance = lazy(() =>
|
||||
import(
|
||||
"../../components/jobs-detail-insurance/jobs-detail-insurance.component"
|
||||
)
|
||||
);
|
||||
const JobsDocumentsContainer = lazy(() =>
|
||||
import("../../components/jobs-documents/jobs-documents.container")
|
||||
);
|
||||
const JobNotesContainer = lazy(() =>
|
||||
import("../../components/jobs-notes/jobs-notes.container")
|
||||
);
|
||||
const ScheduleJobModalContainer = lazy(() =>
|
||||
import("../../components/schedule-job-modal/schedule-job-modal.container")
|
||||
);
|
||||
const JobLineUpsertModalContainer = lazy(() =>
|
||||
import(
|
||||
"../../components/job-lines-upsert-modal/job-lines-upsert-modal.container"
|
||||
)
|
||||
);
|
||||
const EnterInvoiceModalContainer = lazy(() =>
|
||||
import("../../components/invoice-enter-modal/invoice-enter-modal.container")
|
||||
);
|
||||
|
||||
export default function JobsDetailPage({
|
||||
job,
|
||||
@@ -39,13 +88,18 @@ export default function JobsDetailPage({
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Suspense
|
||||
fallback={<LoadingSpinner message={t("general.labels.loadingapp")} />}
|
||||
>
|
||||
<ScheduleJobModalContainer
|
||||
scheduleModalState={scheduleModalState}
|
||||
jobId={job.id}
|
||||
refetch={refetch}
|
||||
/>
|
||||
|
||||
<JobLineUpsertModalContainer />
|
||||
<EnterInvoiceModalContainer />
|
||||
|
||||
<Form onSubmit={handleSubmit} {...formItemLayout} autoComplete={"off"}>
|
||||
<JobsDetailHeader
|
||||
job={job}
|
||||
@@ -116,7 +170,7 @@ export default function JobsDetailPage({
|
||||
}
|
||||
key="partssublet"
|
||||
>
|
||||
Partssublet
|
||||
<JobsDetailPliContainer job={job} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
@@ -167,6 +221,6 @@ export default function JobsDetailPage({
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Form>
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,10 +34,8 @@ export default connect(
|
||||
const [selectedJob, setSelectedJob] = useState(hash ? hash.substr(1) : null);
|
||||
const searchTextState = useState("");
|
||||
const searchText = searchTextState[0];
|
||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||
//TODO Implement pagination for this.
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
console.log(typeof searchText);
|
||||
return (
|
||||
<div>
|
||||
<JobsList
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
import React from "react";
|
||||
import SendEmailButton from "../../components/send-email-button/send-email-button.container";
|
||||
import PartsOrderEmail from "../../emails/parts-order/parts-order.email";
|
||||
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../emails/parts-order/parts-order.query";
|
||||
|
||||
export default function ManageRootPageComponent() {
|
||||
//const client = useApolloClient();
|
||||
return (
|
||||
<div>
|
||||
<SendEmailButton
|
||||
MessageOptions={{
|
||||
from: {
|
||||
name: "Kavia"
|
||||
},
|
||||
to: "patrickwf@gmail.com",
|
||||
replyTo: "patrickwf@gmail.com"
|
||||
}}
|
||||
Template={PartsOrderEmail}
|
||||
QueryConfig={[
|
||||
REPORT_QUERY_PARTS_ORDER_BY_PK,
|
||||
{
|
||||
variables: { id: "ebe0fb6b-6ec4-4ae0-8fdc-49bdf1e37ff3" }
|
||||
}
|
||||
]}>
|
||||
Send an Email in new Window
|
||||
</SendEmailButton>
|
||||
{
|
||||
// <SendEmailButton
|
||||
// MessageOptions={{
|
||||
// from: {
|
||||
// name: "Kavia"
|
||||
// },
|
||||
// to: "patrickwf@gmail.com",
|
||||
// replyTo: "patrickwf@gmail.com"
|
||||
// }}
|
||||
// Template={PartsOrderEmail}
|
||||
// QueryConfig={[
|
||||
// REPORT_QUERY_PARTS_ORDER_BY_PK,
|
||||
// {
|
||||
// variables: { id: "ebe0fb6b-6ec4-4ae0-8fdc-49bdf1e37ff3" }
|
||||
// }
|
||||
// ]}>
|
||||
// Send an Email in new Window
|
||||
// </SendEmailButton>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import FooterComponent from "../../components/footer/footer.component";
|
||||
import HeaderContainer from "../../components/header/header.container";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import "./manage.page.styles.scss";
|
||||
import Test from "../../components/_test/test.component";
|
||||
|
||||
const ManageRootPage = lazy(() =>
|
||||
import("../manage-root/manage-root.page.container")
|
||||
@@ -24,7 +25,7 @@ const JobsAvailablePage = lazy(() =>
|
||||
import("../jobs-available/jobs-available.page.container")
|
||||
);
|
||||
const ChatWindowContainer = lazy(() =>
|
||||
import("../../components/chat-window/chat-window.container")
|
||||
import("../../components/chat-overlay/chat-overlay.container")
|
||||
);
|
||||
const ScheduleContainer = lazy(() =>
|
||||
import("../schedule/schedule.page.container")
|
||||
@@ -44,6 +45,10 @@ const ShopVendorPageContainer = lazy(() =>
|
||||
import("../shop-vendor/shop-vendor.page.container")
|
||||
);
|
||||
|
||||
const EmailOverlayContainer = lazy(() =>
|
||||
import("../../components/email-overlay/email-overlay.container.jsx")
|
||||
);
|
||||
|
||||
const { Header, Content, Footer } = Layout;
|
||||
|
||||
export default function Manage({ match }) {
|
||||
@@ -60,17 +65,20 @@ export default function Manage({ match }) {
|
||||
</Header>
|
||||
<Layout>
|
||||
<Content
|
||||
className='content-container'
|
||||
style={{ padding: "0em 4em 4em" }}>
|
||||
className="content-container"
|
||||
style={{ padding: "0em 4em 4em" }}
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<Suspense
|
||||
fallback={
|
||||
<LoadingSpinner message={t("general.labels.loadingapp")} />
|
||||
}>
|
||||
}
|
||||
>
|
||||
DELETE THIS
|
||||
<Test />
|
||||
<EmailOverlayContainer />
|
||||
<Route exact path={`${match.path}`} component={ManageRootPage} />
|
||||
|
||||
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
|
||||
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/jobs/:jobId`}
|
||||
@@ -111,13 +119,11 @@ export default function Manage({ match }) {
|
||||
path={`${match.path}/schedule`}
|
||||
component={ScheduleContainer}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/available`}
|
||||
component={JobsAvailablePage}
|
||||
/>
|
||||
|
||||
<Route exact path={`${match.path}/shop/`} component={ShopPage} />
|
||||
<Route
|
||||
exact
|
||||
@@ -130,13 +136,8 @@ export default function Manage({ match }) {
|
||||
</Layout>
|
||||
<Footer>
|
||||
<FooterComponent />
|
||||
{
|
||||
// <Affix offsetBottom={20}>
|
||||
// <ChatWindowContainer />
|
||||
// </Affix>
|
||||
}
|
||||
<ChatWindowContainer />
|
||||
</Footer>
|
||||
<ChatWindowContainer />
|
||||
<BackTop />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
24
client/src/redux/email/email.actions.js
Normal file
24
client/src/redux/email/email.actions.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import EmailActionTypes from "./email.types";
|
||||
|
||||
export const toggleEmailOverlayVisible = () => ({
|
||||
type: EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE
|
||||
});
|
||||
|
||||
export const setEmailOptions = options => ({
|
||||
type: EmailActionTypes.SET_EMAIL_OPTIONS,
|
||||
payload: options
|
||||
});
|
||||
|
||||
export const sendEmail = email => ({
|
||||
type: EmailActionTypes.SEND_EMAIL,
|
||||
payload: email
|
||||
});
|
||||
|
||||
export const sendEmailSuccess = options => ({
|
||||
type: EmailActionTypes.SEND_EMAIL_SUCCESS
|
||||
});
|
||||
|
||||
export const sendEmailFailure = error => ({
|
||||
type: EmailActionTypes.SEND_EMAIL_FAILURE,
|
||||
payload: error
|
||||
});
|
||||
35
client/src/redux/email/email.reducer.js
Normal file
35
client/src/redux/email/email.reducer.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import EmailActionTypes from "./email.types";
|
||||
|
||||
const INITIAL_STATE = {
|
||||
emailConfig: {
|
||||
messageOptions: {
|
||||
from: { name: "ShopName", address: "noreply@bodyshop.app" },
|
||||
to: null,
|
||||
replyTo: null
|
||||
},
|
||||
template: null,
|
||||
queryConfig: [null, { variables: null }]
|
||||
},
|
||||
|
||||
visible: false,
|
||||
error: null
|
||||
};
|
||||
|
||||
const emailReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE:
|
||||
return {
|
||||
...state,
|
||||
visible: !state.visible
|
||||
};
|
||||
case EmailActionTypes.SET_EMAIL_OPTIONS:
|
||||
return {
|
||||
...state,
|
||||
emailConfig: { ...action.payload }
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default emailReducer;
|
||||
51
client/src/redux/email/email.sagas.js
Normal file
51
client/src/redux/email/email.sagas.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { all, call, put, takeLatest } from "redux-saga/effects";
|
||||
import { sendEmailFailure, sendEmailSuccess } from "./email.actions";
|
||||
import EmailActionTypes from "./email.types";
|
||||
import axios from "axios";
|
||||
|
||||
export function* onSendEmail() {
|
||||
yield takeLatest(EmailActionTypes.SEND_EMAIL, sendEmail);
|
||||
}
|
||||
export function* sendEmail(payload) {
|
||||
try {
|
||||
console.log("Sending thta email", payload);
|
||||
axios.post("/sendemail", payload).then(response => {
|
||||
console.log(JSON.stringify(response));
|
||||
put(sendEmailSuccess());
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error in sendEmail saga.");
|
||||
yield put(sendEmailFailure(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
export function* onSendEmailSuccess() {
|
||||
yield takeLatest(EmailActionTypes.SEND_EMAIL_SUCCESS, sendEmailSuccessSaga);
|
||||
}
|
||||
export function* sendEmailSuccessSaga() {
|
||||
try {
|
||||
console.log("Send email success.");
|
||||
} catch (error) {
|
||||
console.log("Error in sendEmailSuccess saga.");
|
||||
yield put(sendEmailFailure(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
export function* onSendEmailFailure() {
|
||||
yield takeLatest(EmailActionTypes.SEND_EMAIL_FAILURE, sendEmailFailureSaga);
|
||||
}
|
||||
export function* sendEmailFailureSaga(error) {
|
||||
try {
|
||||
yield console.log(error);
|
||||
} catch (error) {
|
||||
console.log("Error in sendEmailFailure saga.", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function* emailSagas() {
|
||||
yield all([
|
||||
call(onSendEmail),
|
||||
call(onSendEmailFailure),
|
||||
call(onSendEmailSuccess)
|
||||
]);
|
||||
}
|
||||
25
client/src/redux/email/email.selectors.js
Normal file
25
client/src/redux/email/email.selectors.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const selectEmail = state => state.email;
|
||||
const selectEmailConfigMessageOptions = state =>
|
||||
state.email.emailConfig.messageOptions;
|
||||
const selectEmailConfigTemplate = state => state.email.emailConfig.template;
|
||||
const selectEmailConfigQuery = state => state.email.emailConfig.queryConfig;
|
||||
|
||||
export const selectEmailVisible = createSelector(
|
||||
[selectEmail],
|
||||
email => email.visible
|
||||
);
|
||||
|
||||
export const selectEmailConfig = createSelector(
|
||||
[
|
||||
selectEmailConfigMessageOptions,
|
||||
selectEmailConfigTemplate,
|
||||
selectEmailConfigQuery
|
||||
],
|
||||
(messageOptions, template, queryConfig) => ({
|
||||
messageOptions,
|
||||
template,
|
||||
queryConfig
|
||||
})
|
||||
);
|
||||
8
client/src/redux/email/email.types.js
Normal file
8
client/src/redux/email/email.types.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const EmailActionTypes = {
|
||||
TOGGLE_EMAIL_OVERLAY_VISIBLE: "TOGGLE_EMAIL_OVERLAY_VISIBLE",
|
||||
SET_EMAIL_OPTIONS: "SET_EMAIL_OPTIONS",
|
||||
SEND_EMAIL: "SEND_EMAIL",
|
||||
SEND_EMAIL_SUCCESS: "SEND_EMAIL_SUCCESS",
|
||||
SEND_EMAIL_FAILURE: "SEND_EMAIL_FAILURE"
|
||||
};
|
||||
export default EmailActionTypes;
|
||||
@@ -1,7 +1,21 @@
|
||||
import MessagingActionTypes from './messaging.types'
|
||||
import MessagingActionTypes from "./messaging.types";
|
||||
|
||||
export const toggleChatVisible = () => ({
|
||||
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE,
|
||||
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE
|
||||
//payload: user
|
||||
});
|
||||
|
||||
export const toggleConversationVisible = conversationId => ({
|
||||
type: MessagingActionTypes.TOGGLE_CONVERSATION_VISIBLE,
|
||||
payload: conversationId
|
||||
});
|
||||
|
||||
export const openConversation = phone => ({
|
||||
type: MessagingActionTypes.OPEN_CONVERSATION,
|
||||
payload: phone
|
||||
});
|
||||
|
||||
export const closeConversation = phone => ({
|
||||
type: MessagingActionTypes.CLOSE_CONVERSATION,
|
||||
payload: phone
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import MessagingActionTypes from "./messaging.types";
|
||||
|
||||
const INITIAL_STATE = {
|
||||
visible: false
|
||||
visible: false,
|
||||
conversations: [
|
||||
{ phone: "6049992002", open: false },
|
||||
{ phone: "6049992991", open: false }
|
||||
]
|
||||
};
|
||||
|
||||
const messagingReducer = (state = INITIAL_STATE, action) => {
|
||||
@@ -16,6 +20,36 @@ const messagingReducer = (state = INITIAL_STATE, action) => {
|
||||
...state,
|
||||
visible: true
|
||||
};
|
||||
case MessagingActionTypes.OPEN_CONVERSATION:
|
||||
if (state.conversations.find(c => c.phone === action.payload))
|
||||
return {
|
||||
...state,
|
||||
conversations: state.conversations.map(c =>
|
||||
c.phone === action.payload ? { ...c, open: true } : c
|
||||
)
|
||||
};
|
||||
else
|
||||
return {
|
||||
...state,
|
||||
conversations: [
|
||||
...state.conversations,
|
||||
{ phone: action.payload, open: true }
|
||||
]
|
||||
};
|
||||
case MessagingActionTypes.CLOSE_CONVERSATION:
|
||||
return {
|
||||
...state,
|
||||
conversations: state.conversations.filter(
|
||||
c => c.phone !== action.payload
|
||||
)
|
||||
};
|
||||
case MessagingActionTypes.TOGGLE_CONVERSATION_VISIBLE:
|
||||
return {
|
||||
...state,
|
||||
conversations: state.conversations.map(c =>
|
||||
c.phone === action.payload ? { ...c, open: !c.open } : c
|
||||
)
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -6,3 +6,8 @@ export const selectChatVisible = createSelector(
|
||||
[selectMessaging],
|
||||
messaging => messaging.visible
|
||||
);
|
||||
|
||||
export const selectConversations = createSelector(
|
||||
[selectMessaging],
|
||||
messaging => messaging.conversations
|
||||
);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
const MessagingActionTypes = {
|
||||
TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE",
|
||||
SET_CHAT_VISIBLE: "SET_CHAT_VISIBLE"
|
||||
SET_CHAT_VISIBLE: "SET_CHAT_VISIBLE",
|
||||
OPEN_CONVERSATION: "OPEN_CONVERSATION",
|
||||
CLOSE_CONVERSATION: "CLOSE_CONVERSATION",
|
||||
TOGGLE_CONVERSATION_VISIBLE: "TOGGLE_CONVERSATION_VISIBLE",
|
||||
SEND_MESSAGE: "SEND_MESSAGE"
|
||||
};
|
||||
export default MessagingActionTypes;
|
||||
|
||||
12
client/src/redux/modals/modals.actions.js
Normal file
12
client/src/redux/modals/modals.actions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import ModalsActionTypes from "./modals.types";
|
||||
|
||||
export const toggleModalVisible = modalName => ({
|
||||
type: ModalsActionTypes.TOGGLE_MODAL_VISIBLE,
|
||||
payload: modalName
|
||||
});
|
||||
|
||||
//Modal Context: {context (context object), modal(name of modal)}
|
||||
export const setModalContext = modalContext => ({
|
||||
type: ModalsActionTypes.SET_MODAL_CONTEXT,
|
||||
payload: modalContext
|
||||
});
|
||||
40
client/src/redux/modals/modals.reducer.js
Normal file
40
client/src/redux/modals/modals.reducer.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import ModalsActionTypes from "./modals.types";
|
||||
|
||||
const baseModal = {
|
||||
visible: false,
|
||||
context: {},
|
||||
actions: {
|
||||
refetch: null
|
||||
}
|
||||
};
|
||||
|
||||
const INITIAL_STATE = {
|
||||
jobLineEdit: { ...baseModal },
|
||||
invoiceEnter: { ...baseModal }
|
||||
};
|
||||
|
||||
const modalsReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case ModalsActionTypes.TOGGLE_MODAL_VISIBLE:
|
||||
return {
|
||||
...state,
|
||||
[action.payload]: {
|
||||
...state[action.payload],
|
||||
visible: !state[action.payload].visible
|
||||
}
|
||||
};
|
||||
case ModalsActionTypes.SET_MODAL_CONTEXT:
|
||||
return {
|
||||
...state,
|
||||
[action.payload.modal]: {
|
||||
...state[action.payload.modal],
|
||||
...action.payload.context,
|
||||
visible: true
|
||||
}
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default modalsReducer;
|
||||
24
client/src/redux/modals/modals.sagas.js
Normal file
24
client/src/redux/modals/modals.sagas.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { all } from "redux-saga/effects";
|
||||
|
||||
// export function* onSendEmail() {
|
||||
// yield takeLatest(EmailActionTypes.SEND_EMAIL, sendEmail);
|
||||
// }
|
||||
// export function* sendEmail(payload) {
|
||||
// try {
|
||||
// console.log("Sending thta email", payload);
|
||||
// axios.post("/sendemail", payload).then(response => {
|
||||
// console.log(JSON.stringify(response));
|
||||
// put(sendEmailSuccess());
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.log("Error in sendEmail saga.");
|
||||
// yield put(sendEmailFailure(error.message));
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
export function* modalsSagas() {
|
||||
yield all([
|
||||
//call(onSendEmail),
|
||||
]);
|
||||
}
|
||||
14
client/src/redux/modals/modals.selectors.js
Normal file
14
client/src/redux/modals/modals.selectors.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const selectModals = state => state.modals;
|
||||
|
||||
export const selectJobLineEditModal = createSelector(
|
||||
[selectModals],
|
||||
modals => modals.jobLineEdit
|
||||
);
|
||||
|
||||
export const selectInvoiceEnterModal = createSelector(
|
||||
[selectModals],
|
||||
modals => modals.invoiceEnter
|
||||
);
|
||||
|
||||
5
client/src/redux/modals/modals.types.js
Normal file
5
client/src/redux/modals/modals.types.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const ModalActionTypes = {
|
||||
TOGGLE_MODAL_VISIBLE: "TOGGLE_MODAL_VISIBLE",
|
||||
SET_MODAL_CONTEXT: "SET_JOBLINEEDIT_CONTEXT"
|
||||
};
|
||||
export default ModalActionTypes;
|
||||
@@ -4,23 +4,20 @@ import storage from "redux-persist/lib/storage";
|
||||
|
||||
import userReducer from "./user/user.reducer";
|
||||
import messagingReducer from "./messaging/messaging.reducer";
|
||||
// import cartReducer from './cart/cart.reducer';
|
||||
// import directoryReducer from './directory/directory.reducer';
|
||||
// import shopReducer from './shop/shop.reducer';
|
||||
|
||||
import emailReducer from "./email/email.reducer";
|
||||
import modalsReducer from './modals/modals.reducer'
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
storage,
|
||||
//whitelist: ["cart"]
|
||||
blacklist: ["user"]
|
||||
//whitelist: ["user"]
|
||||
blacklist: ["user", "email", "messaging", "modals"]
|
||||
};
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
user: userReducer,
|
||||
messaging: messagingReducer
|
||||
// cart: cartReducer,
|
||||
// directory: directoryReducer,
|
||||
// shop: shopReducer
|
||||
messaging: messagingReducer,
|
||||
email: emailReducer,
|
||||
modals: modalsReducer
|
||||
});
|
||||
|
||||
export default persistReducer(persistConfig, rootReducer);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { all, call } from "redux-saga/effects";
|
||||
|
||||
//List of all Sagas
|
||||
// import { shopSagas } from "./shop/shop.sagas";
|
||||
import { userSagas } from "./user/user.sagas";
|
||||
import { messagingSagas } from "./messaging/messaging.sagas";
|
||||
//import { cartSagas } from "./cart/cart.sagas";
|
||||
|
||||
import { emailSagas } from "./email/email.sagas";
|
||||
import { modalsSagas } from "./modals/modals.sagas";
|
||||
export default function* rootSaga() {
|
||||
//All starts all the Sagas concurrently.
|
||||
yield all([call(userSagas), call(messagingSagas)]);
|
||||
yield all([call(userSagas), call(messagingSagas), call(emailSagas),
|
||||
call(modalsSagas)]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createStore, applyMiddleware } from "redux";
|
||||
import { persistStore } from "redux-persist";
|
||||
import logger from "redux-logger";
|
||||
import { createLogger } from "redux-logger";
|
||||
import createSagaMiddleware from "redux-saga";
|
||||
import rootReducer from "./root.reducer";
|
||||
import rootSaga from "./root.saga";
|
||||
@@ -8,7 +8,7 @@ import rootSaga from "./root.saga";
|
||||
const sagaMiddleWare = createSagaMiddleware();
|
||||
const middlewares = [sagaMiddleWare];
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
middlewares.push(logger);
|
||||
middlewares.push(createLogger({ collapsed: true, diff: true }));
|
||||
}
|
||||
|
||||
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
|
||||
|
||||
@@ -4,8 +4,17 @@
|
||||
"actions": {
|
||||
"assign": "Assign"
|
||||
},
|
||||
"errors": {
|
||||
"deleting": "Error encountered while deleting allocation. {{message}}",
|
||||
"saving": "Error while allocating. {{message}}",
|
||||
"validation": "Please ensure all fields are entered correctly. "
|
||||
},
|
||||
"fields": {
|
||||
"employee": "Allocated To"
|
||||
},
|
||||
"successes": {
|
||||
"deleted": "Allocation deleted successfully.",
|
||||
"save": "Allocated successfully. "
|
||||
}
|
||||
},
|
||||
"appointments": {
|
||||
@@ -67,6 +76,14 @@
|
||||
"insert": "Uploaded document successfully. "
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
"errors": {
|
||||
"notsent": "Email not sent. Error encountered while sending {{message}}"
|
||||
},
|
||||
"successes": {
|
||||
"sent": "Email sent successfully."
|
||||
}
|
||||
},
|
||||
"employees": {
|
||||
"actions": {
|
||||
"new": "New Employee"
|
||||
@@ -128,14 +145,33 @@
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"actions": {
|
||||
"new": "New Line"
|
||||
},
|
||||
"errors": {
|
||||
"creating": "Error encountered while creating job line. {{message}}",
|
||||
"updating": "Error encountered updating job line. {{message}}"
|
||||
},
|
||||
"fields": {
|
||||
"act_price": "Actual Price",
|
||||
"db_price": "Database Price",
|
||||
"line_desc": "Line Description",
|
||||
"line_ind": "S#",
|
||||
"mod_lb_hrs": "Labor Hours",
|
||||
"mod_lbr_ty": "Labor Type",
|
||||
"oem_partno": "OEM Part #",
|
||||
"op_code_desc": "Operation Code Description",
|
||||
"part_type": "Part Type",
|
||||
"status": "Status",
|
||||
"unq_seq": "Seq #"
|
||||
},
|
||||
"labels": {
|
||||
"edit": "Edit Line",
|
||||
"new": "New Line"
|
||||
},
|
||||
"successes": {
|
||||
"created": "Job line created successfully.",
|
||||
"updated": "Job line updated successfully."
|
||||
}
|
||||
},
|
||||
"jobs": {
|
||||
@@ -323,6 +359,12 @@
|
||||
"shops": "My Shops"
|
||||
}
|
||||
},
|
||||
"messaging": {
|
||||
"labels": {
|
||||
"messaging": "Messaging",
|
||||
"typeamessage": "Send a message..."
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"actions": {
|
||||
"actions": "Actions",
|
||||
@@ -385,10 +427,13 @@
|
||||
"creating": "Error encountered when creating parts order. "
|
||||
},
|
||||
"fields": {
|
||||
"deliver_by": "Deliver By"
|
||||
"deliver_by": "Deliver By",
|
||||
"lineremarks": "Line Remarks"
|
||||
},
|
||||
"labels": {
|
||||
"email": "Send by Email",
|
||||
"inthisorder": "Parts in this Order",
|
||||
"orderhistory": "Order History",
|
||||
"print": "Show Printed Form"
|
||||
},
|
||||
"successes": {
|
||||
|
||||
@@ -4,8 +4,17 @@
|
||||
"actions": {
|
||||
"assign": "Asignar"
|
||||
},
|
||||
"errors": {
|
||||
"deleting": "",
|
||||
"saving": "",
|
||||
"validation": ""
|
||||
},
|
||||
"fields": {
|
||||
"employee": "Asignado a"
|
||||
},
|
||||
"successes": {
|
||||
"deleted": "",
|
||||
"save": ""
|
||||
}
|
||||
},
|
||||
"appointments": {
|
||||
@@ -67,6 +76,14 @@
|
||||
"insert": "Documento cargado con éxito."
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
"errors": {
|
||||
"notsent": "Correo electrónico no enviado Se encontró un error al enviar {{message}}"
|
||||
},
|
||||
"successes": {
|
||||
"sent": "Correo electrónico enviado con éxito."
|
||||
}
|
||||
},
|
||||
"employees": {
|
||||
"actions": {
|
||||
"new": "Nuevo empleado"
|
||||
@@ -128,14 +145,33 @@
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"actions": {
|
||||
"new": ""
|
||||
},
|
||||
"errors": {
|
||||
"creating": "",
|
||||
"updating": ""
|
||||
},
|
||||
"fields": {
|
||||
"act_price": "Precio actual",
|
||||
"db_price": "Precio de base de datos",
|
||||
"line_desc": "Descripción de línea",
|
||||
"line_ind": "S#",
|
||||
"mod_lb_hrs": "Horas laborales",
|
||||
"mod_lbr_ty": "Tipo de trabajo",
|
||||
"oem_partno": "OEM parte #",
|
||||
"op_code_desc": "",
|
||||
"part_type": "Tipo de parte",
|
||||
"status": "Estado",
|
||||
"unq_seq": "Seq #"
|
||||
},
|
||||
"labels": {
|
||||
"edit": "Línea de edición",
|
||||
"new": "Nueva línea"
|
||||
},
|
||||
"successes": {
|
||||
"created": "",
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"jobs": {
|
||||
@@ -323,6 +359,12 @@
|
||||
"shops": "Mis tiendas"
|
||||
}
|
||||
},
|
||||
"messaging": {
|
||||
"labels": {
|
||||
"messaging": "Mensajería",
|
||||
"typeamessage": "Enviar un mensaje..."
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"actions": {
|
||||
"actions": "Comportamiento",
|
||||
@@ -385,10 +427,13 @@
|
||||
"creating": "Se encontró un error al crear el pedido de piezas."
|
||||
},
|
||||
"fields": {
|
||||
"deliver_by": "Entregado por"
|
||||
"deliver_by": "Entregado por",
|
||||
"lineremarks": "Comentarios de línea"
|
||||
},
|
||||
"labels": {
|
||||
"email": "Enviar por correo electrónico",
|
||||
"inthisorder": "Partes en este pedido",
|
||||
"orderhistory": "Historial de pedidos",
|
||||
"print": "Mostrar formulario impreso"
|
||||
},
|
||||
"successes": {
|
||||
|
||||
@@ -4,8 +4,17 @@
|
||||
"actions": {
|
||||
"assign": "Attribuer"
|
||||
},
|
||||
"errors": {
|
||||
"deleting": "",
|
||||
"saving": "",
|
||||
"validation": ""
|
||||
},
|
||||
"fields": {
|
||||
"employee": "Alloué à"
|
||||
},
|
||||
"successes": {
|
||||
"deleted": "",
|
||||
"save": ""
|
||||
}
|
||||
},
|
||||
"appointments": {
|
||||
@@ -67,6 +76,14 @@
|
||||
"insert": "Document téléchargé avec succès."
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
"errors": {
|
||||
"notsent": "Courriel non envoyé. Erreur rencontrée lors de l'envoi de {{message}}"
|
||||
},
|
||||
"successes": {
|
||||
"sent": "E-mail envoyé avec succès."
|
||||
}
|
||||
},
|
||||
"employees": {
|
||||
"actions": {
|
||||
"new": "Nouvel employé"
|
||||
@@ -128,14 +145,33 @@
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"actions": {
|
||||
"new": ""
|
||||
},
|
||||
"errors": {
|
||||
"creating": "",
|
||||
"updating": ""
|
||||
},
|
||||
"fields": {
|
||||
"act_price": "Prix actuel",
|
||||
"db_price": "Prix de la base de données",
|
||||
"line_desc": "Description de la ligne",
|
||||
"line_ind": "S#",
|
||||
"mod_lb_hrs": "Heures de travail",
|
||||
"mod_lbr_ty": "Type de travail",
|
||||
"oem_partno": "Pièce OEM #",
|
||||
"op_code_desc": "",
|
||||
"part_type": "Type de pièce",
|
||||
"status": "Statut",
|
||||
"unq_seq": "Seq #"
|
||||
},
|
||||
"labels": {
|
||||
"edit": "Ligne d'édition",
|
||||
"new": "Nouvelle ligne"
|
||||
},
|
||||
"successes": {
|
||||
"created": "",
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"jobs": {
|
||||
@@ -323,6 +359,12 @@
|
||||
"shops": "Mes boutiques"
|
||||
}
|
||||
},
|
||||
"messaging": {
|
||||
"labels": {
|
||||
"messaging": "Messagerie",
|
||||
"typeamessage": "Envoyer un message..."
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"actions": {
|
||||
"actions": "actes",
|
||||
@@ -385,10 +427,13 @@
|
||||
"creating": "Erreur rencontrée lors de la création de la commande de pièces."
|
||||
},
|
||||
"fields": {
|
||||
"deliver_by": "Livrer par"
|
||||
"deliver_by": "Livrer par",
|
||||
"lineremarks": "Remarques sur la ligne"
|
||||
},
|
||||
"labels": {
|
||||
"email": "Envoyé par email",
|
||||
"inthisorder": "Pièces dans cette commande",
|
||||
"orderhistory": "Historique des commandes",
|
||||
"print": "Afficher le formulaire imprimé"
|
||||
},
|
||||
"successes": {
|
||||
|
||||
@@ -2,10 +2,7 @@ export function alphaSort(a, b) {
|
||||
let A;
|
||||
let B;
|
||||
A = a ? a.toLowerCase() : "";
|
||||
|
||||
B = b ? b.toLowerCase() : "";
|
||||
console.log("Objects", A, B, A < B, A > B);
|
||||
|
||||
if (A < B)
|
||||
//sort string ascending
|
||||
return -1;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."joblines" DROP COLUMN "status";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."joblines" ADD COLUMN "status" text NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,75 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- jobid
|
||||
- unq_seq
|
||||
- line_ind
|
||||
- line_desc
|
||||
- part_type
|
||||
- oem_partno
|
||||
- est_seq
|
||||
- db_ref
|
||||
- line_ref
|
||||
- tax_part
|
||||
- db_price
|
||||
- act_price
|
||||
- part_qty
|
||||
- alt_partno
|
||||
- mod_lbr_ty
|
||||
- db_hrs
|
||||
- mod_lb_hrs
|
||||
- lbr_op
|
||||
- lbr_amt
|
||||
- glass_flag
|
||||
- price_inc
|
||||
- alt_part_i
|
||||
- price_j
|
||||
- cert_part
|
||||
- alt_co_id
|
||||
- alt_overrd
|
||||
- alt_partm
|
||||
- prt_dsmk_p
|
||||
- prt_dsmk_m
|
||||
- lbr_inc
|
||||
- lbr_hrs_j
|
||||
- lbr_typ_j
|
||||
- lbr_op_j
|
||||
- paint_stg
|
||||
- paint_tone
|
||||
- lbr_tax
|
||||
- misc_amt
|
||||
- misc_sublt
|
||||
- misc_tax
|
||||
- bett_type
|
||||
- bett_pctg
|
||||
- bett_amt
|
||||
- bett_tax
|
||||
- op_code_desc
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,76 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- jobid
|
||||
- unq_seq
|
||||
- line_ind
|
||||
- line_desc
|
||||
- part_type
|
||||
- oem_partno
|
||||
- est_seq
|
||||
- db_ref
|
||||
- line_ref
|
||||
- tax_part
|
||||
- db_price
|
||||
- act_price
|
||||
- part_qty
|
||||
- alt_partno
|
||||
- mod_lbr_ty
|
||||
- db_hrs
|
||||
- mod_lb_hrs
|
||||
- lbr_op
|
||||
- lbr_amt
|
||||
- glass_flag
|
||||
- price_inc
|
||||
- alt_part_i
|
||||
- price_j
|
||||
- cert_part
|
||||
- alt_co_id
|
||||
- alt_overrd
|
||||
- alt_partm
|
||||
- prt_dsmk_p
|
||||
- prt_dsmk_m
|
||||
- lbr_inc
|
||||
- lbr_hrs_j
|
||||
- lbr_typ_j
|
||||
- lbr_op_j
|
||||
- paint_stg
|
||||
- paint_tone
|
||||
- lbr_tax
|
||||
- misc_amt
|
||||
- misc_sublt
|
||||
- misc_tax
|
||||
- bett_type
|
||||
- bett_pctg
|
||||
- bett_amt
|
||||
- bett_tax
|
||||
- op_code_desc
|
||||
- status
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,73 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- alt_overrd
|
||||
- alt_part_i
|
||||
- bett_tax
|
||||
- cert_part
|
||||
- glass_flag
|
||||
- lbr_hrs_j
|
||||
- lbr_inc
|
||||
- lbr_op_j
|
||||
- lbr_tax
|
||||
- lbr_typ_j
|
||||
- misc_sublt
|
||||
- misc_tax
|
||||
- price_inc
|
||||
- price_j
|
||||
- tax_part
|
||||
- est_seq
|
||||
- paint_stg
|
||||
- paint_tone
|
||||
- part_qty
|
||||
- unq_seq
|
||||
- act_price
|
||||
- bett_amt
|
||||
- bett_pctg
|
||||
- db_hrs
|
||||
- db_price
|
||||
- lbr_amt
|
||||
- line_ref
|
||||
- misc_amt
|
||||
- mod_lb_hrs
|
||||
- prt_dsmk_m
|
||||
- prt_dsmk_p
|
||||
- alt_co_id
|
||||
- alt_partm
|
||||
- alt_partno
|
||||
- bett_type
|
||||
- db_ref
|
||||
- lbr_op
|
||||
- line_desc
|
||||
- line_ind
|
||||
- mod_lbr_ty
|
||||
- oem_partno
|
||||
- op_code_desc
|
||||
- part_type
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- jobid
|
||||
computed_fields: []
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,74 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- alt_overrd
|
||||
- alt_part_i
|
||||
- bett_tax
|
||||
- cert_part
|
||||
- glass_flag
|
||||
- lbr_hrs_j
|
||||
- lbr_inc
|
||||
- lbr_op_j
|
||||
- lbr_tax
|
||||
- lbr_typ_j
|
||||
- misc_sublt
|
||||
- misc_tax
|
||||
- price_inc
|
||||
- price_j
|
||||
- tax_part
|
||||
- est_seq
|
||||
- paint_stg
|
||||
- paint_tone
|
||||
- part_qty
|
||||
- unq_seq
|
||||
- act_price
|
||||
- bett_amt
|
||||
- bett_pctg
|
||||
- db_hrs
|
||||
- db_price
|
||||
- lbr_amt
|
||||
- line_ref
|
||||
- misc_amt
|
||||
- mod_lb_hrs
|
||||
- prt_dsmk_m
|
||||
- prt_dsmk_p
|
||||
- alt_co_id
|
||||
- alt_partm
|
||||
- alt_partno
|
||||
- bett_type
|
||||
- db_ref
|
||||
- lbr_op
|
||||
- line_desc
|
||||
- line_ind
|
||||
- mod_lbr_ty
|
||||
- oem_partno
|
||||
- op_code_desc
|
||||
- part_type
|
||||
- status
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- jobid
|
||||
computed_fields: []
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,75 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- alt_overrd
|
||||
- alt_part_i
|
||||
- bett_tax
|
||||
- cert_part
|
||||
- glass_flag
|
||||
- lbr_hrs_j
|
||||
- lbr_inc
|
||||
- lbr_op_j
|
||||
- lbr_tax
|
||||
- lbr_typ_j
|
||||
- misc_sublt
|
||||
- misc_tax
|
||||
- price_inc
|
||||
- price_j
|
||||
- tax_part
|
||||
- est_seq
|
||||
- paint_stg
|
||||
- paint_tone
|
||||
- part_qty
|
||||
- unq_seq
|
||||
- act_price
|
||||
- bett_amt
|
||||
- bett_pctg
|
||||
- db_hrs
|
||||
- db_price
|
||||
- lbr_amt
|
||||
- line_ref
|
||||
- misc_amt
|
||||
- mod_lb_hrs
|
||||
- prt_dsmk_m
|
||||
- prt_dsmk_p
|
||||
- alt_co_id
|
||||
- alt_partm
|
||||
- alt_partno
|
||||
- bett_type
|
||||
- db_ref
|
||||
- lbr_op
|
||||
- line_desc
|
||||
- line_ind
|
||||
- mod_lbr_ty
|
||||
- oem_partno
|
||||
- op_code_desc
|
||||
- part_type
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- jobid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,76 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- alt_overrd
|
||||
- alt_part_i
|
||||
- bett_tax
|
||||
- cert_part
|
||||
- glass_flag
|
||||
- lbr_hrs_j
|
||||
- lbr_inc
|
||||
- lbr_op_j
|
||||
- lbr_tax
|
||||
- lbr_typ_j
|
||||
- misc_sublt
|
||||
- misc_tax
|
||||
- price_inc
|
||||
- price_j
|
||||
- tax_part
|
||||
- est_seq
|
||||
- paint_stg
|
||||
- paint_tone
|
||||
- part_qty
|
||||
- unq_seq
|
||||
- act_price
|
||||
- bett_amt
|
||||
- bett_pctg
|
||||
- db_hrs
|
||||
- db_price
|
||||
- lbr_amt
|
||||
- line_ref
|
||||
- misc_amt
|
||||
- mod_lb_hrs
|
||||
- prt_dsmk_m
|
||||
- prt_dsmk_p
|
||||
- alt_co_id
|
||||
- alt_partm
|
||||
- alt_partno
|
||||
- bett_type
|
||||
- db_ref
|
||||
- lbr_op
|
||||
- line_desc
|
||||
- line_ind
|
||||
- mod_lbr_ty
|
||||
- oem_partno
|
||||
- op_code_desc
|
||||
- part_type
|
||||
- status
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- jobid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: joblines
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: DROP TABLE "public"."invoices";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,28 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
type: run_sql
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: "CREATE TABLE \"public\".\"invoices\"(\"id\" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
\"created_at\" timestamptz NOT NULL DEFAULT now(), \"updated_at\" timestamptz
|
||||
NOT NULL DEFAULT now(), \"vendorid\" uuid NOT NULL, \"jobid\" uuid NOT NULL,
|
||||
\"date\" date NOT NULL DEFAULT now(), \"due_date\" date, \"exported\" boolean
|
||||
NOT NULL DEFAULT false, \"exported_at\" timestamptz, \"is_credit_memo\" boolean
|
||||
NOT NULL DEFAULT false, \"total\" numeric NOT NULL DEFAULT 0, \"invoice_number\"
|
||||
text NOT NULL, PRIMARY KEY (\"id\") , FOREIGN KEY (\"jobid\") REFERENCES \"public\".\"jobs\"(\"id\")
|
||||
ON UPDATE restrict ON DELETE cascade, FOREIGN KEY (\"vendorid\") REFERENCES
|
||||
\"public\".\"vendors\"(\"id\") ON UPDATE restrict ON DELETE restrict, UNIQUE
|
||||
(\"jobid\"));\nCREATE OR REPLACE FUNCTION \"public\".\"set_current_timestamp_updated_at\"()\nRETURNS
|
||||
TRIGGER AS $$\nDECLARE\n _new record;\nBEGIN\n _new := NEW;\n _new.\"updated_at\"
|
||||
= NOW();\n RETURN _new;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER \"set_public_invoices_updated_at\"\nBEFORE
|
||||
UPDATE ON \"public\".\"invoices\"\nFOR EACH ROW\nEXECUTE PROCEDURE \"public\".\"set_current_timestamp_updated_at\"();\nCOMMENT
|
||||
ON TRIGGER \"set_public_invoices_updated_at\" ON \"public\".\"invoices\" \nIS
|
||||
'trigger to set value of column \"updated_at\" to current timestamp on row update';"
|
||||
type: run_sql
|
||||
- args:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: add_existing_table_or_view
|
||||
@@ -0,0 +1,24 @@
|
||||
- args:
|
||||
relationship: job
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: drop_relationship
|
||||
- args:
|
||||
relationship: vendor
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: drop_relationship
|
||||
- args:
|
||||
relationship: invoice
|
||||
table:
|
||||
name: jobs
|
||||
schema: public
|
||||
type: drop_relationship
|
||||
- args:
|
||||
relationship: invoices
|
||||
table:
|
||||
name: vendors
|
||||
schema: public
|
||||
type: drop_relationship
|
||||
@@ -0,0 +1,41 @@
|
||||
- args:
|
||||
name: job
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
using:
|
||||
foreign_key_constraint_on: jobid
|
||||
type: create_object_relationship
|
||||
- args:
|
||||
name: vendor
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
using:
|
||||
foreign_key_constraint_on: vendorid
|
||||
type: create_object_relationship
|
||||
- args:
|
||||
name: invoice
|
||||
table:
|
||||
name: jobs
|
||||
schema: public
|
||||
using:
|
||||
manual_configuration:
|
||||
column_mapping:
|
||||
id: jobid
|
||||
remote_table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: create_object_relationship
|
||||
- args:
|
||||
name: invoices
|
||||
table:
|
||||
name: vendors
|
||||
schema: public
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: vendorid
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: create_array_relationship
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
@@ -0,0 +1,35 @@
|
||||
- args:
|
||||
permission:
|
||||
allow_upsert: true
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- vendorid
|
||||
- jobid
|
||||
- date
|
||||
- due_date
|
||||
- exported
|
||||
- exported_at
|
||||
- is_credit_memo
|
||||
- total
|
||||
- invoice_number
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
@@ -0,0 +1,33 @@
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- exported
|
||||
- is_credit_memo
|
||||
- date
|
||||
- due_date
|
||||
- total
|
||||
- invoice_number
|
||||
- created_at
|
||||
- exported_at
|
||||
- updated_at
|
||||
- id
|
||||
- jobid
|
||||
- vendorid
|
||||
computed_fields: []
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
limit: null
|
||||
role: user
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
@@ -0,0 +1,34 @@
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- exported
|
||||
- is_credit_memo
|
||||
- date
|
||||
- due_date
|
||||
- total
|
||||
- invoice_number
|
||||
- created_at
|
||||
- exported_at
|
||||
- updated_at
|
||||
- id
|
||||
- jobid
|
||||
- vendorid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: DROP TABLE "public"."invoicelines";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,26 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
type: run_sql
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: "CREATE TABLE \"public\".\"invoicelines\"(\"id\" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
\"created_at\" timestamptz NOT NULL DEFAULT now(), \"updated_at\" timestamptz
|
||||
NOT NULL DEFAULT now(), \"invoiceid\" uuid NOT NULL, \"line_desc\" text, \"actual_price\"
|
||||
numeric NOT NULL DEFAULT 0, \"actual_cost\" numeric NOT NULL DEFAULT 0, \"cost_center\"
|
||||
text NOT NULL, \"estlindid\" uuid, PRIMARY KEY (\"id\") , FOREIGN KEY (\"invoiceid\")
|
||||
REFERENCES \"public\".\"invoices\"(\"id\") ON UPDATE restrict ON DELETE cascade);\nCREATE
|
||||
OR REPLACE FUNCTION \"public\".\"set_current_timestamp_updated_at\"()\nRETURNS
|
||||
TRIGGER AS $$\nDECLARE\n _new record;\nBEGIN\n _new := NEW;\n _new.\"updated_at\"
|
||||
= NOW();\n RETURN _new;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER \"set_public_invoicelines_updated_at\"\nBEFORE
|
||||
UPDATE ON \"public\".\"invoicelines\"\nFOR EACH ROW\nEXECUTE PROCEDURE \"public\".\"set_current_timestamp_updated_at\"();\nCOMMENT
|
||||
ON TRIGGER \"set_public_invoicelines_updated_at\" ON \"public\".\"invoicelines\"
|
||||
\nIS 'trigger to set value of column \"updated_at\" to current timestamp on
|
||||
row update';"
|
||||
type: run_sql
|
||||
- args:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: add_existing_table_or_view
|
||||
@@ -0,0 +1,12 @@
|
||||
- args:
|
||||
relationship: invoice
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: drop_relationship
|
||||
- args:
|
||||
relationship: invoicelines
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
type: drop_relationship
|
||||
@@ -0,0 +1,20 @@
|
||||
- args:
|
||||
name: invoice
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
using:
|
||||
foreign_key_constraint_on: invoiceid
|
||||
type: create_object_relationship
|
||||
- args:
|
||||
name: invoicelines
|
||||
table:
|
||||
name: invoices
|
||||
schema: public
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: invoiceid
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: create_array_relationship
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
@@ -0,0 +1,33 @@
|
||||
- args:
|
||||
permission:
|
||||
allow_upsert: true
|
||||
check:
|
||||
invoice:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- invoiceid
|
||||
- line_desc
|
||||
- actual_price
|
||||
- actual_cost
|
||||
- cost_center
|
||||
- estlindid
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
@@ -0,0 +1,31 @@
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- actual_cost
|
||||
- actual_price
|
||||
- cost_center
|
||||
- line_desc
|
||||
- created_at
|
||||
- updated_at
|
||||
- estlindid
|
||||
- id
|
||||
- invoiceid
|
||||
computed_fields: []
|
||||
filter:
|
||||
invoice:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
limit: null
|
||||
role: user
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
@@ -0,0 +1,32 @@
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- actual_cost
|
||||
- actual_price
|
||||
- cost_center
|
||||
- line_desc
|
||||
- created_at
|
||||
- updated_at
|
||||
- estlindid
|
||||
- id
|
||||
- invoiceid
|
||||
filter:
|
||||
invoice:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
localPresets:
|
||||
- key: ""
|
||||
value: ""
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: drop_delete_permission
|
||||
@@ -0,0 +1,18 @@
|
||||
- args:
|
||||
permission:
|
||||
filter:
|
||||
invoice:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: invoicelines
|
||||
schema: public
|
||||
type: create_delete_permission
|
||||
@@ -31,6 +31,6 @@
|
||||
"concurrently": "^4.0.1",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"hasura-cli": "^1.0.0-beta.10"
|
||||
"hasura-cli": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1950,10 +1950,10 @@ has-flag@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
hasura-cli@^1.0.0-beta.10:
|
||||
version "1.0.0-rc.1"
|
||||
resolved "https://registry.yarnpkg.com/hasura-cli/-/hasura-cli-1.0.0-rc.1.tgz#481453f88e7624f468f329c75a88fbbde3407f00"
|
||||
integrity sha512-w6DGAhJZ6l7U89SD6QIxYetP3/dDxJc4jEVzjMAYsueeYWQKDeGgtZBFBW1sdgAlUtRmtIa5wbiFLXuagB6JqA==
|
||||
hasura-cli@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hasura-cli/-/hasura-cli-1.1.0.tgz#a095e94c654d30354d8979602b8c0047c7b8a9e1"
|
||||
integrity sha512-D1qXoYydx9Mgq7VQdCmOOvTlYhd1RcjQtn4s7pN1wb5w1ORIcDFLm1rS3w97bsx7wPRotIl0reyhc3+FDq+FFg==
|
||||
dependencies:
|
||||
axios "^0.19.0"
|
||||
chalk "^2.4.2"
|
||||
|
||||
Reference in New Issue
Block a user