@@ -47,6 +47,74 @@
|
|||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</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>
|
<folder_node>
|
||||||
<name>fields</name>
|
<name>fields</name>
|
||||||
<children>
|
<children>
|
||||||
@@ -73,6 +141,53 @@
|
|||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</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>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
@@ -722,6 +837,63 @@
|
|||||||
</folder_node>
|
</folder_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</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>
|
<folder_node>
|
||||||
<name>employees</name>
|
<name>employees</name>
|
||||||
<children>
|
<children>
|
||||||
@@ -1578,6 +1750,79 @@
|
|||||||
<folder_node>
|
<folder_node>
|
||||||
<name>joblines</name>
|
<name>joblines</name>
|
||||||
<children>
|
<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>
|
<folder_node>
|
||||||
<name>fields</name>
|
<name>fields</name>
|
||||||
<children>
|
<children>
|
||||||
@@ -1644,6 +1889,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
<concept_node>
|
||||||
<name>mod_lb_hrs</name>
|
<name>mod_lb_hrs</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -1665,6 +1931,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
<concept_node>
|
||||||
<name>oem_partno</name>
|
<name>oem_partno</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -1686,6 +1973,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
<concept_node>
|
||||||
<name>part_type</name>
|
<name>part_type</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -1707,6 +2015,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
<concept_node>
|
||||||
<name>unq_seq</name>
|
<name>unq_seq</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -1730,6 +2059,100 @@
|
|||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</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>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
@@ -5173,6 +5596,58 @@
|
|||||||
</folder_node>
|
</folder_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</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>
|
<folder_node>
|
||||||
<name>notes</name>
|
<name>notes</name>
|
||||||
<children>
|
<children>
|
||||||
@@ -5979,6 +6454,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
@@ -6005,6 +6501,48 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
<concept_node>
|
||||||
<name>print</name>
|
<name>print</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<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 [insertAllocation] = useMutation(INSERT_ALLOCATION);
|
||||||
|
|
||||||
const handleAssignment = () => {
|
const handleAssignment = () => {
|
||||||
insertAllocation({ variables: { alloc: { ...assignment } } }).then(r => {
|
insertAllocation({ variables: { alloc: { ...assignment } } })
|
||||||
notification["success"]({
|
.then(r => {
|
||||||
message: t("employees.successes.save")
|
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 (
|
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 {
|
.messages ul li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
clear: both;
|
clear: both;
|
||||||
float: left;
|
//float: left;
|
||||||
margin: 5px 15px 5px 15px;
|
margin: 5px 15px 5px 15px;
|
||||||
width: calc(100% - 25px);
|
width: calc(100% - 25px);
|
||||||
font-size: 0.9em;
|
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";
|
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
|
||||||
|
|
||||||
export default function SendEmailButtonComponent({
|
export default function SendEmailButtonComponent({
|
||||||
emailConfig,
|
messageOptions,
|
||||||
handleConfigChange,
|
handleConfigChange,
|
||||||
handleHtmlChange
|
handleHtmlChange
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
THis is where the text editing will happen To
|
|
||||||
<Input
|
<Input
|
||||||
defaultValue={emailConfig.to}
|
defaultValue={messageOptions.to}
|
||||||
onChange={handleConfigChange}
|
onChange={handleConfigChange}
|
||||||
name='to'
|
name='to'
|
||||||
/>
|
/>
|
||||||
CC
|
CC
|
||||||
<Input
|
<Input
|
||||||
defaultValue={emailConfig.cc}
|
defaultValue={messageOptions.cc}
|
||||||
onChange={handleConfigChange}
|
onChange={handleConfigChange}
|
||||||
name='cc'
|
name='cc'
|
||||||
/>
|
/>
|
||||||
Subject
|
Subject
|
||||||
<Input
|
<Input
|
||||||
defaultValue={emailConfig.subject}
|
defaultValue={messageOptions.subject}
|
||||||
onChange={handleConfigChange}
|
onChange={handleConfigChange}
|
||||||
name='subject'
|
name='subject'
|
||||||
/>
|
/>
|
||||||
<CKEditor
|
<CKEditor
|
||||||
editor={ClassicEditor}
|
editor={ClassicEditor}
|
||||||
data={emailConfig.html}
|
data={messageOptions.html}
|
||||||
onInit={editor => {
|
|
||||||
// You can store the "editor" and use when it is needed.
|
|
||||||
console.log("Editor is ready to use!", editor);
|
|
||||||
}}
|
|
||||||
onChange={(event, editor) => {
|
onChange={(event, editor) => {
|
||||||
const data = editor.getData();
|
handleHtmlChange(editor.getData());
|
||||||
console.log({ event, editor, data });
|
|
||||||
handleHtmlChange(data);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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={
|
message={
|
||||||
<div>
|
<div>
|
||||||
{t("general.messages.unsavedchanges")}
|
{t("general.messages.unsavedchanges")}
|
||||||
<Button onClick={() => resetFields()}>
|
<Button style={{ marginLeft: "20px" }} onClick={() => resetFields()}>
|
||||||
{t("general.actions.reset")}
|
{t("general.actions.reset")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,107 +15,168 @@ export default ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
//TODO Add
|
//TODO Add
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row type='flex' justify='space-around' align='middle'>
|
<Row type="flex" justify="space-around" align="middle">
|
||||||
{logo ? (
|
{logo ? (
|
||||||
<Col span={4}>
|
<Col span={3}>
|
||||||
<img alt='Shop Logo' src={logo} style={{ height: "40px" }} />
|
<img alt="Shop Logo" src={logo} style={{ height: "40px" }} />
|
||||||
</Col>
|
</Col>
|
||||||
) : null}
|
) : null}
|
||||||
<Col span={14}>
|
<Col span={14}>
|
||||||
<Menu
|
{landingHeader ? (
|
||||||
theme='dark'
|
<Menu
|
||||||
className='header'
|
theme="dark"
|
||||||
selectedKeys={selectedNavItem}
|
className="header"
|
||||||
mode='horizontal'
|
selectedKeys={selectedNavItem}
|
||||||
onClick={handleMenuClick}>
|
mode="horizontal"
|
||||||
<Menu.Item key='home'>
|
onClick={handleMenuClick}
|
||||||
<Link to='/manage'>
|
>
|
||||||
<Icon type='home' />
|
<ManageSignInButton />
|
||||||
{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 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
|
<Menu.SubMenu
|
||||||
title={
|
title={
|
||||||
<span>
|
<div>
|
||||||
<Icon type='global' />
|
<Avatar
|
||||||
<span>{t("menus.currentuser.languageselector")}</span>
|
size="medium"
|
||||||
</span>
|
alt="Avatar"
|
||||||
}>
|
src={
|
||||||
<Menu.Item actiontype='lang-select' key='en_us'>
|
currentUser.photoURL ? currentUser.photoURL : UserImage
|
||||||
{t("general.languages.english")}
|
}
|
||||||
|
style={{ margin: "10px" }}
|
||||||
|
/>
|
||||||
|
{currentUser.displayName || t("general.labels.unknown")}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Menu.Item onClick={signOutStart()}>
|
||||||
|
{t("user.actions.signout")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item actiontype='lang-select' key='fr'>
|
<Menu.Item>
|
||||||
{t("general.languages.french")}
|
<Link to="/manage/profile">
|
||||||
|
{t("menus.currentuser.profile")}
|
||||||
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item actiontype='lang-select' key='es'>
|
<Menu.SubMenu
|
||||||
{t("general.languages.spanish")}
|
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.Item>
|
||||||
</Menu.SubMenu>
|
</Menu.SubMenu>
|
||||||
</Menu.SubMenu>
|
<Menu.SubMenu title={t("menus.header.customers")}>
|
||||||
</Menu>
|
<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>
|
||||||
<Col span={4}>{!landingHeader ? null : <ManageSignInButton />}</Col>
|
|
||||||
</Row>
|
</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 { Button, Input, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
//import EditableCell from "./job-lines-cell.component";
|
|
||||||
import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
|
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";
|
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||||
|
|
||||||
export default function JobLinesComponent({
|
export default function JobLinesComponent({
|
||||||
@@ -15,7 +17,8 @@ export default function JobLinesComponent({
|
|||||||
selectedLines,
|
selectedLines,
|
||||||
setSelectedLines,
|
setSelectedLines,
|
||||||
partsOrderModalVisible,
|
partsOrderModalVisible,
|
||||||
jobId
|
jobId,
|
||||||
|
setJobLineEditContext
|
||||||
}) {
|
}) {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {}
|
sortedInfo: {}
|
||||||
@@ -44,7 +47,8 @@ export default function JobLinesComponent({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
editable: true
|
editable: true,
|
||||||
|
width: "20%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.oem_partno"),
|
title: t("joblines.fields.oem_partno"),
|
||||||
@@ -59,7 +63,7 @@ export default function JobLinesComponent({
|
|||||||
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
editable: true,
|
editable: true,
|
||||||
width: "20%",
|
width: "10%",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span>
|
<span>
|
||||||
{record.oem_partno ? record.oem_partno : record.op_code_desc}
|
{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,
|
state.sortedInfo.columnKey === "part_type" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
editable: 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"),
|
title: t("joblines.fields.db_price"),
|
||||||
@@ -85,7 +97,7 @@ export default function JobLinesComponent({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "db_price" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "db_price" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
width: "10%",
|
width: "8%",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
|
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
|
||||||
)
|
)
|
||||||
@@ -98,7 +110,7 @@ export default function JobLinesComponent({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
width: "10%",
|
width: "8%",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||||
)
|
)
|
||||||
@@ -111,21 +123,39 @@ export default function JobLinesComponent({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order
|
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"),
|
title: t("allocations.fields.employee"),
|
||||||
dataIndex: "employee",
|
dataIndex: "employee",
|
||||||
key: "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:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span>
|
<span>
|
||||||
{record.allocations && record.allocations.length > 0
|
{record.allocations && record.allocations.length > 0
|
||||||
? record.allocations.map(item => (
|
? record.allocations.map(item => (
|
||||||
<div
|
<AllocationsEmployeeLabelContainer
|
||||||
key={
|
key={item.id}
|
||||||
item.id
|
refetch={refetch}
|
||||||
}>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div>
|
allocation={item}
|
||||||
|
/>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
<AllocationsAssignmentContainer
|
<AllocationsAssignmentContainer
|
||||||
@@ -143,7 +173,16 @@ export default function JobLinesComponent({
|
|||||||
key: "actions",
|
key: "actions",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span>
|
<span>
|
||||||
<Button>{t("general.actions.edit")}</Button>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setJobLineEditContext({
|
||||||
|
actions: { refetch: refetch },
|
||||||
|
context: record
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.actions.edit")}
|
||||||
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -169,6 +208,7 @@ export default function JobLinesComponent({
|
|||||||
<PartsOrderModalContainer
|
<PartsOrderModalContainer
|
||||||
partsOrderModalVisible={partsOrderModalVisible}
|
partsOrderModalVisible={partsOrderModalVisible}
|
||||||
linesToOrder={selectedLines}
|
linesToOrder={selectedLines}
|
||||||
|
refetch={refetch}
|
||||||
jobId={jobId}
|
jobId={jobId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -185,23 +225,55 @@ export default function JobLinesComponent({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
disabled={selectedLines.length > 0 ? false : true}
|
disabled={selectedLines.length > 0 ? false : true}
|
||||||
onClick={() => setPartsModalVisible(true)}>
|
onClick={() => setPartsModalVisible(true)}
|
||||||
|
>
|
||||||
{t("parts.actions.order")}
|
{t("parts.actions.order")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<AllocationsBulkAssignmentContainer
|
||||||
|
jobLines={selectedLines}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setJobLineEditContext({
|
||||||
|
actions: { refetch: refetch },
|
||||||
|
context: { jobid: jobId }
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("joblines.actions.new")}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
{...formItemLayout}
|
{...formItemLayout}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size='small'
|
size="small"
|
||||||
pagination={{ position: "bottom", defaultPageSize: 50 }}
|
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={{
|
rowSelection={{
|
||||||
// selectedRowKeys: selectedLines,
|
// selectedRowKeys: selectedLines,
|
||||||
|
onSelectAll: (selected, selectedRows, changeRows) => {
|
||||||
|
setSelectedLines(selectedRows);
|
||||||
|
},
|
||||||
onSelect: (record, selected, selectedRows, nativeEvent) =>
|
onSelect: (record, selected, selectedRows, nativeEvent) =>
|
||||||
setSelectedLines(selectedRows)
|
setSelectedLines(selectedRows)
|
||||||
}}
|
}}
|
||||||
columns={columns.map(item => ({ ...item }))}
|
columns={columns.map(item => ({ ...item }))}
|
||||||
rowKey='id'
|
rowKey="id"
|
||||||
dataSource={jobLines}
|
dataSource={jobLines}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,9 +5,16 @@ import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries";
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import JobLinesComponent from "./job-lines.component";
|
import JobLinesComponent from "./job-lines.component";
|
||||||
|
|
||||||
//export default Form.create({ name: "JobsDetailJobLines" })(
|
import { connect } from "react-redux";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
export default function JobLinesContainer({ jobId }) {
|
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, {
|
const { loading, error, data, refetch } = useQuery(GET_JOB_LINES_BY_PK, {
|
||||||
variables: { id: jobId },
|
variables: { id: jobId },
|
||||||
fetchPolicy: "network-only"
|
fetchPolicy: "network-only"
|
||||||
@@ -16,7 +23,8 @@ export default function JobLinesContainer({ jobId }) {
|
|||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [selectedLines, setSelectedLines] = useState([]);
|
const [selectedLines, setSelectedLines] = useState([]);
|
||||||
const partsOrderModalVisible = useState(false);
|
const partsOrderModalVisible = useState(false);
|
||||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<JobLinesComponent
|
<JobLinesComponent
|
||||||
@@ -58,7 +66,7 @@ export default function JobLinesContainer({ jobId }) {
|
|||||||
setSelectedLines={setSelectedLines}
|
setSelectedLines={setSelectedLines}
|
||||||
partsOrderModalVisible={partsOrderModalVisible}
|
partsOrderModalVisible={partsOrderModalVisible}
|
||||||
jobId={jobId}
|
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
|
//create upsert job
|
||||||
let supp = estData.data.available_jobs_by_pk.est_data;
|
let supp = estData.data.available_jobs_by_pk.est_data;
|
||||||
delete supp.joblines;
|
delete supp.joblines;
|
||||||
|
//TODO How to update the estimate lines.
|
||||||
delete supp.owner;
|
delete supp.owner;
|
||||||
delete supp.vehicle;
|
delete supp.vehicle;
|
||||||
|
|
||||||
if (!importOptions.overrideHeaders) {
|
if (!importOptions.overrideHeaders) {
|
||||||
delete supp["ins_ea"];
|
delete supp["ins_ea"];
|
||||||
//Strip out the header options
|
//TODO Remove all required fields.
|
||||||
}
|
}
|
||||||
|
|
||||||
updateJob({
|
updateJob({
|
||||||
@@ -101,12 +102,11 @@ export default withRouter(function JobsAvailableSupplementContainer({
|
|||||||
setSelectedJob(null);
|
setSelectedJob(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) return <AlertComponent type="error" message={error.message} />;
|
if (error) return <AlertComponent type='error' message={error.message} />;
|
||||||
return (
|
return (
|
||||||
<LoadingSpinner
|
<LoadingSpinner
|
||||||
loading={insertLoading}
|
loading={insertLoading}
|
||||||
message={t("jobs.labels.creating_new_job")}
|
message={t("jobs.labels.creating_new_job")}>
|
||||||
>
|
|
||||||
<JobsAvailableSupplementComponent
|
<JobsAvailableSupplementComponent
|
||||||
loading={loading}
|
loading={loading}
|
||||||
data={data}
|
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 React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
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 PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import { withRouter } from "react-router-dom";
|
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
|
||||||
|
|
||||||
export default withRouter(function JobsList({
|
export default withRouter(function JobsList({
|
||||||
searchTextState,
|
searchTextState,
|
||||||
@@ -78,13 +78,7 @@ export default withRouter(function JobsList({
|
|||||||
return record.ownr_ph1 ? (
|
return record.ownr_ph1 ? (
|
||||||
<span>
|
<span>
|
||||||
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
||||||
<Icon
|
<StartChatButton phone={record.ownr_ph1} />
|
||||||
style={{ margin: 4 }}
|
|
||||||
type='message'
|
|
||||||
onClick={() => {
|
|
||||||
alert("SMSing will happen here.");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
t("general.labels.unknown")
|
t("general.labels.unknown")
|
||||||
@@ -214,10 +208,10 @@ export default withRouter(function JobsList({
|
|||||||
return (
|
return (
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<Icon type='sync' />
|
<Icon type="sync" />
|
||||||
</Button>
|
</Button>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder='Search...'
|
placeholder="Search..."
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setSearchText(e.target.value);
|
setSearchText(e.target.value);
|
||||||
}}
|
}}
|
||||||
@@ -226,10 +220,10 @@ export default withRouter(function JobsList({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
size='small'
|
size="small"
|
||||||
pagination={{ position: "top" }}
|
pagination={{ position: "top" }}
|
||||||
columns={columns.map(item => ({ ...item }))}
|
columns={columns.map(item => ({ ...item }))}
|
||||||
rowKey='id'
|
rowKey="id"
|
||||||
dataSource={jobs}
|
dataSource={jobs}
|
||||||
rowSelection={{ selectedRowKeys: [selectedJob] }}
|
rowSelection={{ selectedRowKeys: [selectedJob] }}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -5,12 +5,20 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries";
|
import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries";
|
||||||
import NoteUpsertModalComponent from "./note-upsert-modal.component";
|
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,
|
jobId,
|
||||||
visible,
|
visible,
|
||||||
changeVisibility,
|
changeVisibility,
|
||||||
refetch,
|
refetch,
|
||||||
existingNote
|
existingNote,currentUser
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
||||||
@@ -33,7 +41,7 @@ export default function NoteUpsertModalContainer({
|
|||||||
insertNote({
|
insertNote({
|
||||||
variables: {
|
variables: {
|
||||||
noteInput: [
|
noteInput: [
|
||||||
{ ...noteState, jobid: jobId, created_by: "patrick@bodyshop.app" } //TODO Fix the created by.
|
{ ...noteState, jobid: jobId, created_by: currentUser.email }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}).then(r => {
|
}).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 React, { useState } from "react";
|
||||||
import { AutoComplete, Icon, DatePicker, Radio } from "antd";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
export default function PartsOrderModalComponent({
|
export default function PartsOrderModalComponent({
|
||||||
vendorList,
|
vendorList,
|
||||||
state,
|
state,
|
||||||
sendTypeState
|
sendTypeState,
|
||||||
|
orderLinesState
|
||||||
}) {
|
}) {
|
||||||
const [partsOrder, setPartsOrder] = state;
|
const [partsOrder, setPartsOrder] = state;
|
||||||
const [sendType, setSendType] = sendTypeState;
|
const [sendType, setSendType] = sendTypeState;
|
||||||
|
const orderLines = orderLinesState[0];
|
||||||
const [vendorComplete, setVendorComplete] = useState(vendorList);
|
const [vendorComplete, setVendorComplete] = useState(vendorList);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -22,8 +24,6 @@ export default function PartsOrderModalComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSelect = (value, option) => {
|
const handleSelect = (value, option) => {
|
||||||
console.log("value", value);
|
|
||||||
console.log("option", option);
|
|
||||||
setPartsOrder({ ...partsOrder, vendorid: option.key });
|
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
|
<Radio.Group
|
||||||
defaultValue={sendType}
|
defaultValue={sendType}
|
||||||
onChange={e => setSendType(e.target.value)}>
|
onChange={e => setSendType(e.target.value)}>
|
||||||
|
|||||||
@@ -1,50 +1,105 @@
|
|||||||
import { Modal, notification } from "antd";
|
import { Modal, notification } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useQuery, useMutation } from "react-apollo";
|
import { useMutation, useQuery } from "react-apollo";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
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 { 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 {
|
import {
|
||||||
selectCurrentUser,
|
selectBodyshop,
|
||||||
selectBodyshop
|
selectCurrentUser
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
import PartsOrderModalComponent from "./parts-order-modal.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({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
setEmailOptions: e => dispatch(setEmailOptions(e)),
|
||||||
|
toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible())
|
||||||
|
});
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
null
|
mapDispatchToProps
|
||||||
)(function PartsOrderModalContainer({
|
)(function PartsOrderModalContainer({
|
||||||
partsOrderModalVisible,
|
partsOrderModalVisible,
|
||||||
linesToOrder,
|
linesToOrder,
|
||||||
jobId,
|
jobId,
|
||||||
currentUser,
|
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 { t } = useTranslation();
|
||||||
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
|
const [modalVisible, setModalVisible] = partsOrderModalVisible;
|
||||||
const sendTypeState = useState("e");
|
|
||||||
|
|
||||||
|
//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({
|
const partsOrderState = useState({
|
||||||
vendorid: null,
|
vendorid: null,
|
||||||
jobid: jobId,
|
jobid: jobId,
|
||||||
user_email: currentUser.email
|
user_email: currentUser.email
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("sendTypeState[0]", sendTypeState[0]);
|
|
||||||
const partsOrder = partsOrderState[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 = () => {
|
const handleOk = () => {
|
||||||
insertPartOrder({
|
insertPartOrder({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -53,29 +108,58 @@ export default connect(
|
|||||||
...partsOrder,
|
...partsOrder,
|
||||||
status: bodyshop.md_order_statuses.default_ordered || "Ordered*",
|
status: bodyshop.md_order_statuses.default_ordered || "Ordered*",
|
||||||
parts_order_lines: {
|
parts_order_lines: {
|
||||||
data: linesToOrder.reduce((acc, value) => {
|
data: orderLines
|
||||||
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;
|
|
||||||
}, [])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(r => {
|
.then(r => {
|
||||||
notification["success"]({
|
updateJobLines({
|
||||||
message: t("parts_orders.successes.created")
|
variables: {
|
||||||
});
|
ids: linesToOrder.map(item => item.id),
|
||||||
setModalVisible(false);
|
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 => {
|
.catch(error => {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
@@ -96,6 +180,7 @@ export default connect(
|
|||||||
vendorList={(data && data.vendors) || []}
|
vendorList={(data && data.vendors) || []}
|
||||||
state={partsOrderState}
|
state={partsOrderState}
|
||||||
sendTypeState={sendTypeState}
|
sendTypeState={sendTypeState}
|
||||||
|
orderLinesState={orderLinesState}
|
||||||
/>
|
/>
|
||||||
</LoadingSpinner>
|
</LoadingSpinner>
|
||||||
</Modal>
|
</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";
|
import React from "react";
|
||||||
|
|
||||||
function Cell({ children }) {
|
function Column({ children }) {
|
||||||
return <td>{children}</td>;
|
return <td>{children}</td>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ function Row({ children }) {
|
|||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
{React.Children.map(children, el => {
|
{React.Children.map(children, el => {
|
||||||
if (el.type === Cell) return el;
|
if (el.type === Column) return el;
|
||||||
|
|
||||||
return <td>{el}</td>;
|
return <td>{el}</td>;
|
||||||
})}
|
})}
|
||||||
@@ -25,7 +25,7 @@ function Grid({ children }) {
|
|||||||
|
|
||||||
if (el.type === Row) return el;
|
if (el.type === Row) return el;
|
||||||
|
|
||||||
if (el.type === Cell) {
|
if (el.type === Column) {
|
||||||
return <tr>{el}</tr>;
|
return <tr>{el}</tr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +41,6 @@ function Grid({ children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Grid.Row = Row;
|
Grid.Row = Row;
|
||||||
Grid.Cell = Cell;
|
Grid.Column = Column;
|
||||||
|
|
||||||
export default Grid;
|
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 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 }) {
|
export default function PartsOrderEmail({ data }) {
|
||||||
console.log("Data", data);
|
const order = data.parts_orders_by_pk;
|
||||||
return (
|
return (
|
||||||
<Email title='Hello World!'>
|
<D>
|
||||||
<Box>
|
<Header bodyshop={data.bodyshops[0]} />
|
||||||
<Item align='center'>
|
<table>
|
||||||
<Span fontSize={20}>
|
<tr>
|
||||||
This is an example email made with:
|
<td>{`Deliver By: ${order.deliver_by}`}</td>
|
||||||
<A href='https://github.com/chromakode/react-html-email'>
|
<td>{`Ordered By: ${order.user_email}`}</td>
|
||||||
react-html-email
|
</tr>
|
||||||
</A>
|
</table>
|
||||||
.
|
<table>
|
||||||
</Span>
|
<tr>
|
||||||
</Item>
|
<th>Line Description</th>
|
||||||
<Item align='center'>
|
<th>Part #</th>
|
||||||
<Span fontSize={20}>
|
<th>Price</th>
|
||||||
This is an example email made with:
|
<th>Line Remarks</th>
|
||||||
<A href='https://github.com/chromakode/react-html-email'>
|
</tr>
|
||||||
react-html-email
|
{order.parts_order_lines.map(item => (
|
||||||
</A>
|
<tr key={item.id}>
|
||||||
.
|
<td>{item.line_desc}</td>
|
||||||
</Span>
|
<td>{item.oem_partno}</td>
|
||||||
</Item>
|
<td>{item.act_price}</td>
|
||||||
</Box>
|
<td>{item.line_remarks}</td>
|
||||||
</Email>
|
</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_op
|
||||||
lbr_amt
|
lbr_amt
|
||||||
op_code_desc
|
op_code_desc
|
||||||
|
status
|
||||||
|
parts_order_lines {
|
||||||
|
id
|
||||||
|
parts_order {
|
||||||
|
id
|
||||||
|
order_number
|
||||||
|
order_date
|
||||||
|
user_email
|
||||||
|
vendor {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
allocations {
|
allocations {
|
||||||
id
|
id
|
||||||
hours
|
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 { Form, Icon, Tabs } from "antd";
|
||||||
import React, { useContext } from "react";
|
import React, { lazy, Suspense, useContext } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
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 ResetForm from "../../components/form-items-formatted/reset-form-item.component";
|
||||||
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
//import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
||||||
import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
|
//import JobsDetailClaims from "../../components/jobs-detail-claims/jobs-detail-claims.component";
|
||||||
import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
|
//import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
|
||||||
import JobsDetailFinancials from "../../components/jobs-detail-financial/jobs-detail-financial.component";
|
//import JobsDetailFinancials from "../../components/jobs-detail-financial/jobs-detail-financial.component";
|
||||||
import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
|
//import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component";
|
||||||
import JobsDetailInsurance from "../../components/jobs-detail-insurance/jobs-detail-insurance.component";
|
//import JobsDetailInsurance from "../../components/jobs-detail-insurance/jobs-detail-insurance.component";
|
||||||
import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container";
|
//import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documents.container";
|
||||||
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
//import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
||||||
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.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 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({
|
export default function JobsDetailPage({
|
||||||
job,
|
job,
|
||||||
@@ -39,13 +88,18 @@ export default function JobsDetailPage({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Suspense
|
||||||
|
fallback={<LoadingSpinner message={t("general.labels.loadingapp")} />}
|
||||||
|
>
|
||||||
<ScheduleJobModalContainer
|
<ScheduleJobModalContainer
|
||||||
scheduleModalState={scheduleModalState}
|
scheduleModalState={scheduleModalState}
|
||||||
jobId={job.id}
|
jobId={job.id}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<JobLineUpsertModalContainer />
|
||||||
|
<EnterInvoiceModalContainer />
|
||||||
|
|
||||||
<Form onSubmit={handleSubmit} {...formItemLayout} autoComplete={"off"}>
|
<Form onSubmit={handleSubmit} {...formItemLayout} autoComplete={"off"}>
|
||||||
<JobsDetailHeader
|
<JobsDetailHeader
|
||||||
job={job}
|
job={job}
|
||||||
@@ -116,7 +170,7 @@ export default function JobsDetailPage({
|
|||||||
}
|
}
|
||||||
key="partssublet"
|
key="partssublet"
|
||||||
>
|
>
|
||||||
Partssublet
|
<JobsDetailPliContainer job={job} />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
@@ -167,6 +221,6 @@ export default function JobsDetailPage({
|
|||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,8 @@ export default connect(
|
|||||||
const [selectedJob, setSelectedJob] = useState(hash ? hash.substr(1) : null);
|
const [selectedJob, setSelectedJob] = useState(hash ? hash.substr(1) : null);
|
||||||
const searchTextState = useState("");
|
const searchTextState = useState("");
|
||||||
const searchText = searchTextState[0];
|
const searchText = searchTextState[0];
|
||||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
//TODO Implement pagination for this.
|
|
||||||
|
|
||||||
console.log(typeof searchText);
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<JobsList
|
<JobsList
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
import React from "react";
|
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() {
|
export default function ManageRootPageComponent() {
|
||||||
//const client = useApolloClient();
|
//const client = useApolloClient();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SendEmailButton
|
{
|
||||||
MessageOptions={{
|
// <SendEmailButton
|
||||||
from: {
|
// MessageOptions={{
|
||||||
name: "Kavia"
|
// from: {
|
||||||
},
|
// name: "Kavia"
|
||||||
to: "patrickwf@gmail.com",
|
// },
|
||||||
replyTo: "patrickwf@gmail.com"
|
// to: "patrickwf@gmail.com",
|
||||||
}}
|
// replyTo: "patrickwf@gmail.com"
|
||||||
Template={PartsOrderEmail}
|
// }}
|
||||||
QueryConfig={[
|
// Template={PartsOrderEmail}
|
||||||
REPORT_QUERY_PARTS_ORDER_BY_PK,
|
// QueryConfig={[
|
||||||
{
|
// REPORT_QUERY_PARTS_ORDER_BY_PK,
|
||||||
variables: { id: "ebe0fb6b-6ec4-4ae0-8fdc-49bdf1e37ff3" }
|
// {
|
||||||
}
|
// variables: { id: "ebe0fb6b-6ec4-4ae0-8fdc-49bdf1e37ff3" }
|
||||||
]}>
|
// }
|
||||||
Send an Email in new Window
|
// ]}>
|
||||||
</SendEmailButton>
|
// Send an Email in new Window
|
||||||
|
// </SendEmailButton>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import FooterComponent from "../../components/footer/footer.component";
|
|||||||
import HeaderContainer from "../../components/header/header.container";
|
import HeaderContainer from "../../components/header/header.container";
|
||||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||||
import "./manage.page.styles.scss";
|
import "./manage.page.styles.scss";
|
||||||
|
import Test from "../../components/_test/test.component";
|
||||||
|
|
||||||
const ManageRootPage = lazy(() =>
|
const ManageRootPage = lazy(() =>
|
||||||
import("../manage-root/manage-root.page.container")
|
import("../manage-root/manage-root.page.container")
|
||||||
@@ -24,7 +25,7 @@ const JobsAvailablePage = lazy(() =>
|
|||||||
import("../jobs-available/jobs-available.page.container")
|
import("../jobs-available/jobs-available.page.container")
|
||||||
);
|
);
|
||||||
const ChatWindowContainer = lazy(() =>
|
const ChatWindowContainer = lazy(() =>
|
||||||
import("../../components/chat-window/chat-window.container")
|
import("../../components/chat-overlay/chat-overlay.container")
|
||||||
);
|
);
|
||||||
const ScheduleContainer = lazy(() =>
|
const ScheduleContainer = lazy(() =>
|
||||||
import("../schedule/schedule.page.container")
|
import("../schedule/schedule.page.container")
|
||||||
@@ -44,6 +45,10 @@ const ShopVendorPageContainer = lazy(() =>
|
|||||||
import("../shop-vendor/shop-vendor.page.container")
|
import("../shop-vendor/shop-vendor.page.container")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const EmailOverlayContainer = lazy(() =>
|
||||||
|
import("../../components/email-overlay/email-overlay.container.jsx")
|
||||||
|
);
|
||||||
|
|
||||||
const { Header, Content, Footer } = Layout;
|
const { Header, Content, Footer } = Layout;
|
||||||
|
|
||||||
export default function Manage({ match }) {
|
export default function Manage({ match }) {
|
||||||
@@ -60,17 +65,20 @@ export default function Manage({ match }) {
|
|||||||
</Header>
|
</Header>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Content
|
<Content
|
||||||
className='content-container'
|
className="content-container"
|
||||||
style={{ padding: "0em 4em 4em" }}>
|
style={{ padding: "0em 4em 4em" }}
|
||||||
|
>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<LoadingSpinner message={t("general.labels.loadingapp")} />
|
<LoadingSpinner message={t("general.labels.loadingapp")} />
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
|
DELETE THIS
|
||||||
|
<Test />
|
||||||
|
<EmailOverlayContainer />
|
||||||
<Route exact path={`${match.path}`} component={ManageRootPage} />
|
<Route exact path={`${match.path}`} component={ManageRootPage} />
|
||||||
|
|
||||||
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
|
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${match.path}/jobs/:jobId`}
|
path={`${match.path}/jobs/:jobId`}
|
||||||
@@ -111,13 +119,11 @@ export default function Manage({ match }) {
|
|||||||
path={`${match.path}/schedule`}
|
path={`${match.path}/schedule`}
|
||||||
component={ScheduleContainer}
|
component={ScheduleContainer}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${match.path}/available`}
|
path={`${match.path}/available`}
|
||||||
component={JobsAvailablePage}
|
component={JobsAvailablePage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route exact path={`${match.path}/shop/`} component={ShopPage} />
|
<Route exact path={`${match.path}/shop/`} component={ShopPage} />
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
@@ -130,13 +136,8 @@ export default function Manage({ match }) {
|
|||||||
</Layout>
|
</Layout>
|
||||||
<Footer>
|
<Footer>
|
||||||
<FooterComponent />
|
<FooterComponent />
|
||||||
{
|
|
||||||
// <Affix offsetBottom={20}>
|
|
||||||
// <ChatWindowContainer />
|
|
||||||
// </Affix>
|
|
||||||
}
|
|
||||||
<ChatWindowContainer />
|
|
||||||
</Footer>
|
</Footer>
|
||||||
|
<ChatWindowContainer />
|
||||||
<BackTop />
|
<BackTop />
|
||||||
</Layout>
|
</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 = () => ({
|
export const toggleChatVisible = () => ({
|
||||||
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE,
|
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE
|
||||||
//payload: user
|
//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";
|
import MessagingActionTypes from "./messaging.types";
|
||||||
|
|
||||||
const INITIAL_STATE = {
|
const INITIAL_STATE = {
|
||||||
visible: false
|
visible: false,
|
||||||
|
conversations: [
|
||||||
|
{ phone: "6049992002", open: false },
|
||||||
|
{ phone: "6049992991", open: false }
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
const messagingReducer = (state = INITIAL_STATE, action) => {
|
const messagingReducer = (state = INITIAL_STATE, action) => {
|
||||||
@@ -16,6 +20,36 @@ const messagingReducer = (state = INITIAL_STATE, action) => {
|
|||||||
...state,
|
...state,
|
||||||
visible: true
|
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:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,3 +6,8 @@ export const selectChatVisible = createSelector(
|
|||||||
[selectMessaging],
|
[selectMessaging],
|
||||||
messaging => messaging.visible
|
messaging => messaging.visible
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectConversations = createSelector(
|
||||||
|
[selectMessaging],
|
||||||
|
messaging => messaging.conversations
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
const MessagingActionTypes = {
|
const MessagingActionTypes = {
|
||||||
TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE",
|
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;
|
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 userReducer from "./user/user.reducer";
|
||||||
import messagingReducer from "./messaging/messaging.reducer";
|
import messagingReducer from "./messaging/messaging.reducer";
|
||||||
// import cartReducer from './cart/cart.reducer';
|
import emailReducer from "./email/email.reducer";
|
||||||
// import directoryReducer from './directory/directory.reducer';
|
import modalsReducer from './modals/modals.reducer'
|
||||||
// import shopReducer from './shop/shop.reducer';
|
|
||||||
|
|
||||||
const persistConfig = {
|
const persistConfig = {
|
||||||
key: "root",
|
key: "root",
|
||||||
storage,
|
storage,
|
||||||
//whitelist: ["cart"]
|
//whitelist: ["user"]
|
||||||
blacklist: ["user"]
|
blacklist: ["user", "email", "messaging", "modals"]
|
||||||
};
|
};
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
user: userReducer,
|
user: userReducer,
|
||||||
messaging: messagingReducer
|
messaging: messagingReducer,
|
||||||
// cart: cartReducer,
|
email: emailReducer,
|
||||||
// directory: directoryReducer,
|
modals: modalsReducer
|
||||||
// shop: shopReducer
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default persistReducer(persistConfig, rootReducer);
|
export default persistReducer(persistConfig, rootReducer);
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { all, call } from "redux-saga/effects";
|
import { all, call } from "redux-saga/effects";
|
||||||
|
|
||||||
//List of all Sagas
|
|
||||||
// import { shopSagas } from "./shop/shop.sagas";
|
|
||||||
import { userSagas } from "./user/user.sagas";
|
import { userSagas } from "./user/user.sagas";
|
||||||
import { messagingSagas } from "./messaging/messaging.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() {
|
export default function* rootSaga() {
|
||||||
//All starts all the Sagas concurrently.
|
yield all([call(userSagas), call(messagingSagas), call(emailSagas),
|
||||||
yield all([call(userSagas), call(messagingSagas)]);
|
call(modalsSagas)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createStore, applyMiddleware } from "redux";
|
import { createStore, applyMiddleware } from "redux";
|
||||||
import { persistStore } from "redux-persist";
|
import { persistStore } from "redux-persist";
|
||||||
import logger from "redux-logger";
|
import { createLogger } from "redux-logger";
|
||||||
import createSagaMiddleware from "redux-saga";
|
import createSagaMiddleware from "redux-saga";
|
||||||
import rootReducer from "./root.reducer";
|
import rootReducer from "./root.reducer";
|
||||||
import rootSaga from "./root.saga";
|
import rootSaga from "./root.saga";
|
||||||
@@ -8,7 +8,7 @@ import rootSaga from "./root.saga";
|
|||||||
const sagaMiddleWare = createSagaMiddleware();
|
const sagaMiddleWare = createSagaMiddleware();
|
||||||
const middlewares = [sagaMiddleWare];
|
const middlewares = [sagaMiddleWare];
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
middlewares.push(logger);
|
middlewares.push(createLogger({ collapsed: true, diff: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
|
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
|
||||||
|
|||||||
@@ -4,8 +4,17 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign"
|
"assign": "Assign"
|
||||||
},
|
},
|
||||||
|
"errors": {
|
||||||
|
"deleting": "Error encountered while deleting allocation. {{message}}",
|
||||||
|
"saving": "Error while allocating. {{message}}",
|
||||||
|
"validation": "Please ensure all fields are entered correctly. "
|
||||||
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"employee": "Allocated To"
|
"employee": "Allocated To"
|
||||||
|
},
|
||||||
|
"successes": {
|
||||||
|
"deleted": "Allocation deleted successfully.",
|
||||||
|
"save": "Allocated successfully. "
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"appointments": {
|
"appointments": {
|
||||||
@@ -67,6 +76,14 @@
|
|||||||
"insert": "Uploaded document successfully. "
|
"insert": "Uploaded document successfully. "
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"emails": {
|
||||||
|
"errors": {
|
||||||
|
"notsent": "Email not sent. Error encountered while sending {{message}}"
|
||||||
|
},
|
||||||
|
"successes": {
|
||||||
|
"sent": "Email sent successfully."
|
||||||
|
}
|
||||||
|
},
|
||||||
"employees": {
|
"employees": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"new": "New Employee"
|
"new": "New Employee"
|
||||||
@@ -128,14 +145,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"joblines": {
|
"joblines": {
|
||||||
|
"actions": {
|
||||||
|
"new": "New Line"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"creating": "Error encountered while creating job line. {{message}}",
|
||||||
|
"updating": "Error encountered updating job line. {{message}}"
|
||||||
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"act_price": "Actual Price",
|
"act_price": "Actual Price",
|
||||||
"db_price": "Database Price",
|
"db_price": "Database Price",
|
||||||
"line_desc": "Line Description",
|
"line_desc": "Line Description",
|
||||||
|
"line_ind": "S#",
|
||||||
"mod_lb_hrs": "Labor Hours",
|
"mod_lb_hrs": "Labor Hours",
|
||||||
|
"mod_lbr_ty": "Labor Type",
|
||||||
"oem_partno": "OEM Part #",
|
"oem_partno": "OEM Part #",
|
||||||
|
"op_code_desc": "Operation Code Description",
|
||||||
"part_type": "Part Type",
|
"part_type": "Part Type",
|
||||||
|
"status": "Status",
|
||||||
"unq_seq": "Seq #"
|
"unq_seq": "Seq #"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"edit": "Edit Line",
|
||||||
|
"new": "New Line"
|
||||||
|
},
|
||||||
|
"successes": {
|
||||||
|
"created": "Job line created successfully.",
|
||||||
|
"updated": "Job line updated successfully."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jobs": {
|
"jobs": {
|
||||||
@@ -323,6 +359,12 @@
|
|||||||
"shops": "My Shops"
|
"shops": "My Shops"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"messaging": {
|
||||||
|
"labels": {
|
||||||
|
"messaging": "Messaging",
|
||||||
|
"typeamessage": "Send a message..."
|
||||||
|
}
|
||||||
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
@@ -385,10 +427,13 @@
|
|||||||
"creating": "Error encountered when creating parts order. "
|
"creating": "Error encountered when creating parts order. "
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"deliver_by": "Deliver By"
|
"deliver_by": "Deliver By",
|
||||||
|
"lineremarks": "Line Remarks"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"email": "Send by Email",
|
"email": "Send by Email",
|
||||||
|
"inthisorder": "Parts in this Order",
|
||||||
|
"orderhistory": "Order History",
|
||||||
"print": "Show Printed Form"
|
"print": "Show Printed Form"
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
|
|||||||
@@ -4,8 +4,17 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Asignar"
|
"assign": "Asignar"
|
||||||
},
|
},
|
||||||
|
"errors": {
|
||||||
|
"deleting": "",
|
||||||
|
"saving": "",
|
||||||
|
"validation": ""
|
||||||
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"employee": "Asignado a"
|
"employee": "Asignado a"
|
||||||
|
},
|
||||||
|
"successes": {
|
||||||
|
"deleted": "",
|
||||||
|
"save": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"appointments": {
|
"appointments": {
|
||||||
@@ -67,6 +76,14 @@
|
|||||||
"insert": "Documento cargado con éxito."
|
"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": {
|
"employees": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"new": "Nuevo empleado"
|
"new": "Nuevo empleado"
|
||||||
@@ -128,14 +145,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"joblines": {
|
"joblines": {
|
||||||
|
"actions": {
|
||||||
|
"new": ""
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"creating": "",
|
||||||
|
"updating": ""
|
||||||
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"act_price": "Precio actual",
|
"act_price": "Precio actual",
|
||||||
"db_price": "Precio de base de datos",
|
"db_price": "Precio de base de datos",
|
||||||
"line_desc": "Descripción de línea",
|
"line_desc": "Descripción de línea",
|
||||||
|
"line_ind": "S#",
|
||||||
"mod_lb_hrs": "Horas laborales",
|
"mod_lb_hrs": "Horas laborales",
|
||||||
|
"mod_lbr_ty": "Tipo de trabajo",
|
||||||
"oem_partno": "OEM parte #",
|
"oem_partno": "OEM parte #",
|
||||||
|
"op_code_desc": "",
|
||||||
"part_type": "Tipo de parte",
|
"part_type": "Tipo de parte",
|
||||||
|
"status": "Estado",
|
||||||
"unq_seq": "Seq #"
|
"unq_seq": "Seq #"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"edit": "Línea de edición",
|
||||||
|
"new": "Nueva línea"
|
||||||
|
},
|
||||||
|
"successes": {
|
||||||
|
"created": "",
|
||||||
|
"updated": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jobs": {
|
"jobs": {
|
||||||
@@ -323,6 +359,12 @@
|
|||||||
"shops": "Mis tiendas"
|
"shops": "Mis tiendas"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"messaging": {
|
||||||
|
"labels": {
|
||||||
|
"messaging": "Mensajería",
|
||||||
|
"typeamessage": "Enviar un mensaje..."
|
||||||
|
}
|
||||||
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"actions": "Comportamiento",
|
"actions": "Comportamiento",
|
||||||
@@ -385,10 +427,13 @@
|
|||||||
"creating": "Se encontró un error al crear el pedido de piezas."
|
"creating": "Se encontró un error al crear el pedido de piezas."
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"deliver_by": "Entregado por"
|
"deliver_by": "Entregado por",
|
||||||
|
"lineremarks": "Comentarios de línea"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"email": "Enviar por correo electrónico",
|
"email": "Enviar por correo electrónico",
|
||||||
|
"inthisorder": "Partes en este pedido",
|
||||||
|
"orderhistory": "Historial de pedidos",
|
||||||
"print": "Mostrar formulario impreso"
|
"print": "Mostrar formulario impreso"
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
|
|||||||
@@ -4,8 +4,17 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Attribuer"
|
"assign": "Attribuer"
|
||||||
},
|
},
|
||||||
|
"errors": {
|
||||||
|
"deleting": "",
|
||||||
|
"saving": "",
|
||||||
|
"validation": ""
|
||||||
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"employee": "Alloué à"
|
"employee": "Alloué à"
|
||||||
|
},
|
||||||
|
"successes": {
|
||||||
|
"deleted": "",
|
||||||
|
"save": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"appointments": {
|
"appointments": {
|
||||||
@@ -67,6 +76,14 @@
|
|||||||
"insert": "Document téléchargé avec succès."
|
"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": {
|
"employees": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"new": "Nouvel employé"
|
"new": "Nouvel employé"
|
||||||
@@ -128,14 +145,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"joblines": {
|
"joblines": {
|
||||||
|
"actions": {
|
||||||
|
"new": ""
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"creating": "",
|
||||||
|
"updating": ""
|
||||||
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"act_price": "Prix actuel",
|
"act_price": "Prix actuel",
|
||||||
"db_price": "Prix de la base de données",
|
"db_price": "Prix de la base de données",
|
||||||
"line_desc": "Description de la ligne",
|
"line_desc": "Description de la ligne",
|
||||||
|
"line_ind": "S#",
|
||||||
"mod_lb_hrs": "Heures de travail",
|
"mod_lb_hrs": "Heures de travail",
|
||||||
|
"mod_lbr_ty": "Type de travail",
|
||||||
"oem_partno": "Pièce OEM #",
|
"oem_partno": "Pièce OEM #",
|
||||||
|
"op_code_desc": "",
|
||||||
"part_type": "Type de pièce",
|
"part_type": "Type de pièce",
|
||||||
|
"status": "Statut",
|
||||||
"unq_seq": "Seq #"
|
"unq_seq": "Seq #"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"edit": "Ligne d'édition",
|
||||||
|
"new": "Nouvelle ligne"
|
||||||
|
},
|
||||||
|
"successes": {
|
||||||
|
"created": "",
|
||||||
|
"updated": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jobs": {
|
"jobs": {
|
||||||
@@ -323,6 +359,12 @@
|
|||||||
"shops": "Mes boutiques"
|
"shops": "Mes boutiques"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"messaging": {
|
||||||
|
"labels": {
|
||||||
|
"messaging": "Messagerie",
|
||||||
|
"typeamessage": "Envoyer un message..."
|
||||||
|
}
|
||||||
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"actions": "actes",
|
"actions": "actes",
|
||||||
@@ -385,10 +427,13 @@
|
|||||||
"creating": "Erreur rencontrée lors de la création de la commande de pièces."
|
"creating": "Erreur rencontrée lors de la création de la commande de pièces."
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"deliver_by": "Livrer par"
|
"deliver_by": "Livrer par",
|
||||||
|
"lineremarks": "Remarques sur la ligne"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"email": "Envoyé par email",
|
"email": "Envoyé par email",
|
||||||
|
"inthisorder": "Pièces dans cette commande",
|
||||||
|
"orderhistory": "Historique des commandes",
|
||||||
"print": "Afficher le formulaire imprimé"
|
"print": "Afficher le formulaire imprimé"
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ export function alphaSort(a, b) {
|
|||||||
let A;
|
let A;
|
||||||
let B;
|
let B;
|
||||||
A = a ? a.toLowerCase() : "";
|
A = a ? a.toLowerCase() : "";
|
||||||
|
|
||||||
B = b ? b.toLowerCase() : "";
|
B = b ? b.toLowerCase() : "";
|
||||||
console.log("Objects", A, B, A < B, A > B);
|
|
||||||
|
|
||||||
if (A < B)
|
if (A < B)
|
||||||
//sort string ascending
|
//sort string ascending
|
||||||
return -1;
|
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",
|
"concurrently": "^4.0.1",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"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"
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||||
|
|
||||||
hasura-cli@^1.0.0-beta.10:
|
hasura-cli@^1.1.0:
|
||||||
version "1.0.0-rc.1"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/hasura-cli/-/hasura-cli-1.0.0-rc.1.tgz#481453f88e7624f468f329c75a88fbbde3407f00"
|
resolved "https://registry.yarnpkg.com/hasura-cli/-/hasura-cli-1.1.0.tgz#a095e94c654d30354d8979602b8c0047c7b8a9e1"
|
||||||
integrity sha512-w6DGAhJZ6l7U89SD6QIxYetP3/dDxJc4jEVzjMAYsueeYWQKDeGgtZBFBW1sdgAlUtRmtIa5wbiFLXuagB6JqA==
|
integrity sha512-D1qXoYydx9Mgq7VQdCmOOvTlYhd1RcjQtn4s7pN1wb5w1ORIcDFLm1rS3w97bsx7wPRotIl0reyhc3+FDq+FFg==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.19.0"
|
axios "^0.19.0"
|
||||||
chalk "^2.4.2"
|
chalk "^2.4.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user