Revert "Release/2026 02 27 (pull request #3070)"
This commit is contained in:
20
.gitignore
vendored
20
.gitignore
vendored
@@ -129,26 +129,6 @@ vitest-coverage/
|
|||||||
test-output.txt
|
test-output.txt
|
||||||
server/job/test/fixtures
|
server/job/test/fixtures
|
||||||
|
|
||||||
# Keep .github ignored by default, but track Copilot instructions
|
|
||||||
.github
|
.github
|
||||||
!.github/
|
|
||||||
.github/*
|
|
||||||
!.github/copilot-instructions.md
|
|
||||||
_reference/ragmate/.ragmate.env
|
_reference/ragmate/.ragmate.env
|
||||||
docker_data
|
docker_data
|
||||||
/.cursorrules
|
|
||||||
/AGENTS.md
|
|
||||||
/AI_CONTEXT.md
|
|
||||||
/CLAUDE.md
|
|
||||||
/COPILOT.md
|
|
||||||
/GEMINI.md
|
|
||||||
/_reference/select-component-test-plan.md
|
|
||||||
|
|
||||||
/.cursorrules
|
|
||||||
/AGENTS.md
|
|
||||||
/AI_CONTEXT.md
|
|
||||||
/CLAUDE.md
|
|
||||||
/COPILOT.md
|
|
||||||
/.github/copilot-instructions.md
|
|
||||||
/GEMINI.md
|
|
||||||
/_reference/select-component-test-plan.md
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<babeledit_project be_version="2.7.1" version="1.2">
|
<babeledit_project version="1.2" be_version="2.7.1">
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
BabelEdit project file
|
BabelEdit project file
|
||||||
@@ -2549,27 +2549,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>confidence</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>cost_center</name>
|
<name>cost_center</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -3015,48 +2994,6 @@
|
|||||||
<folder_node>
|
<folder_node>
|
||||||
<name>errors</name>
|
<name>errors</name>
|
||||||
<children>
|
<children>
|
||||||
<concept_node>
|
|
||||||
<name>calculating_totals</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>calculating_totals_generic</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>creating</name>
|
<name>creating</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -3529,310 +3466,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<folder_node>
|
|
||||||
<name>ai</name>
|
|
||||||
<children>
|
|
||||||
<concept_node>
|
|
||||||
<name>accept_and_continue</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>
|
|
||||||
<folder_node>
|
|
||||||
<name>confidence</name>
|
|
||||||
<children>
|
|
||||||
<concept_node>
|
|
||||||
<name>breakdown</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>match</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>missing_data</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>ocr</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>overall</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>
|
|
||||||
<concept_node>
|
|
||||||
<name>disclaimer_title</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>generic_failure</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>multipage</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>processing</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>scan</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>scancomplete</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>scanfailed</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>scanstarted</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>
|
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>bill_lines</name>
|
<name>bill_lines</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -17769,48 +17402,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>earlyrorequired</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>earlyrorequired.message</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>
|
||||||
@@ -20632,27 +20223,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>gotoadmin</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>login</name>
|
<name>login</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -21450,27 +21020,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>apply</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>areyousure</name>
|
<name>areyousure</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -21513,27 +21062,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>beta</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>cancel</name>
|
<name>cancel</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -27132,27 +26660,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>convertwithoutearlyro</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>createiou</name>
|
<name>createiou</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -27240,27 +26747,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>createearlyro</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>createnewcustomer</name>
|
<name>createnewcustomer</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -27392,27 +26878,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>update_ro</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>usegeneric</name>
|
<name>usegeneric</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -30493,27 +29958,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>customer</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>dms_make</name>
|
<name>dms_make</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -30918,90 +30362,6 @@
|
|||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<concept_node>
|
|
||||||
<name>rr_opcode</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>rr_opcode_base</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>rr_opcode_prefix</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>rr_opcode_suffix</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>sale</name>
|
<name>sale</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -36606,74 +35966,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<folder_node>
|
|
||||||
<name>earlyro</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>fields</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>willupdate</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>
|
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>invoicedatefuture</name>
|
<name>invoicedatefuture</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -39767,27 +39059,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>early_ro_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>
|
<concept_node>
|
||||||
<name>exported</name>
|
<name>exported</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -18,4 +18,3 @@ VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
|||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
|
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
|
||||||
VITE_APP_ENABLE_RESPONSIVE_TABLE_FILTERING=false
|
|
||||||
|
|||||||
@@ -20,4 +20,3 @@ VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
|||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
|
VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
|
||||||
VITE_APP_ENABLE_RESPONSIVE_TABLE_FILTERING=false
|
|
||||||
|
|||||||
890
client/package-lock.json
generated
890
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,9 +8,9 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-browser": "^2.35.3",
|
"@amplitude/analytics-browser": "^2.34.0",
|
||||||
"@ant-design/pro-layout": "^7.22.6",
|
"@ant-design/pro-layout": "^7.22.6",
|
||||||
"@apollo/client": "^4.1.6",
|
"@apollo/client": "^4.1.3",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/modifiers": "^9.0.0",
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
@@ -18,43 +18,43 @@
|
|||||||
"@emotion/is-prop-valid": "^1.4.0",
|
"@emotion/is-prop-valid": "^1.4.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^5.0.1",
|
"@fingerprintjs/fingerprintjs": "^5.0.1",
|
||||||
"@firebase/analytics": "^0.10.19",
|
"@firebase/analytics": "^0.10.19",
|
||||||
"@firebase/app": "^0.14.8",
|
"@firebase/app": "^0.14.7",
|
||||||
"@firebase/auth": "^1.12.0",
|
"@firebase/auth": "^1.12.0",
|
||||||
"@firebase/firestore": "^4.11.0",
|
"@firebase/firestore": "^4.10.0",
|
||||||
"@firebase/messaging": "^0.12.22",
|
"@firebase/messaging": "^0.12.22",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.11.2",
|
"@reduxjs/toolkit": "^2.11.2",
|
||||||
"@sentry/cli": "^3.2.2",
|
"@sentry/cli": "^3.1.0",
|
||||||
"@sentry/react": "^10.40.0",
|
"@sentry/react": "^10.38.0",
|
||||||
"@sentry/vite-plugin": "^4.9.1",
|
"@sentry/vite-plugin": "^4.8.0",
|
||||||
"@splitsoftware/splitio-react": "^2.6.1",
|
"@splitsoftware/splitio-react": "^2.6.1",
|
||||||
"@tanem/react-nprogress": "^5.0.63",
|
"@tanem/react-nprogress": "^5.0.58",
|
||||||
"antd": "^6.3.1",
|
"antd": "^6.2.2",
|
||||||
"apollo-link-logger": "^3.0.0",
|
"apollo-link-logger": "^3.0.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.13.5",
|
"axios": "^1.13.4",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"css-box-model": "^1.2.1",
|
"css-box-model": "^1.2.1",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"dayjs-business-days2": "^1.3.2",
|
"dayjs-business-days2": "^1.3.2",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.2.3",
|
||||||
"env-cmd": "^11.0.0",
|
"env-cmd": "^11.0.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"graphql": "^16.13.0",
|
"graphql": "^16.12.0",
|
||||||
"graphql-ws": "^6.0.7",
|
"graphql-ws": "^6.0.7",
|
||||||
"i18next": "^25.8.13",
|
"i18next": "^25.8.0",
|
||||||
"i18next-browser-languagedetector": "^8.2.1",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"libphonenumber-js": "^1.12.38",
|
"libphonenumber-js": "^1.12.36",
|
||||||
"lightningcss": "^1.31.1",
|
"lightningcss": "^1.31.1",
|
||||||
"logrocket": "^12.0.0",
|
"logrocket": "^12.0.0",
|
||||||
"markerjs2": "^2.32.7",
|
"markerjs2": "^2.32.7",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"normalize-url": "^8.1.1",
|
"normalize-url": "^8.1.1",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"phone": "^3.1.71",
|
"phone": "^3.1.70",
|
||||||
"posthog-js": "^1.355.0",
|
"posthog-js": "^1.336.4",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.3.1",
|
"query-string": "^9.3.1",
|
||||||
"raf-schd": "^4.0.3",
|
"raf-schd": "^4.0.3",
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
"react-product-fruits": "^2.2.62",
|
"react-product-fruits": "^2.2.62",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-resizable": "^3.1.3",
|
"react-resizable": "^3.1.3",
|
||||||
"react-router-dom": "^7.13.1",
|
"react-router-dom": "^7.13.0",
|
||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-virtuoso": "^4.18.1",
|
"react-virtuoso": "^4.18.1",
|
||||||
"recharts": "^3.7.0",
|
"recharts": "^3.7.0",
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"sass": "^1.97.3",
|
"sass": "^1.97.3",
|
||||||
"socket.io-client": "^4.8.3",
|
"socket.io-client": "^4.8.3",
|
||||||
"styled-components": "^6.3.11",
|
"styled-components": "^6.3.8",
|
||||||
"vite-plugin-ejs": "^1.7.0",
|
"vite-plugin-ejs": "^1.7.0",
|
||||||
"web-vitals": "^5.1.0"
|
"web-vitals": "^5.1.0"
|
||||||
},
|
},
|
||||||
@@ -144,11 +144,11 @@
|
|||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@playwright/test": "^1.58.2",
|
"@playwright/test": "^1.58.0",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@vitejs/plugin-react": "^5.1.4",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"babel-plugin-react-compiler": "^1.0.0",
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
"browserslist": "^4.28.1",
|
"browserslist": "^4.28.1",
|
||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
@@ -156,16 +156,16 @@
|
|||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
||||||
"globals": "^17.3.0",
|
"globals": "^17.2.0",
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^27.4.0",
|
||||||
"memfs": "^4.56.10",
|
"memfs": "^4.56.10",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"playwright": "^1.58.2",
|
"playwright": "^1.58.0",
|
||||||
"react-error-overlay": "^6.1.0",
|
"react-error-overlay": "^6.1.0",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-plugin-babel": "^1.5.1",
|
"vite-plugin-babel": "^1.4.1",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-node-polyfills": "^0.25.0",
|
"vite-plugin-node-polyfills": "^0.25.0",
|
||||||
"vite-plugin-pwa": "^1.2.0",
|
"vite-plugin-pwa": "^1.2.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ApolloProvider } from "@apollo/client/react";
|
import { ApolloProvider } from "@apollo/client/react";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
||||||
import { ConfigProvider, Grid } from "antd";
|
import { ConfigProvider } from "antd";
|
||||||
import enLocale from "antd/es/locale/en_US";
|
import enLocale from "antd/es/locale/en_US";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { CookiesProvider } from "react-cookie";
|
import { CookiesProvider } from "react-cookie";
|
||||||
@@ -43,47 +43,10 @@ function AppContainer() {
|
|||||||
|
|
||||||
const currentUser = useSelector(selectCurrentUser);
|
const currentUser = useSelector(selectCurrentUser);
|
||||||
const isDarkMode = useSelector(selectDarkMode);
|
const isDarkMode = useSelector(selectDarkMode);
|
||||||
const screens = Grid.useBreakpoint();
|
|
||||||
const isPhone = !screens.md;
|
|
||||||
const isUltraWide = Boolean(screens.xxxl);
|
|
||||||
|
|
||||||
const theme = useMemo(() => {
|
const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]);
|
||||||
const baseTheme = getTheme(isDarkMode);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...baseTheme,
|
|
||||||
token: {
|
|
||||||
...(baseTheme.token || {}),
|
|
||||||
screenXXXL: 2160
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
...(baseTheme.components || {}),
|
|
||||||
Table: {
|
|
||||||
...(baseTheme.components?.Table || {}),
|
|
||||||
cellFontSizeSM: isPhone ? 12 : 13,
|
|
||||||
cellFontSizeMD: isPhone ? 13 : isUltraWide ? 15 : 14,
|
|
||||||
cellFontSize: isUltraWide ? 15 : 14,
|
|
||||||
cellPaddingInlineSM: isPhone ? 8 : 10,
|
|
||||||
cellPaddingInlineMD: isPhone ? 10 : 14,
|
|
||||||
cellPaddingInline: isUltraWide ? 20 : 16,
|
|
||||||
cellPaddingBlockSM: isPhone ? 8 : 10,
|
|
||||||
cellPaddingBlockMD: isPhone ? 10 : 12,
|
|
||||||
cellPaddingBlock: isUltraWide ? 14 : 12,
|
|
||||||
selectionColumnWidth: isPhone ? 44 : 52
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [isDarkMode, isPhone, isUltraWide]);
|
|
||||||
|
|
||||||
const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []);
|
const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []);
|
||||||
const antdTable = useMemo(() => ({ scroll: { x: "max-content" } }), []);
|
|
||||||
const antdPagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
showSizeChanger: !isPhone,
|
|
||||||
totalBoundaryShowSizeChanger: 100
|
|
||||||
}),
|
|
||||||
[isPhone]
|
|
||||||
);
|
|
||||||
|
|
||||||
const antdForm = useMemo(
|
const antdForm = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -159,16 +122,7 @@ function AppContainer() {
|
|||||||
return (
|
return (
|
||||||
<CookiesProvider>
|
<CookiesProvider>
|
||||||
<ApolloProvider client={client}>
|
<ApolloProvider client={client}>
|
||||||
<ConfigProvider
|
<ConfigProvider input={antdInput} locale={enLocale} theme={theme} form={antdForm}>
|
||||||
input={antdInput}
|
|
||||||
locale={enLocale}
|
|
||||||
theme={theme}
|
|
||||||
form={antdForm}
|
|
||||||
table={antdTable}
|
|
||||||
pagination={antdPagination}
|
|
||||||
componentSize={isPhone ? "small" : isUltraWide ? "large" : "middle"}
|
|
||||||
popupOverflow="viewport"
|
|
||||||
>
|
|
||||||
<GlobalLoadingBar />
|
<GlobalLoadingBar />
|
||||||
<SplitFactoryProvider config={config}>
|
<SplitFactoryProvider config={config}>
|
||||||
<SplitClientProvider>
|
<SplitClientProvider>
|
||||||
|
|||||||
@@ -443,30 +443,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DMS top panels: prevent card/table overflow into adjacent column at desktop+zoom */
|
|
||||||
.dms-top-panel-col {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dms-top-panel-col > .ant-card {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dms-top-panel-col > .ant-card .ant-card-body {
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dms-top-panel-col .ant-table-wrapper,
|
|
||||||
.dms-top-panel-col .ant-tabs,
|
|
||||||
.dms-top-panel-col .ant-tabs-content,
|
|
||||||
.dms-top-panel-col .ant-tabs-tabpane {
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
//.rbc-time-header-gutter {
|
//.rbc-time-header-gutter {
|
||||||
// padding: 0;
|
// padding: 0;
|
||||||
//}
|
//}
|
||||||
@@ -499,13 +475,3 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.global-search-autocomplete-fix {
|
|
||||||
// This is the extra value render that causes the “duplicate text”
|
|
||||||
.ant-select-selection-item {
|
|
||||||
position: absolute !important;
|
|
||||||
left: -10000px !important;
|
|
||||||
pointer-events: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const currentTheme = import.meta.env.DEV ? devTheme : prodTheme;
|
|||||||
|
|
||||||
const getTheme = (isDarkMode) => ({
|
const getTheme = (isDarkMode) => ({
|
||||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||||
...defaultsDeep({}, currentTheme, defaultTheme(isDarkMode))
|
...defaultsDeep(currentTheme, defaultTheme)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default getTheme;
|
export default getTheme;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, Checkbox, Input, Space } from "antd";
|
import { Card, Checkbox, Input, Space, Table } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -16,7 +16,6 @@ import PayableExportAll from "../payable-export-all-button/payable-export-all-bu
|
|||||||
import PayableExportButton from "../payable-export-button/payable-export-button.component";
|
import PayableExportButton from "../payable-export-button/payable-export-button.component";
|
||||||
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import useLocalStorage from "./../../utils/useLocalStorage";
|
import useLocalStorage from "./../../utils/useLocalStorage";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -180,12 +179,11 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ placement: "top", pageSize: exportPageLimit, showSizeChanger: false }}
|
pagination={{ placement: "top", pageSize: exportPageLimit, showSizeChanger: false }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["vendorname", "invoice_number", "ro_number", "total", "actions"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, Input, Space } from "antd";
|
import { Card, Input, Space, Table } from "antd";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -17,7 +17,6 @@ import PaymentExportButton from "../payment-export-button/payment-export-button.
|
|||||||
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
|
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
|
||||||
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -193,12 +192,11 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ placement: "top", pageSize: exportPageLimit, showSizeChanger: false }}
|
pagination={{ placement: "top", pageSize: exportPageLimit, showSizeChanger: false }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["ro_number", "date", "owner", "amount", "actions"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, Card, Input, Space } from "antd";
|
import { Button, Card, Input, Space, Table } from "antd";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -17,7 +17,6 @@ import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-butto
|
|||||||
import JobMarkSelectedExported from "../jobs-mark-selected-exported/jobs-mark-selected-exported";
|
import JobMarkSelectedExported from "../jobs-mark-selected-exported/jobs-mark-selected-exported";
|
||||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -210,12 +209,11 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ placement: "top", pageSize: exportPageLimit, showSizeChanger: false }}
|
pagination={{ placement: "top", pageSize: exportPageLimit, showSizeChanger: false }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["ro_number", "status", "owner", "clm_total", "actions"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
|
|||||||
@@ -29,18 +29,19 @@ export function AllocationsAssignmentComponent({
|
|||||||
<Select
|
<Select
|
||||||
id="employeeSelector"
|
id="employeeSelector"
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "label",
|
optionFilterProp: "children",
|
||||||
filterOption: (input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
placeholder="Select a person"
|
placeholder="Select a person"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={bodyshop.employees.map((emp) => ({
|
>
|
||||||
value: emp.id,
|
{bodyshop.employees.map((emp) => (
|
||||||
key: emp.id,
|
<Select.Option value={emp.id} key={emp.id}>
|
||||||
label: `${emp.first_name} ${emp.last_name}`
|
{`${emp.first_name} ${emp.last_name}`}
|
||||||
}))}
|
</Select.Option>
|
||||||
/>
|
))}
|
||||||
|
</Select>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
defaultValue={assignment.hours}
|
defaultValue={assignment.hours}
|
||||||
placeholder={t("joblines.fields.mod_lb_hrs")}
|
placeholder={t("joblines.fields.mod_lb_hrs")}
|
||||||
|
|||||||
@@ -31,17 +31,19 @@ export default connect(
|
|||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "label",
|
optionFilterProp: "children",
|
||||||
filterOption: (input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
placeholder="Select a person"
|
placeholder="Select a person"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={bodyshop.employees.map((emp) => ({
|
>
|
||||||
value: emp.id,
|
{bodyshop.employees.map((emp) => (
|
||||||
label: `${emp.first_name} ${emp.last_name}`
|
<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}>
|
<Button type="primary" disabled={!assignment.employeeid} onClick={handleAssignment}>
|
||||||
Assign
|
Assign
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
import { Table } from "antd";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -62,12 +62,11 @@ export default function AuditTrailListComponent({ loading, data }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable
|
<Table
|
||||||
{...formItemLayout}
|
{...formItemLayout}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={[" created", "operation", " old_val", "useremail"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
import { Table } from "antd";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
@@ -47,12 +47,11 @@ export default function EmailAuditTrailListComponent({ loading, data }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable
|
<Table
|
||||||
{...formItemLayout}
|
{...formItemLayout}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={[" created", "useremail"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -28,20 +28,6 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const initialValues =
|
|
||||||
data && data.bills_by_pk
|
|
||||||
? {
|
|
||||||
...data.bills_by_pk,
|
|
||||||
billlines: (data.bills_by_pk.billlines || []).map((bl) => {
|
|
||||||
const oem = bl.oem_partno || (bl.jobline && bl.jobline.oem_partno) || "";
|
|
||||||
const alt = bl.alt_partno || (bl.jobline && bl.jobline.alt_partno) || "";
|
|
||||||
return {
|
|
||||||
...bl,
|
|
||||||
oem_partno: `${oem || ""} ${alt ? `(${alt})` : ""}`.trim()
|
|
||||||
};
|
|
||||||
})
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const handleFinish = ({ billlines }) => {
|
const handleFinish = ({ billlines }) => {
|
||||||
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
|
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
|
||||||
@@ -88,9 +74,8 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
|||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
title={t("bills.actions.return")}
|
title={t("bills.actions.return")}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
width={700}
|
|
||||||
>
|
>
|
||||||
<Form initialValues={initialValues} onFinish={handleFinish} form={form}>
|
<Form initialValues={data?.bills_by_pk} onFinish={handleFinish} form={form}>
|
||||||
<Form.List name={["billlines"]}>
|
<Form.List name={["billlines"]}>
|
||||||
{(fields) => {
|
{(fields) => {
|
||||||
return (
|
return (
|
||||||
@@ -110,10 +95,9 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>{t("billlines.fields.line_desc")}</td>
|
<td>{t("billlines.fields.line_desc")}</td>
|
||||||
<td>{t("billlines.fields.oem_partno")}</td>
|
<td>{t("billlines.fields.quantity")}</td>
|
||||||
<td style={{ textAlign: "right" }}>{t("billlines.fields.quantity")}</td>
|
<td>{t("billlines.fields.actual_price")}</td>
|
||||||
<td style={{ textAlign: "right" }}>{t("billlines.fields.actual_price")}</td>
|
<td>{t("billlines.fields.actual_cost")}</td>
|
||||||
<td style={{ textAlign: "right" }}>{t("billlines.fields.actual_cost")}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -143,15 +127,6 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Form.Item
|
|
||||||
// label={t("joblines.fields.oem_partno")}
|
|
||||||
key={`${index}jobline.oem_partno`}
|
|
||||||
name={[field.name, "oem_partno"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
<td style={{ textAlign: "right" }}>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
// label={t("joblines.fields.quantity")}
|
// label={t("joblines.fields.quantity")}
|
||||||
key={`${index}quantity`}
|
key={`${index}quantity`}
|
||||||
@@ -160,7 +135,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
|||||||
<ReadOnlyFormItemComponent />
|
<ReadOnlyFormItemComponent />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
<td style={{ textAlign: "right" }}>
|
<td>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
// label={t("joblines.fields.actual_price")}
|
// label={t("joblines.fields.actual_price")}
|
||||||
key={`${index}actual_price`}
|
key={`${index}actual_price`}
|
||||||
@@ -169,7 +144,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
|||||||
<ReadOnlyFormItemComponent type="currency" />
|
<ReadOnlyFormItemComponent type="currency" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
<td style={{ textAlign: "right" }}>
|
<td>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
// label={t("joblines.fields.actual_cost")}
|
// label={t("joblines.fields.actual_cost")}
|
||||||
key={`${index}actual_cost`}
|
key={`${index}actual_cost`}
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ export default function BillDetailEditcontainer() {
|
|||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
|
|
||||||
const screens = Grid.useBreakpoint();
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
|
.filter((screen) => !!screen[1])
|
||||||
|
.slice(-1)[0];
|
||||||
|
|
||||||
const bpoints = {
|
const bpoints = {
|
||||||
xs: "100%",
|
xs: "100%",
|
||||||
sm: "100%",
|
sm: "100%",
|
||||||
@@ -17,14 +19,7 @@ export default function BillDetailEditcontainer() {
|
|||||||
xl: "90%",
|
xl: "90%",
|
||||||
xxl: "90%"
|
xxl: "90%"
|
||||||
};
|
};
|
||||||
|
const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%";
|
||||||
let drawerPercentage = "100%";
|
|
||||||
if (screens.xxl) drawerPercentage = bpoints.xxl;
|
|
||||||
else if (screens.xl) drawerPercentage = bpoints.xl;
|
|
||||||
else if (screens.lg) drawerPercentage = bpoints.lg;
|
|
||||||
else if (screens.md) drawerPercentage = bpoints.md;
|
|
||||||
else if (screens.sm) drawerPercentage = bpoints.sm;
|
|
||||||
else if (screens.xs) drawerPercentage = bpoints.xs;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|||||||
@@ -1,203 +0,0 @@
|
|||||||
import { Button, Tag, Modal, Typography } from "antd";
|
|
||||||
import axios from "axios";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { FaWandMagicSparkles } from "react-icons/fa6";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext";
|
|
||||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
billEnterModal: selectBillEnterModal,
|
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
|
|
||||||
function BillEnterAiScan({
|
|
||||||
billEnterModal,
|
|
||||||
bodyshop,
|
|
||||||
pollingIntervalRef,
|
|
||||||
setPollingIntervalRef,
|
|
||||||
form,
|
|
||||||
fileInputRef,
|
|
||||||
scanLoading,
|
|
||||||
setScanLoading,
|
|
||||||
setIsAiScan
|
|
||||||
}) {
|
|
||||||
const notification = useNotification();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [showBetaModal, setShowBetaModal] = useState(false);
|
|
||||||
const BETA_ACCEPTANCE_KEY = "ai_scan_beta_acceptance";
|
|
||||||
const handleBetaAcceptance = () => {
|
|
||||||
localStorage.setItem(BETA_ACCEPTANCE_KEY, "true");
|
|
||||||
setShowBetaModal(false);
|
|
||||||
fileInputRef.current?.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkBetaAcceptance = () => {
|
|
||||||
const hasAccepted = localStorage.getItem(BETA_ACCEPTANCE_KEY);
|
|
||||||
if (hasAccepted) {
|
|
||||||
fileInputRef.current?.click();
|
|
||||||
} else {
|
|
||||||
setShowBetaModal(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Polling function for multipage PDF status
|
|
||||||
const pollJobStatus = async (textractJobId) => {
|
|
||||||
try {
|
|
||||||
const { data } = await axios.get(`/ai/bill-ocr/status/${textractJobId}`);
|
|
||||||
|
|
||||||
if (data.status === "COMPLETED") {
|
|
||||||
// Stop polling
|
|
||||||
if (pollingIntervalRef.current) {
|
|
||||||
clearInterval(pollingIntervalRef.current);
|
|
||||||
setPollingIntervalRef(null);
|
|
||||||
}
|
|
||||||
setScanLoading(false);
|
|
||||||
|
|
||||||
// Update form with the extracted data
|
|
||||||
if (data?.data?.billForm) {
|
|
||||||
form.setFieldsValue(data.data.billForm);
|
|
||||||
await form.validateFields(["billlines"], { recursive: true });
|
|
||||||
notification.success({
|
|
||||||
title: t("bills.labels.ai.scancomplete")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (data.status === "FAILED") {
|
|
||||||
// Stop polling on failure
|
|
||||||
if (pollingIntervalRef.current) {
|
|
||||||
clearInterval(pollingIntervalRef.current);
|
|
||||||
setPollingIntervalRef(null);
|
|
||||||
}
|
|
||||||
setScanLoading(false);
|
|
||||||
|
|
||||||
notification.error({
|
|
||||||
title: t("bills.labels.ai.scanfailed"),
|
|
||||||
description: data.error || ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// If status is IN_PROGRESS, continue polling
|
|
||||||
} catch (error) {
|
|
||||||
// Stop polling on error
|
|
||||||
if (pollingIntervalRef.current) {
|
|
||||||
clearInterval(pollingIntervalRef.current);
|
|
||||||
setPollingIntervalRef(null);
|
|
||||||
}
|
|
||||||
setScanLoading(false);
|
|
||||||
|
|
||||||
notification.error({
|
|
||||||
title: t("bills.labels.ai.scanfailed"),
|
|
||||||
description: error.response?.data?.message || error.message || "Failed to check scan status"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
type="file"
|
|
||||||
accept="image/*,application/pdf"
|
|
||||||
style={{ display: "none" }}
|
|
||||||
onChange={async (e) => {
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
if (file) {
|
|
||||||
setScanLoading(true);
|
|
||||||
setIsAiScan(true);
|
|
||||||
const formdata = new FormData();
|
|
||||||
formdata.append("billScan", file);
|
|
||||||
formdata.append("jobid", form.getFieldValue("jobid") || billEnterModal.context.job?.id);
|
|
||||||
formdata.append("bodyshopid", bodyshop.id);
|
|
||||||
formdata.append("partsorderid", billEnterModal.context.parts_order?.id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data, status } = await axios.post("/ai/bill-ocr", formdata);
|
|
||||||
|
|
||||||
// Add the scanned file to the upload field
|
|
||||||
const currentUploads = form.getFieldValue("upload") || [];
|
|
||||||
form.setFieldValue("upload", [
|
|
||||||
...currentUploads,
|
|
||||||
{
|
|
||||||
uid: `ai-scan-${Date.now()}`,
|
|
||||||
name: file.name,
|
|
||||||
originFileObj: file,
|
|
||||||
status: "done"
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
if (status === 202) {
|
|
||||||
// Multipage PDF - start polling
|
|
||||||
notification.info({
|
|
||||||
title: t("bills.labels.ai.scanstarted"),
|
|
||||||
description: t("bills.labels.ai.multipage")
|
|
||||||
});
|
|
||||||
|
|
||||||
//Workaround needed to bypass react-compiler error about manipulating refs in child components. Refactor may be needed in the future to clean this up.
|
|
||||||
setPollingIntervalRef(
|
|
||||||
setInterval(() => {
|
|
||||||
pollJobStatus(data.textractJobId);
|
|
||||||
}, 3000)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initial poll
|
|
||||||
pollJobStatus(data.textractJobId);
|
|
||||||
} else if (status === 200) {
|
|
||||||
// Single page - immediate response
|
|
||||||
setScanLoading(false);
|
|
||||||
|
|
||||||
form.setFieldsValue(data.data.billForm);
|
|
||||||
await form.validateFields(["billlines"], { recursive: true });
|
|
||||||
|
|
||||||
notification.success({
|
|
||||||
title: t("bills.labels.ai.scancomplete")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setScanLoading(false);
|
|
||||||
notification.error({
|
|
||||||
title: t("bills.labels.ai.scanfailed"),
|
|
||||||
description: error.response?.data?.message || error.message || t("bills.labels.ai.generic_failure")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Reset the input so the same file can be selected again
|
|
||||||
e.target.value = "";
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button onClick={checkBetaAcceptance} icon={<FaWandMagicSparkles />} loading={scanLoading} disabled={scanLoading}>
|
|
||||||
{scanLoading ? t("bills.labels.ai.processing") : t("bills.labels.ai.scan")}
|
|
||||||
<Tag color="red">{t("general.labels.beta")}</Tag>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
title={t("bills.labels.ai.disclaimer_title")}
|
|
||||||
open={showBetaModal}
|
|
||||||
onOk={handleBetaAcceptance}
|
|
||||||
onCancel={() => setShowBetaModal(false)}
|
|
||||||
okText={t("bills.labels.ai.accept_and_continue")}
|
|
||||||
cancelText={t("general.actions.cancel")}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
//This is explicitly not translated.
|
|
||||||
}
|
|
||||||
<Typography.Text>
|
|
||||||
This AI scanning feature is currently in <strong>beta</strong>. While it can accelerate data entry, you{" "}
|
|
||||||
<strong>must carefully review all extracted results</strong> for accuracy.
|
|
||||||
</Typography.Text>
|
|
||||||
<Typography.Text>The AI may make mistakes or miss information. Always verify:</Typography.Text>
|
|
||||||
<ul>
|
|
||||||
<li>All line items and quantities</li>
|
|
||||||
<li>Prices and totals</li>
|
|
||||||
<li>Part numbers and descriptions</li>
|
|
||||||
<li>Any other critical invoice details</li>
|
|
||||||
</ul>
|
|
||||||
<Typography.Text>
|
|
||||||
By continuing, you acknowledge that you will review and verify all AI-generated data before posting.
|
|
||||||
</Typography.Text>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export default connect(mapStateToProps, null)(BillEnterAiScan);
|
|
||||||
@@ -2,11 +2,10 @@ import { useApolloClient, useMutation } from "@apollo/client/react";
|
|||||||
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Checkbox, Form, Modal, Space } from "antd";
|
import { Button, Checkbox, Form, Modal, Space } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
|
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
|
||||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||||
@@ -22,12 +21,12 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
|
|||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import confirmDialog from "../../utils/asyncConfirm";
|
import confirmDialog from "../../utils/asyncConfirm";
|
||||||
import useLocalStorage from "../../utils/useLocalStorage";
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import BillEnterAiScan from "../bill-enter-ai-scan/bill-enter-ai-scan.component.jsx";
|
|
||||||
import BillFormContainer from "../bill-form/bill-form.container";
|
import BillFormContainer from "../bill-form/bill-form.container";
|
||||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||||
import { handleUpload as handleUploadToImageProxy } from "../documents-upload-imgproxy/documents-upload-imgproxy.utility";
|
|
||||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||||
|
import { handleUpload as handleUploadToImageProxy } from "../documents-upload-imgproxy/documents-upload-imgproxy.utility";
|
||||||
|
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
billEnterModal: selectBillEnterModal,
|
billEnterModal: selectBillEnterModal,
|
||||||
@@ -51,20 +50,15 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED);
|
const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED);
|
||||||
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
|
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [scanLoading, setScanLoading] = useState(false);
|
|
||||||
const [isAiScan, setIsAiScan] = useState(false);
|
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false);
|
const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false);
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
const fileInputRef = useRef(null);
|
|
||||||
const pollingIntervalRef = useRef(null);
|
|
||||||
const formTopRef = useRef(null);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { Enhanced_Payroll, Imgproxy, Bill_OCR_AI }
|
treatments: { Enhanced_Payroll, Imgproxy }
|
||||||
} = useTreatmentsWithConfig({
|
} = useTreatmentsWithConfig({
|
||||||
attributes: {},
|
attributes: {},
|
||||||
names: ["Enhanced_Payroll", "Imgproxy", "Bill_OCR_AI"],
|
names: ["Enhanced_Payroll", "Imgproxy"],
|
||||||
splitKey: bodyshop.imexshopid
|
splitKey: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,8 +113,6 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
create_ppc,
|
create_ppc,
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
original_actual_price,
|
original_actual_price,
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
confidence,
|
|
||||||
...restI
|
...restI
|
||||||
} = i;
|
} = i;
|
||||||
|
|
||||||
@@ -386,7 +378,6 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
vendorid: values.vendorid,
|
vendorid: values.vendorid,
|
||||||
billlines: []
|
billlines: []
|
||||||
});
|
});
|
||||||
setIsAiScan(false);
|
|
||||||
// form.resetFields();
|
// form.resetFields();
|
||||||
} else {
|
} else {
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
@@ -397,22 +388,10 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
const r = window.confirm(t("general.labels.cancel"));
|
const r = window.confirm(t("general.labels.cancel"));
|
||||||
if (r === true) {
|
if (r === true) {
|
||||||
// Clean up polling on cancel
|
|
||||||
if (pollingIntervalRef.current) {
|
|
||||||
clearInterval(pollingIntervalRef.current);
|
|
||||||
pollingIntervalRef.current = null;
|
|
||||||
}
|
|
||||||
setScanLoading(false);
|
|
||||||
setIsAiScan(false);
|
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//Workaround needed to bypass react-compiler error about manipulating refs in child components. Refactor may be needed in the future to clean this up.
|
|
||||||
const setPollingIntervalRef = (func) => {
|
|
||||||
pollingIntervalRef.current = func;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (enterAgain) form.submit();
|
if (enterAgain) form.submit();
|
||||||
}, [enterAgain, form]);
|
}, [enterAgain, form]);
|
||||||
@@ -422,44 +401,12 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
form.setFieldsValue(formValues);
|
form.setFieldsValue(formValues);
|
||||||
} else {
|
} else {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
// Clean up polling on modal close
|
|
||||||
if (pollingIntervalRef.current) {
|
|
||||||
clearInterval(pollingIntervalRef.current);
|
|
||||||
pollingIntervalRef.current = null;
|
|
||||||
}
|
|
||||||
setScanLoading(false);
|
|
||||||
setIsAiScan(false);
|
|
||||||
}
|
}
|
||||||
}, [billEnterModal.open, form, formValues]);
|
}, [billEnterModal.open, form, formValues]);
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (pollingIntervalRef.current) {
|
|
||||||
clearInterval(pollingIntervalRef.current);
|
|
||||||
pollingIntervalRef.current = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={t("bills.labels.new")}
|
||||||
<Space size="large">
|
|
||||||
{t("bills.labels.new")}
|
|
||||||
{Bill_OCR_AI.treatment === "on" && (
|
|
||||||
<BillEnterAiScan
|
|
||||||
fileInputRef={fileInputRef}
|
|
||||||
form={form}
|
|
||||||
pollingIntervalRef={pollingIntervalRef}
|
|
||||||
setPollingIntervalRef={setPollingIntervalRef}
|
|
||||||
scanLoading={scanLoading}
|
|
||||||
setScanLoading={setScanLoading}
|
|
||||||
setIsAiScan={setIsAiScan}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
width={"98%"}
|
width={"98%"}
|
||||||
open={billEnterModal.open}
|
open={billEnterModal.open}
|
||||||
okText={t("general.actions.save")}
|
okText={t("general.actions.save")}
|
||||||
@@ -500,25 +447,13 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
autoComplete={"off"}
|
autoComplete={"off"}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={form}
|
form={form}
|
||||||
onFinishFailed={(errorInfo) => {
|
onFinishFailed={() => {
|
||||||
setEnterAgain(false);
|
setEnterAgain(false);
|
||||||
// Scroll to the top of the form to show validation errors
|
|
||||||
if (errorInfo.errorFields && errorInfo.errorFields.length > 0) {
|
|
||||||
setTimeout(() => {
|
|
||||||
formTopRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div ref={formTopRef}>
|
<RbacWrapper action="bills:enter">
|
||||||
<RbacWrapper action="bills:enter">
|
<BillFormContainer form={form} disableInvNumber={billEnterModal.context.disableInvNumber} />
|
||||||
<BillFormContainer
|
</RbacWrapper>
|
||||||
form={form}
|
|
||||||
isAiScan={isAiScan}
|
|
||||||
disableInvNumber={billEnterModal.context.disableInvNumber}
|
|
||||||
/>
|
|
||||||
</RbacWrapper>
|
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Form, Input } from "antd";
|
import { Form, Input, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
@@ -109,14 +108,7 @@ export default function BillFormLinesExtended({ lineData, discount, form, respon
|
|||||||
<Form.Item noStyle name="billlineskeys">
|
<Form.Item noStyle name="billlineskeys">
|
||||||
<button onClick={() => console.log(form.getFieldsValue())}>form</button>
|
<button onClick={() => console.log(form.getFieldsValue())}>form</button>
|
||||||
<Input onChange={(e) => setSearch(e.target.value)} allowClear />
|
<Input onChange={(e) => setSearch(e.target.value)} allowClear />
|
||||||
<ResponsiveTable
|
<Table pagination={false} size="small" columns={columns} rowKey="id" dataSource={data} />
|
||||||
pagination={false}
|
|
||||||
size="small"
|
|
||||||
columns={columns}
|
|
||||||
mobileColumnKeys={["line_desc", "oem_partno", "part_type", "act_price"]}
|
|
||||||
rowKey="id"
|
|
||||||
dataSource={data}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,22 +99,20 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("billlines.fields.cost_center")} name={["billlineskeys", record.id, "cost_center"]}>
|
<Form.Item label={t("billlines.fields.cost_center")} name={["billlineskeys", record.id, "cost_center"]}>
|
||||||
<Select
|
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
||||||
showSearch
|
{bodyshopHasDmsKey(bodyshop)
|
||||||
style={{ minWidth: "3rem" }}
|
? CiecaSelect(true, false)
|
||||||
disabled={disabled}
|
: responsibilityCenters.costs.map((item) => <Select.Option key={item.name}>{item.name}</Select.Option>)}
|
||||||
options={
|
</Select>
|
||||||
bodyshopHasDmsKey(bodyshop)
|
|
||||||
? CiecaSelect(true, false)
|
|
||||||
: responsibilityCenters.costs.map((item) => ({ value: item.name, label: item.name }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("billlines.fields.location")} name={["billlineskeys", record.id, "location"]}>
|
<Form.Item label={t("billlines.fields.location")} name={["billlineskeys", record.id, "location"]}>
|
||||||
<Select
|
<Select disabled={disabled}>
|
||||||
disabled={disabled}
|
{bodyshop.md_parts_locations.map((loc, idx) => (
|
||||||
options={bodyshop.md_parts_locations.map((loc) => ({ value: loc, label: loc }))}
|
<Select.Option key={idx} value={loc}>
|
||||||
/>
|
{loc}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("billlines.fields.deductedfromlbr")}
|
label={t("billlines.fields.deductedfromlbr")}
|
||||||
@@ -138,10 +136,22 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
]}
|
]}
|
||||||
name={["billlineskeys", record.id, "lbr_adjustment", "mod_lbr_ty"]}
|
name={["billlineskeys", record.id, "lbr_adjustment", "mod_lbr_ty"]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select allowClear>
|
||||||
allowClear
|
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
||||||
options={CiecaSelect(false, true)}
|
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
||||||
/>
|
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
||||||
|
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
||||||
|
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
||||||
|
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
||||||
|
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
||||||
|
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
||||||
|
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
||||||
|
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
||||||
|
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
||||||
|
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
||||||
|
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
||||||
|
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.labels.adjustmentrate")}
|
label={t("jobs.labels.adjustmentrate")}
|
||||||
|
|||||||
@@ -8,15 +8,12 @@ import { MdOpenInNew } from "react-icons/md";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
|
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import { bodyshopHasDmsKey } from "../../utils/dmsUtils.js";
|
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
|
||||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
@@ -24,6 +21,8 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import BillFormLines from "./bill-form.lines.component";
|
import BillFormLines from "./bill-form.lines.component";
|
||||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
import { bodyshopHasDmsKey } from "../../utils/dmsUtils.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -44,14 +43,11 @@ export function BillFormComponent({
|
|||||||
loadOutstandingReturns,
|
loadOutstandingReturns,
|
||||||
loadInventory,
|
loadInventory,
|
||||||
preferredMake,
|
preferredMake,
|
||||||
disableInHouse,
|
disableInHouse
|
||||||
isAiScan
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const [discount, setDiscount] = useState(0);
|
const [discount, setDiscount] = useState(0);
|
||||||
const notification = useNotification();
|
|
||||||
const jobIdFormWatch = Form.useWatch("jobid", form);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { Extended_Bill_Posting, ClosingPeriod }
|
treatments: { Extended_Bill_Posting, ClosingPeriod }
|
||||||
@@ -127,23 +123,6 @@ export function BillFormComponent({
|
|||||||
bodyshop.inhousevendorid
|
bodyshop.inhousevendorid
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// When the jobid is set by AI scan, we need to reload the lines. This prevents having to hoist the apollo query.
|
|
||||||
if (jobIdFormWatch !== null) {
|
|
||||||
if (form.getFieldValue("jobid") !== null && form.getFieldValue("jobid") !== undefined) {
|
|
||||||
loadLines({ variables: { id: form.getFieldValue("jobid") } });
|
|
||||||
if (form.getFieldValue("vendorid") !== null && form.getFieldValue("vendorid") !== undefined) {
|
|
||||||
loadOutstandingReturns({
|
|
||||||
variables: {
|
|
||||||
jobId: form.getFieldValue("jobid"),
|
|
||||||
vendorId: form.getFieldValue("vendorid")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [jobIdFormWatch, form]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormFieldsChanged form={form} />
|
<FormFieldsChanged form={form} />
|
||||||
@@ -349,12 +328,13 @@ export function BillFormComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
{!billEdit && (
|
{!billEdit && (
|
||||||
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
|
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
|
||||||
<Select
|
<Select style={{ width: "10rem" }} disabled={disabled} allowClear>
|
||||||
style={{ width: "10rem" }}
|
{bodyshop.md_parts_locations.map((loc, idx) => (
|
||||||
disabled={disabled}
|
<Select.Option key={idx} value={loc}>
|
||||||
allowClear
|
{loc}
|
||||||
options={bodyshop.md_parts_locations.map((loc) => ({ value: loc, label: loc }))}
|
</Select.Option>
|
||||||
/>
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
@@ -394,15 +374,7 @@ export function BillFormComponent({
|
|||||||
]);
|
]);
|
||||||
let totals;
|
let totals;
|
||||||
if (!!values.total && !!values.billlines && values.billlines.length > 0) {
|
if (!!values.total && !!values.billlines && values.billlines.length > 0) {
|
||||||
try {
|
totals = CalculateBillTotal(values);
|
||||||
totals = CalculateBillTotal(values);
|
|
||||||
} catch (error) {
|
|
||||||
notification.error({
|
|
||||||
title: t("bills.errors.calculating_totals"),
|
|
||||||
message: error.message || t("bills.errors.calculating_totals_generic"),
|
|
||||||
key: "bill_totals_calculation_error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totals) {
|
if (totals) {
|
||||||
@@ -480,7 +452,6 @@ export function BillFormComponent({
|
|||||||
responsibilityCenters={responsibilityCenters}
|
responsibilityCenters={responsibilityCenters}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
billEdit={billEdit}
|
billEdit={billEdit}
|
||||||
isAiScan={isAiScan}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Divider titlePlacement="left" style={{ display: billEdit ? "none" : null }}>
|
<Divider titlePlacement="left" style={{ display: billEdit ? "none" : null }}>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse,isAiScan }) {
|
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse }) {
|
||||||
const {
|
const {
|
||||||
treatments: { Simple_Inventory }
|
treatments: { Simple_Inventory }
|
||||||
} = useTreatmentsWithConfig({
|
} = useTreatmentsWithConfig({
|
||||||
@@ -50,7 +50,6 @@ export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableI
|
|||||||
loadOutstandingReturns={loadOutstandingReturns}
|
loadOutstandingReturns={loadOutstandingReturns}
|
||||||
loadInventory={loadInventory}
|
loadInventory={loadInventory}
|
||||||
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
||||||
isAiScan={isAiScan}
|
|
||||||
/>
|
/>
|
||||||
{!billEdit && <BillCmdReturnsTableComponent form={form} returnLoading={returnLoading} returnData={returnData} />}
|
{!billEdit && <BillCmdReturnsTableComponent form={form} returnLoading={returnLoading} returnData={returnData} />}
|
||||||
{Simple_Inventory.treatment === "on" && (
|
{Simple_Inventory.treatment === "on" && (
|
||||||
|
|||||||
@@ -5,15 +5,14 @@ import { useRef } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectDarkMode } from "../../redux/application/application.selectors";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { selectDarkMode } from "../../redux/application/application.selectors";
|
||||||
import CiecaSelect from "../../utils/Ciecaselect";
|
import CiecaSelect from "../../utils/Ciecaselect";
|
||||||
import { bodyshopHasDmsKey } from "../../utils/dmsUtils.js";
|
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import ConfidenceDisplay from "./bill-form.lines.confidence.component.jsx";
|
import { bodyshopHasDmsKey } from "../../utils/dmsUtils.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -30,8 +29,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
discount,
|
discount,
|
||||||
form,
|
form,
|
||||||
responsibilityCenters,
|
responsibilityCenters,
|
||||||
billEdit,
|
billEdit
|
||||||
isAiScan
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||||
@@ -141,29 +139,6 @@ export function BillEnterModalLinesComponent({
|
|||||||
|
|
||||||
const columns = (remove) => {
|
const columns = (remove) => {
|
||||||
return [
|
return [
|
||||||
...(isAiScan
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
title: t("billlines.fields.confidence"),
|
|
||||||
dataIndex: "confidence",
|
|
||||||
editable: true,
|
|
||||||
width: "5rem",
|
|
||||||
formItemProps: (field) => ({
|
|
||||||
key: `${field.index}confidence`,
|
|
||||||
name: [field.name, "confidence"],
|
|
||||||
label: t("billlines.fields.confidence")
|
|
||||||
}),
|
|
||||||
formInput: (record) => {
|
|
||||||
const rowValue = getFieldValue(["billlines", record.name]);
|
|
||||||
return (
|
|
||||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
|
|
||||||
<ConfidenceDisplay rowValue={rowValue} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.jobline"),
|
title: t("billlines.fields.jobline"),
|
||||||
dataIndex: "joblineid",
|
dataIndex: "joblineid",
|
||||||
@@ -237,7 +212,6 @@ export function BillEnterModalLinesComponent({
|
|||||||
}),
|
}),
|
||||||
formInput: () => <Input.TextArea disabled={disabled} autoSize tabIndex={0} />
|
formInput: () => <Input.TextArea disabled={disabled} autoSize tabIndex={0} />
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.quantity"),
|
title: t("billlines.fields.quantity"),
|
||||||
dataIndex: "quantity",
|
dataIndex: "quantity",
|
||||||
@@ -276,16 +250,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
key: `${field.name}actual_price`,
|
key: `${field.name}actual_price`,
|
||||||
name: [field.name, "actual_price"],
|
name: [field.name, "actual_price"],
|
||||||
label: t("billlines.fields.actual_price"),
|
label: t("billlines.fields.actual_price"),
|
||||||
rules: [
|
rules: [{ required: true }]
|
||||||
{ required: true },
|
|
||||||
{
|
|
||||||
validator: (_, value) => {
|
|
||||||
return Math.abs(parseFloat(value)) < 0.01 ? Promise.reject() : Promise.resolve();
|
|
||||||
},
|
|
||||||
warningOnly: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
hasFeedback: true
|
|
||||||
}),
|
}),
|
||||||
formInput: (record, index) => (
|
formInput: (record, index) => (
|
||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
@@ -434,17 +399,11 @@ export function BillEnterModalLinesComponent({
|
|||||||
rules: [{ required: true }]
|
rules: [{ required: true }]
|
||||||
}),
|
}),
|
||||||
formInput: () => (
|
formInput: () => (
|
||||||
<Select
|
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled} tabIndex={0}>
|
||||||
showSearch
|
{bodyshopHasDmsKey(bodyshop)
|
||||||
style={{ minWidth: "3rem" }}
|
? CiecaSelect(true, false)
|
||||||
disabled={disabled}
|
: responsibilityCenters.costs.map((item) => <Select.Option key={item.name}>{item.name}</Select.Option>)}
|
||||||
tabIndex={0}
|
</Select>
|
||||||
options={
|
|
||||||
bodyshopHasDmsKey(bodyshop)
|
|
||||||
? CiecaSelect(true, false)
|
|
||||||
: responsibilityCenters.costs.map((item) => ({ value: item.name, label: item.name }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
...(billEdit
|
...(billEdit
|
||||||
@@ -460,11 +419,13 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "location"]
|
name: [field.name, "location"]
|
||||||
}),
|
}),
|
||||||
formInput: () => (
|
formInput: () => (
|
||||||
<Select
|
<Select disabled={disabled} tabIndex={0}>
|
||||||
disabled={disabled}
|
{bodyshop.md_parts_locations.map((loc, idx) => (
|
||||||
tabIndex={0}
|
<Select.Option key={idx} value={loc}>
|
||||||
options={bodyshop.md_parts_locations.map((loc) => ({ value: loc, label: loc }))}
|
{loc}
|
||||||
/>
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
]),
|
]),
|
||||||
@@ -505,10 +466,22 @@ export function BillEnterModalLinesComponent({
|
|||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
name={[record.name, "lbr_adjustment", "mod_lbr_ty"]}
|
name={[record.name, "lbr_adjustment", "mod_lbr_ty"]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select allowClear>
|
||||||
allowClear
|
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
||||||
options={CiecaSelect(false, true)}
|
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
||||||
/>
|
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
||||||
|
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
||||||
|
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
||||||
|
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
||||||
|
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
||||||
|
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
||||||
|
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
||||||
|
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
||||||
|
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
||||||
|
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
||||||
|
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
||||||
|
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{Enhanced_Payroll.treatment === "on" ? (
|
{Enhanced_Payroll.treatment === "on" ? (
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import { Progress, Space, Tag, Tooltip } from "antd";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
const parseConfidence = (confidenceStr) => {
|
|
||||||
if (!confidenceStr || typeof confidenceStr !== "string") return null;
|
|
||||||
|
|
||||||
const match = confidenceStr.match(/T([\d.]+)\s*-\s*O([\d.]+)\s*-\s*J([\d.]+)/);
|
|
||||||
if (!match) return null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
total: parseFloat(match[1]),
|
|
||||||
ocr: parseFloat(match[2]),
|
|
||||||
jobMatch: parseFloat(match[3])
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getConfidenceColor = (value) => {
|
|
||||||
if (value >= 80) return "green";
|
|
||||||
if (value >= 60) return "orange";
|
|
||||||
if (value >= 40) return "gold";
|
|
||||||
return "red";
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConfidenceDisplay = ({ rowValue: { confidence, actual_price, actual_cost } }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const parsed = parseConfidence(confidence);
|
|
||||||
const parsed_actual_price = parseFloat(actual_price);
|
|
||||||
const parsed_actual_cost = parseFloat(actual_cost);
|
|
||||||
if (!parsed) {
|
|
||||||
return <span style={{ color: "#959595", fontSize: "0.85em" }}>N/A</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { total, ocr, jobMatch } = parsed;
|
|
||||||
const color = getConfidenceColor(total);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
title={
|
|
||||||
<div style={{ padding: "4px 0" }}>
|
|
||||||
<div style={{ marginBottom: 8, fontWeight: 600 }}>{t("bills.labels.ai.confidence.breakdown")}</div>
|
|
||||||
<div style={{ marginBottom: 4 }}>
|
|
||||||
<strong>{t("bills.labels.ai.confidence.overall")}:</strong> {total.toFixed(1)}%
|
|
||||||
<Progress
|
|
||||||
percent={total}
|
|
||||||
size="small"
|
|
||||||
strokeColor={getConfidenceColor(total)}
|
|
||||||
showInfo={false}
|
|
||||||
style={{ marginTop: 2 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 4 }}>
|
|
||||||
<strong>{t("bills.labels.ai.confidence.ocr")}:</strong> {ocr.toFixed(1)}%
|
|
||||||
<Progress
|
|
||||||
percent={ocr}
|
|
||||||
size="small"
|
|
||||||
strokeColor={getConfidenceColor(ocr)}
|
|
||||||
showInfo={false}
|
|
||||||
style={{ marginTop: 2 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>{t("bills.labels.ai.confidence.match")}:</strong> {jobMatch.toFixed(1)}%
|
|
||||||
<Progress
|
|
||||||
percent={jobMatch}
|
|
||||||
size="small"
|
|
||||||
strokeColor={getConfidenceColor(jobMatch)}
|
|
||||||
showInfo={false}
|
|
||||||
style={{ marginTop: 2 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Space size="small">
|
|
||||||
{!parsed_actual_cost || !parsed_actual_price || parsed_actual_cost === 0 || parsed_actual_price === 0 ? (
|
|
||||||
<Tag color="red" style={{ margin: 0, cursor: "help", userSelect: "none" }}>
|
|
||||||
{t("bills.labels.ai.confidence.missing_data")}
|
|
||||||
</Tag>
|
|
||||||
) : null}
|
|
||||||
<Tag color={color} style={{ margin: 0, cursor: "help", userSelect: "none" }}>
|
|
||||||
{total.toFixed(0)}%
|
|
||||||
</Tag>
|
|
||||||
</Space>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConfidenceDisplay;
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Checkbox, Input, Space } from "antd";
|
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FaTasks } from "react-icons/fa";
|
import { FaTasks } from "react-icons/fa";
|
||||||
@@ -18,7 +18,6 @@ import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-
|
|||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
import LockerWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
import LockerWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
||||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -238,13 +237,12 @@ export function BillsListTableComponent({
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={billsQuery.loading}
|
loading={billsQuery.loading}
|
||||||
scroll={{
|
scroll={{
|
||||||
x: true // y: "50rem"
|
x: true // y: "50rem"
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["vendorname", "invoice_number", "date", "total", "actions"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={hasBillsAccess ? filteredBills : []}
|
dataSource={hasBillsAccess ? filteredBills : []}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
|
|||||||
import { useQuery } from "@apollo/client/react";
|
import { useQuery } from "@apollo/client/react";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Input } from "antd";
|
import { Input, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
@@ -80,7 +79,7 @@ export default function BillsVendorsList() {
|
|||||||
: (data && data.vendors) || [];
|
: (data && data.vendors) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
title={() => {
|
title={() => {
|
||||||
return (
|
return (
|
||||||
@@ -92,7 +91,6 @@ export default function BillsVendorsList() {
|
|||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["name", "cost_center", "city"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ export default function ChatTagRoComponent({ roOptions, loading, handleSearch, h
|
|||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
onSelect={handleInsertTag}
|
onSelect={handleInsertTag}
|
||||||
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
||||||
options={roOptions.map((item, idx) => ({
|
>
|
||||||
key: item.id || idx,
|
{roOptions.map((item, idx) => (
|
||||||
value: item.id || idx,
|
<Select.Option key={item.id || idx}>
|
||||||
label: ` ${item.ro_number || ""} | ${OwnerNameDisplayFunction(item)}`
|
{` ${item.ro_number || ""} | ${OwnerNameDisplayFunction(item)}`}
|
||||||
}))}
|
</Select.Option>
|
||||||
/>
|
))}
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
{loading ? <LoadingOutlined /> : null}
|
{loading ? <LoadingOutlined /> : null}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Card, Input } from "antd";
|
import { Card, Input, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
@@ -104,11 +103,10 @@ export default function ContractsCarsComponent({ loading, data, selectedCarId, h
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["status", "fleetnumber", "readiness", "year"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={filteredData}
|
dataSource={filteredData}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -309,13 +309,13 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select>
|
||||||
options={bodyshop.md_ins_cos.map((s) => ({
|
{bodyshop.md_ins_cos.map((s) => (
|
||||||
key: s.name,
|
<Select.Option key={s.name} value={s.name}>
|
||||||
value: s.name,
|
{s.name}
|
||||||
label: s.name
|
</Select.Option>
|
||||||
}))}
|
))}
|
||||||
/>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={"class"}
|
name={"class"}
|
||||||
@@ -327,13 +327,13 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select>
|
||||||
options={bodyshop.md_classes.map((s) => ({
|
{bodyshop.md_classes.map((s) => (
|
||||||
key: s,
|
<Select.Option key={s} value={s}>
|
||||||
value: s,
|
{s}
|
||||||
label: s
|
</Select.Option>
|
||||||
}))}
|
))}
|
||||||
/>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("contracts.labels.convertform.applycleanupcharge")}
|
label={t("contracts.labels.convertform.applycleanupcharge")}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Card, Input } from "antd";
|
import { Card, Input, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
@@ -128,11 +127,10 @@ export default function ContractsJobsComponent({ loading, data, selectedJob, han
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ placement: "top", defaultPageSize: pageLimit, defaultCurrent: defaultCurrent }}
|
pagination={{ placement: "top", defaultPageSize: pageLimit, defaultCurrent: defaultCurrent }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["ro_number", "owner", "status", "vehicle", "plate_no"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={filteredData}
|
dataSource={filteredData}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { useEffect, useState } from "react";
|
|||||||
import { Select } from "antd";
|
import { Select } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
const ContractStatusComponent = ({ value, onChange, ref }) => {
|
const ContractStatusComponent = ({ value, onChange, ref }) => {
|
||||||
const [option, setOption] = useState(value);
|
const [option, setOption] = useState(value);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -13,17 +15,11 @@ const ContractStatusComponent = ({ value, onChange, ref }) => {
|
|||||||
}, [value, option, onChange]);
|
}, [value, option, onChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select ref={ref} value={option} style={{ width: 100 }} onChange={setOption}>
|
||||||
ref={ref}
|
<Option value="contracts.status.new">{t("contracts.status.new")}</Option>
|
||||||
value={option}
|
<Option value="contracts.status.out">{t("contracts.status.out")}</Option>
|
||||||
style={{ width: 100 }}
|
<Option value="contracts.status.returned">{t("contracts.status.out")}</Option>
|
||||||
onChange={setOption}
|
</Select>
|
||||||
options={[
|
|
||||||
{ value: "contracts.status.new", label: t("contracts.status.new") },
|
|
||||||
{ value: "contracts.status.out", label: t("contracts.status.out") },
|
|
||||||
{ value: "contracts.status.returned", label: t("contracts.status.out") }
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useLazyQuery } from "@apollo/client/react";
|
import { useLazyQuery } from "@apollo/client/react";
|
||||||
import { Button, Form, Modal } from "antd";
|
import { Button, Form, Modal, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -65,7 +64,7 @@ export function ContractsFindModalContainer({ contractFinderModal, toggleModalVi
|
|||||||
{t("general.labels.search")}
|
{t("general.labels.search")}
|
||||||
</Button>
|
</Button>
|
||||||
{error && <AlertComponent type="error" title={JSON.stringify(error)} />}
|
{error && <AlertComponent type="error" title={JSON.stringify(error)} />}
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
@@ -125,7 +124,6 @@ export function ContractsFindModalContainer({ contractFinderModal, toggleModalVi
|
|||||||
render: (text, record) => <DateTimeFormatter>{record.actualreturn}</DateTimeFormatter>
|
render: (text, record) => <DateTimeFormatter>{record.actualreturn}</DateTimeFormatter>
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
mobileColumnKeys={["agreementnumber", "job.ro_number", "driver_ln", "status"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data?.cccontracts}
|
dataSource={data?.cccontracts}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Input, Space, Typography } from "antd";
|
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -14,7 +14,6 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { pageLimit } from "../../utils/config";
|
import { pageLimit } from "../../utils/config";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -171,14 +170,13 @@ export function ContractsList({ bodyshop, loading, contracts, refetch, total, se
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ContractsFindModalContainer />
|
<ContractsFindModalContainer />
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
scroll={{
|
scroll={{
|
||||||
x: "50%" //y: "40rem"
|
x: "50%" //y: "40rem"
|
||||||
}}
|
}}
|
||||||
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(page || 1, 10), total: total }}
|
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(page || 1, 10), total: total }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["agreementnumber", "driver_ln", "status", "scheduledreturn"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={contracts}
|
dataSource={contracts}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Card } from "antd";
|
import { Card, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
@@ -74,11 +73,10 @@ export default function CourtesyCarContractListComponent({ contracts, totalContr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={t("menus.header.courtesycars-contracts")}>
|
<Card title={t("menus.header.courtesycars-contracts")}>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(page || 1), total: totalContracts }}
|
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(page || 1), total: totalContracts }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["agreementnumber", "driver_ln", "status", "job.ro_number"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={contracts}
|
dataSource={contracts}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Select } from "antd";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
const CourtesyCarReadinessComponent = ({ value, onChange, ref }) => {
|
const CourtesyCarReadinessComponent = ({ value, onChange, ref }) => {
|
||||||
const [option, setOption] = useState(value);
|
const [option, setOption] = useState(value);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -21,11 +23,10 @@ const CourtesyCarReadinessComponent = ({ value, onChange, ref }) => {
|
|||||||
width: 100
|
width: 100
|
||||||
}}
|
}}
|
||||||
onChange={setOption}
|
onChange={setOption}
|
||||||
options={[
|
>
|
||||||
{ value: "courtesycars.readiness.ready", label: t("courtesycars.readiness.ready") },
|
<Option value="courtesycars.readiness.ready">{t("courtesycars.readiness.ready")}</Option>
|
||||||
{ value: "courtesycars.readiness.notready", label: t("courtesycars.readiness.notready") }
|
<Option value="courtesycars.readiness.notready">{t("courtesycars.readiness.notready")}</Option>
|
||||||
]}
|
</Select>
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CourtesyCarReadinessComponent;
|
export default CourtesyCarReadinessComponent;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { useEffect, useState } from "react";
|
|||||||
import { Select } from "antd";
|
import { Select } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
const CourtesyCarStatusComponent = ({ value, onChange, ref }) => {
|
const CourtesyCarStatusComponent = ({ value, onChange, ref }) => {
|
||||||
const [option, setOption] = useState(value);
|
const [option, setOption] = useState(value);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -20,15 +22,14 @@ const CourtesyCarStatusComponent = ({ value, onChange, ref }) => {
|
|||||||
width: 100
|
width: 100
|
||||||
}}
|
}}
|
||||||
onChange={setOption}
|
onChange={setOption}
|
||||||
options={[
|
>
|
||||||
{ value: "courtesycars.status.in", label: t("courtesycars.status.in") },
|
<Option value="courtesycars.status.in">{t("courtesycars.status.in")}</Option>
|
||||||
{ value: "courtesycars.status.inservice", label: t("courtesycars.status.inservice") },
|
<Option value="courtesycars.status.inservice">{t("courtesycars.status.inservice")}</Option>
|
||||||
{ value: "courtesycars.status.out", label: t("courtesycars.status.out") },
|
<Option value="courtesycars.status.out">{t("courtesycars.status.out")}</Option>
|
||||||
{ value: "courtesycars.status.sold", label: t("courtesycars.status.sold") },
|
<Option value="courtesycars.status.sold">{t("courtesycars.status.sold")}</Option>
|
||||||
{ value: "courtesycars.status.leasereturn", label: t("courtesycars.status.leasereturn") },
|
<Option value="courtesycars.status.leasereturn">{t("courtesycars.status.leasereturn")}</Option>
|
||||||
{ value: "courtesycars.status.unavailable", label: t("courtesycars.status.unavailable") }
|
<Option value="courtesycars.status.unavailable">{t("courtesycars.status.unavailable")}</Option>
|
||||||
]}
|
</Select>
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CourtesyCarStatusComponent;
|
export default CourtesyCarStatusComponent;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SyncOutlined, WarningFilled } from "@ant-design/icons";
|
import { SyncOutlined, WarningFilled } from "@ant-design/icons";
|
||||||
import { Button, Card, Dropdown, Input, Space, Tooltip } from "antd";
|
import { Button, Card, Dropdown, Input, Space, Table, Tooltip } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -276,11 +275,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["status", "fleetnumber", "vin", "readiness"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={tableData}
|
dataSource={tableData}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card } from "antd";
|
import { Button, Card, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -87,11 +86,10 @@ export default function CsiResponseListPaginated({ refetch, loading, responses,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card extra={<Button onClick={() => refetch()} icon={<SyncOutlined />} />}>
|
<Card extra={<Button onClick={() => refetch()} icon={<SyncOutlined />} />}>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(state.page || 1), total: total }}
|
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(state.page || 1), total: total }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["ro_number", "owner_name", "completedon"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={responses}
|
dataSource={responses}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Card, Tag } from "antd";
|
import { Card, Table, Tag } from "antd";
|
||||||
import ResponsiveTable from "../../responsive-table/responsive-table.component";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -183,11 +182,10 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card style={{ marginTop: "5px" }} type="inner" title={t("job_lifecycle.titles.top_durations")}>
|
<Card style={{ marginTop: "5px" }} type="inner" title={t("job_lifecycle.titles.top_durations")}>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
size="small"
|
size="small"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["status", "humanReadable", "averageHumanReadable", "statusCount"]}
|
|
||||||
rowKey={(record) => record.status}
|
rowKey={(record) => record.status}
|
||||||
dataSource={lifecycleData.summations.sort((a, b) => b.value - a.value).slice(0, 3)}
|
dataSource={lifecycleData.summations.sort((a, b) => b.value - a.value).slice(0, 3)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, Input, Space, Typography } from "antd";
|
import { Card, Input, Space, Table, Typography } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -7,7 +7,6 @@ import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
|||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import DashboardRefreshRequired from "../refresh-required.component";
|
import DashboardRefreshRequired from "../refresh-required.component";
|
||||||
import { pageLimit } from "../../../utils/config";
|
import { pageLimit } from "../../../utils/config";
|
||||||
import ResponsiveTable from "../../responsive-table/responsive-table.component.jsx";
|
|
||||||
|
|
||||||
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -104,33 +103,31 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
|||||||
{...cardProps}
|
{...cardProps}
|
||||||
>
|
>
|
||||||
<LoadingSkeleton loading={loading}>
|
<LoadingSkeleton loading={loading}>
|
||||||
<div style={{ height: "100%", minHeight: 0, width: "100%", overflow: "auto" }}>
|
<div style={{ height: "100%" }}>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
size="small"
|
|
||||||
tableLayout="fixed"
|
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
scroll={{ x: "max-content" }}
|
scroll={{ x: true, y: "calc(100% - 4em)" }}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
style={{ width: "100%" }}
|
style={{ height: "100%" }}
|
||||||
dataSource={filteredData}
|
dataSource={filteredData}
|
||||||
summary={() => (
|
summary={() => (
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
|
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
{Dinero(costingData?.allSummaryData && costingData.allSummaryData.totalSales).toFormat()}
|
{Dinero(costingData?.allSummaryData && costingData.allSummaryData.totalSales).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
{Dinero(costingData?.allSummaryData && costingData.allSummaryData.totalCost).toFormat()}
|
{Dinero(costingData?.allSummaryData && costingData.allSummaryData.totalCost).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
{Dinero(costingData?.allSummaryData && costingData.allSummaryData.gpdollars).toFormat()}
|
{Dinero(costingData?.allSummaryData && costingData.allSummaryData.gpdollars).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell></ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell></Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -363,7 +363,6 @@ export default function DashboardScheduledDeliveryToday({ data, ...cardProps })
|
|||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={isTvModeScheduledDelivery ? tvColumns : columns}
|
columns={isTvModeScheduledDelivery ? tvColumns : columns}
|
||||||
// mobileColumnKeys={["ro_number", "owner", "status", "vehicle"]}
|
|
||||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
style={{ height: "85%" }}
|
style={{ height: "85%" }}
|
||||||
|
|||||||
@@ -368,7 +368,6 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={isTvModeScheduledIn ? tvColumns : columns}
|
columns={isTvModeScheduledIn ? tvColumns : columns}
|
||||||
mobileColumnKeys={["ro_number", "owner", "vehicle", "start"]}
|
|
||||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
style={{ height: "85%" }}
|
style={{ height: "85%" }}
|
||||||
|
|||||||
@@ -363,7 +363,6 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={isTvModeScheduledOut ? tvColumns : columns}
|
columns={isTvModeScheduledOut ? tvColumns : columns}
|
||||||
// mobileColumnKeys={["ro_number", "owner", "status", "vehicle"]}
|
|
||||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
style={{ height: "85%" }}
|
style={{ height: "85%" }}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Form, Input } from "antd";
|
import { Button, Card, Form, Input, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -79,7 +78,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
|||||||
dataIndex: "Lines",
|
dataIndex: "Lines",
|
||||||
key: "Lines",
|
key: "Lines",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ResponsiveTable style={{ tableLayout: "auto", width: "100%" }}>
|
<table style={{ tableLayout: "auto", width: "100%" }}>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{t("bills.fields.invoice_number")}</th>
|
<th>{t("bills.fields.invoice_number")}</th>
|
||||||
<th>{t("bodyshop.fields.dms.dms_acctnumber")}</th>
|
<th>{t("bodyshop.fields.dms.dms_acctnumber")}</th>
|
||||||
@@ -92,7 +91,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
|||||||
<td>{l.Amount}</td>
|
<td>{l.Amount}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</ResponsiveTable>
|
</table>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -116,10 +115,9 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["status", "reference", "Lines"]}
|
|
||||||
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
|
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
|
||||||
dataSource={allocationsSummary}
|
dataSource={allocationsSummary}
|
||||||
locale={{ emptyText: t("dms.labels.refreshallocations") }}
|
locale={{ emptyText: t("dms.labels.refreshallocations") }}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Alert, Button, Card, Typography } from "antd";
|
import { Alert, Button, Card, Table, Typography } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -117,10 +116,9 @@ export function DmsAllocationsSummary({ mode, socket, bodyshop, jobId, title, on
|
|||||||
<Alert type="warning" title={t("jobs.labels.dms.disablebillwip")} />
|
<Alert type="warning" title={t("jobs.labels.dms.disablebillwip")} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ResponsiveTable
|
<Table
|
||||||
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["center", "sale", "cost", "sale_dms_acctnumber"]}
|
|
||||||
rowKey="center"
|
rowKey="center"
|
||||||
dataSource={allocationsSummary}
|
dataSource={allocationsSummary}
|
||||||
locale={{ emptyText: t("dms.labels.refreshallocations") }}
|
locale={{ emptyText: t("dms.labels.refreshallocations") }}
|
||||||
@@ -137,17 +135,15 @@ export function DmsAllocationsSummary({ mode, socket, bodyshop, jobId, title, on
|
|||||||
const hasNonZeroSaleTotal = totals.totalSale.getAmount() !== 0;
|
const hasNonZeroSaleTotal = totals.totalSale.getAmount() !== 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
|
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>{hasNonZeroSaleTotal ? totals.totalSale.toFormat() : null}</Table.Summary.Cell>
|
||||||
{hasNonZeroSaleTotal ? totals.totalSale.toFormat() : null}
|
<Table.Summary.Cell />
|
||||||
</ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell />
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
<ResponsiveTable.Summary.Cell />
|
</Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell />
|
|
||||||
</ResponsiveTable.Summary.Row>
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Alert, Button, Card, Tabs, Typography } from "antd";
|
import { Alert, Button, Card, Table, Tabs, Typography } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -262,10 +261,9 @@ export function RrAllocationsSummary({ socket, bodyshop, jobId, title, onAllocat
|
|||||||
into taxable / non-taxable segments.
|
into taxable / non-taxable segments.
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
|
|
||||||
<ResponsiveTable
|
<Table
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={roggColumns}
|
columns={roggColumns}
|
||||||
mobileColumnKeys={["jobNo", "opCode", "breakOut", "itemType"]}
|
|
||||||
rowKey="key"
|
rowKey="key"
|
||||||
dataSource={roggRows}
|
dataSource={roggRows}
|
||||||
locale={{ emptyText: "No ROGOG lines would be generated." }}
|
locale={{ emptyText: "No ROGOG lines would be generated." }}
|
||||||
@@ -288,23 +286,19 @@ export function RrAllocationsSummary({ socket, bodyshop, jobId, title, onAllocat
|
|||||||
const hasCostTotal = Number(roggTotals.totalDlrCost) !== 0;
|
const hasCostTotal = Number(roggTotals.totalDlrCost) !== 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell index={0}>
|
<Table.Summary.Cell index={0}>
|
||||||
<Typography.Title level={5}>{t("general.labels.totals")}</Typography.Title>
|
<Typography.Title level={5}>{t("general.labels.totals")}</Typography.Title>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell index={1} />
|
<Table.Summary.Cell index={1} />
|
||||||
<ResponsiveTable.Summary.Cell index={2} />
|
<Table.Summary.Cell index={2} />
|
||||||
<ResponsiveTable.Summary.Cell index={3} />
|
<Table.Summary.Cell index={3} />
|
||||||
<ResponsiveTable.Summary.Cell index={4} />
|
<Table.Summary.Cell index={4} />
|
||||||
<ResponsiveTable.Summary.Cell index={5} />
|
<Table.Summary.Cell index={5} />
|
||||||
<ResponsiveTable.Summary.Cell index={6} />
|
<Table.Summary.Cell index={6} />
|
||||||
<ResponsiveTable.Summary.Cell index={7}>
|
<Table.Summary.Cell index={7}>{hasCustTotal ? roggTotals.totalCustPrice : null}</Table.Summary.Cell>
|
||||||
{hasCustTotal ? roggTotals.totalCustPrice : null}
|
<Table.Summary.Cell index={8}>{hasCostTotal ? roggTotals.totalDlrCost : null}</Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell index={8}>
|
|
||||||
{hasCostTotal ? roggTotals.totalDlrCost : null}
|
|
||||||
</ResponsiveTable.Summary.Cell>
|
|
||||||
</ResponsiveTable.Summary.Row>
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -319,10 +313,9 @@ export function RrAllocationsSummary({ socket, bodyshop, jobId, title, onAllocat
|
|||||||
<Typography.Paragraph type="secondary" style={{ marginBottom: 8 }}>
|
<Typography.Paragraph type="secondary" style={{ marginBottom: 8 }}>
|
||||||
This mirrors the shell that would be sent for ROLABOR when all financials are carried in GOG.
|
This mirrors the shell that would be sent for ROLABOR when all financials are carried in GOG.
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={rolaborColumns}
|
columns={rolaborColumns}
|
||||||
mobileColumnKeys={["jobNo", "opCode", "breakOut", "itemType"]}
|
|
||||||
rowKey="key"
|
rowKey="key"
|
||||||
dataSource={rolaborRows}
|
dataSource={rolaborRows}
|
||||||
locale={{ emptyText: "No ROLABOR lines would be generated." }}
|
locale={{ emptyText: "No ROLABOR lines would be generated." }}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useLazyQuery } from "@apollo/client/react";
|
import { useLazyQuery } from "@apollo/client/react";
|
||||||
import { Button, Input, Modal } from "antd";
|
import { Button, Input, Modal, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -61,7 +60,7 @@ export function DmsCdkVehicles({ form, job }) {
|
|||||||
okButtonProps={{ disabled: !selectedModel }}
|
okButtonProps={{ disabled: !selectedModel }}
|
||||||
>
|
>
|
||||||
{error && <AlertComponent title={error.message} type="error" />}
|
{error && <AlertComponent title={error.message} type="error" />}
|
||||||
<ResponsiveTable
|
<Table
|
||||||
title={() => (
|
title={() => (
|
||||||
<Input.Search
|
<Input.Search
|
||||||
onSearch={(val) => callSearch({ variables: { search: val } })}
|
onSearch={(val) => callSearch({ variables: { search: val } })}
|
||||||
@@ -70,7 +69,6 @@ export function DmsCdkVehicles({ form, job }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["make", "model", "makecode", "modelcode"]}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data ? data.search_dms_vehicles : []}
|
dataSource={data ? data.search_dms_vehicles : []}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button, Checkbox, Col } from "antd";
|
import { Button, Checkbox, Col, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
@@ -73,7 +72,7 @@ export default function CDKCustomerSelector({ bodyshop, socket }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
title={() => (
|
title={() => (
|
||||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||||
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
|
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
|
||||||
@@ -87,7 +86,6 @@ export default function CDKCustomerSelector({ bodyshop, socket }) {
|
|||||||
)}
|
)}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["id", "vinOwner", "name1", "address"]}
|
|
||||||
rowKey={rowKey}
|
rowKey={rowKey}
|
||||||
dataSource={customerList}
|
dataSource={customerList}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button, Checkbox, Col } from "antd";
|
import { Button, Checkbox, Col, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
@@ -79,7 +78,7 @@ export default function FortellisCustomerSelector({ bodyshop, jobid, socket }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
title={() => (
|
title={() => (
|
||||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||||
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
|
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
|
||||||
@@ -93,7 +92,6 @@ export default function FortellisCustomerSelector({ bodyshop, jobid, socket }) {
|
|||||||
)}
|
)}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["id", "vinOwner", "firstName", "address"]}
|
|
||||||
rowKey={(r) => r.customerId}
|
rowKey={(r) => r.customerId}
|
||||||
dataSource={customerList}
|
dataSource={customerList}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button, Col } from "antd";
|
import { Button, Col, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
@@ -67,7 +66,7 @@ export default function PBSCustomerSelector({ bodyshop, socket }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
title={() => (
|
title={() => (
|
||||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||||
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
|
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
|
||||||
@@ -81,7 +80,6 @@ export default function PBSCustomerSelector({ bodyshop, socket }) {
|
|||||||
)}
|
)}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["ContactId", "name1", "address"]}
|
|
||||||
rowKey={(r) => r.ContactId}
|
rowKey={(r) => r.ContactId}
|
||||||
dataSource={customerList}
|
dataSource={customerList}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Alert, Button, Checkbox, message, Modal, Space } from "antd";
|
import { Alert, Button, Checkbox, message, Modal, Space, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
@@ -73,14 +72,14 @@ export default function RRCustomerSelector({
|
|||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
const handleRrSelectCustomer = (list) => {
|
const handleRrSelectCustomer = (list) => {
|
||||||
const normalized = normalizeRrList(list);
|
const normalized = normalizeRrList(list);
|
||||||
|
|
||||||
// If list is empty, it means early RO exists and customer selection should be skipped
|
// If list is empty, it means early RO exists and customer selection should be skipped
|
||||||
// Don't open the modal in this case
|
// Don't open the modal in this case
|
||||||
if (normalized.length === 0) {
|
if (normalized.length === 0) {
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setCustomerList(normalized);
|
setCustomerList(normalized);
|
||||||
const firstOwner = normalized.find((r) => r.vinOwner)?.custNo;
|
const firstOwner = normalized.find((r) => r.vinOwner)?.custNo;
|
||||||
@@ -196,8 +195,8 @@ export default function RRCustomerSelector({
|
|||||||
description={
|
description={
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||||
<div>
|
<div>
|
||||||
We created the Repair Order. Please validate the totals and taxes in the DMS system. When done, click{" "}
|
We created the Repair Order. Please validate the totals and taxes in the DMS system. When done,
|
||||||
<strong>Finished</strong> to finalize and mark this export as complete.
|
click <strong>Finished</strong> to finalize and mark this export as complete.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Space>
|
<Space>
|
||||||
@@ -216,8 +215,14 @@ export default function RRCustomerSelector({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onCancel={handleClose} footer={null} width={800} title={t("dms.selectCustomer")}>
|
<Modal
|
||||||
<ResponsiveTable
|
open={open}
|
||||||
|
onCancel={handleClose}
|
||||||
|
footer={null}
|
||||||
|
width={800}
|
||||||
|
title={t("dms.selectCustomer")}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
title={() => (
|
title={() => (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||||
{/* Open RO limit banner */}
|
{/* Open RO limit banner */}
|
||||||
@@ -299,7 +304,6 @@ export default function RRCustomerSelector({
|
|||||||
)}
|
)}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["custNo", "vinOwner", "name", "address"]}
|
|
||||||
rowKey={(r) => r.custNo}
|
rowKey={(r) => r.custNo}
|
||||||
dataSource={customerList}
|
dataSource={customerList}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import dayjs from "../../utils/day";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectDarkMode } from "../../redux/application/application.selectors.js";
|
import { selectDarkMode } from "../../redux/application/application.selectors.js";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
isDarkMode: selectDarkMode
|
isDarkMode: selectDarkMode
|
||||||
@@ -20,35 +19,25 @@ export function DmsLogEvents({
|
|||||||
detailsNonce,
|
detailsNonce,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
colorizeJson = false,
|
colorizeJson = false,
|
||||||
showDetails = true,
|
showDetails = true
|
||||||
allowXmlPayload = true
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
|
||||||
const [openSet, setOpenSet] = useState(() => new Set());
|
const [openSet, setOpenSet] = useState(() => new Set());
|
||||||
const [copiedKey, setCopiedKey] = useState(null);
|
|
||||||
|
|
||||||
// Inject JSON highlight styles once (only when colorize is enabled)
|
// Inject JSON highlight styles once (only when colorize is enabled)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!colorizeJson) return;
|
if (!colorizeJson) return;
|
||||||
if (typeof document === "undefined") return;
|
if (typeof document === "undefined") return;
|
||||||
let style = document.getElementById("json-highlight-styles");
|
if (document.getElementById("json-highlight-styles")) return;
|
||||||
if (!style) {
|
const style = document.createElement("style");
|
||||||
style = document.createElement("style");
|
style.id = "json-highlight-styles";
|
||||||
style.id = "json-highlight-styles";
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
|
||||||
style.textContent = `
|
style.textContent = `
|
||||||
.json-key { color: #fa8c16; }
|
.json-key { color: #fa8c16; }
|
||||||
.json-string { color: #52c41a; }
|
.json-string { color: #52c41a; }
|
||||||
.json-number { color: #722ed1; }
|
.json-number { color: #722ed1; }
|
||||||
.json-boolean { color: #1890ff; }
|
.json-boolean { color: #1890ff; }
|
||||||
.json-null { color: #faad14; }
|
.json-null { color: #faad14; }
|
||||||
.xml-tag { color: #1677ff; }
|
|
||||||
.xml-attr { color: #d46b08; }
|
|
||||||
.xml-value { color: #389e0d; }
|
|
||||||
.xml-decl { color: #7c3aed; }
|
|
||||||
.xml-comment { color: #8c8c8c; }
|
|
||||||
`;
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
}, [colorizeJson]);
|
}, [colorizeJson]);
|
||||||
|
|
||||||
// Trim openSet if logs shrink
|
// Trim openSet if logs shrink
|
||||||
@@ -76,13 +65,6 @@ export function DmsLogEvents({
|
|||||||
// Only treat meta as "present" when we are allowed to show details
|
// Only treat meta as "present" when we are allowed to show details
|
||||||
const hasMeta = !isEmpty(meta) && showDetails;
|
const hasMeta = !isEmpty(meta) && showDetails;
|
||||||
const isOpen = hasMeta && openSet.has(idx);
|
const isOpen = hasMeta && openSet.has(idx);
|
||||||
const xml = hasMeta && allowXmlPayload ? extractXmlFromMeta(meta) : { request: null, response: null };
|
|
||||||
const hasRequestXml = !!xml.request;
|
|
||||||
const hasResponseXml = !!xml.response;
|
|
||||||
const copyPayload = hasMeta ? getCopyPayload(meta) : null;
|
|
||||||
const copyPayloadKey = `copy-${idx}`;
|
|
||||||
const copyReqKey = `copy-req-${idx}`;
|
|
||||||
const copyResKey = `copy-res-${idx}`;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: idx,
|
key: idx,
|
||||||
@@ -110,42 +92,10 @@ export function DmsLogEvents({
|
|||||||
return next;
|
return next;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
style={{ cursor: "pointer", userSelect: "none", fontSize: 11 }}
|
style={{ cursor: "pointer", userSelect: "none" }}
|
||||||
>
|
>
|
||||||
{isOpen ? t("dms.labels.hide_details") : t("dms.labels.details")}
|
{isOpen ? "Hide details" : "Details"}
|
||||||
</a>
|
</a>
|
||||||
<Divider orientation="vertical" />
|
|
||||||
<a
|
|
||||||
role="button"
|
|
||||||
onClick={() => handleCopyAction(copyPayloadKey, copyPayload, setCopiedKey)}
|
|
||||||
style={{ cursor: "pointer", userSelect: "none", fontSize: 11 }}
|
|
||||||
>
|
|
||||||
{copiedKey === copyPayloadKey ? t("dms.labels.copied") : t("dms.labels.copy")}
|
|
||||||
</a>
|
|
||||||
{hasRequestXml && (
|
|
||||||
<>
|
|
||||||
<Divider orientation="vertical" />
|
|
||||||
<a
|
|
||||||
role="button"
|
|
||||||
onClick={() => handleCopyAction(copyReqKey, xml.request, setCopiedKey)}
|
|
||||||
style={{ cursor: "pointer", userSelect: "none", fontSize: 11 }}
|
|
||||||
>
|
|
||||||
{copiedKey === copyReqKey ? t("dms.labels.copied") : t("dms.labels.copy_request")}
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{hasResponseXml && (
|
|
||||||
<>
|
|
||||||
<Divider orientation="vertical" />
|
|
||||||
<a
|
|
||||||
role="button"
|
|
||||||
onClick={() => handleCopyAction(copyResKey, xml.response, setCopiedKey)}
|
|
||||||
style={{ cursor: "pointer", userSelect: "none", fontSize: 11 }}
|
|
||||||
>
|
|
||||||
{copiedKey === copyResKey ? t("dms.labels.copied") : t("dms.labels.copy_response")}
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
@@ -153,30 +103,14 @@ export function DmsLogEvents({
|
|||||||
{/* Row 2: details body (only when open) */}
|
{/* Row 2: details body (only when open) */}
|
||||||
{hasMeta && isOpen && (
|
{hasMeta && isOpen && (
|
||||||
<div style={{ marginLeft: 6 }}>
|
<div style={{ marginLeft: 6 }}>
|
||||||
<JsonBlock isDarkMode={isDarkMode} data={removeXmlFromMeta(meta)} colorize={colorizeJson} />
|
<JsonBlock isDarkMode={isDarkMode} data={meta} colorize={colorizeJson} />
|
||||||
{hasRequestXml && (
|
|
||||||
<XmlBlock
|
|
||||||
isDarkMode={isDarkMode}
|
|
||||||
title={t("dms.labels.request_xml")}
|
|
||||||
xmlText={xml.request}
|
|
||||||
colorize={colorizeJson}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{hasResponseXml && (
|
|
||||||
<XmlBlock
|
|
||||||
isDarkMode={isDarkMode}
|
|
||||||
title={t("dms.labels.response_xml")}
|
|
||||||
xmlText={xml.response}
|
|
||||||
colorize={colorizeJson}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
[logs, openSet, colorizeJson, copiedKey, isDarkMode, showDetails, allowXmlPayload, t]
|
[logs, openSet, colorizeJson, isDarkMode, showDetails]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Timeline reverse items={items} />;
|
return <Timeline reverse items={items} />;
|
||||||
@@ -245,121 +179,6 @@ const safeStringify = (obj, spaces = 2) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get request/response XML from various Reynolds log meta shapes.
|
|
||||||
* @param meta
|
|
||||||
* @returns {{request: string|null, response: string|null}}
|
|
||||||
*/
|
|
||||||
const extractXmlFromMeta = (meta) => {
|
|
||||||
const request =
|
|
||||||
firstString(meta?.requestXml) ||
|
|
||||||
firstString(meta?.xml?.request) ||
|
|
||||||
firstString(meta?.response?.xml?.request) ||
|
|
||||||
firstString(meta?.response?.requestXml);
|
|
||||||
|
|
||||||
const response =
|
|
||||||
firstString(meta?.responseXml) || firstString(meta?.xml?.response) || firstString(meta?.response?.xml?.response);
|
|
||||||
|
|
||||||
return { request, response };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the value to copy when clicking the "Copy" action.
|
|
||||||
* @param meta
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
const getCopyPayload = (meta) => {
|
|
||||||
if (meta?.payload != null) return meta.payload;
|
|
||||||
return meta;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove bulky XML fields from object shown in JSON block (XML is rendered separately).
|
|
||||||
* @param meta
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
const removeXmlFromMeta = (meta) => {
|
|
||||||
if (meta == null || typeof meta !== "object") return meta;
|
|
||||||
const cloned = safeClone(meta);
|
|
||||||
if (cloned == null || typeof cloned !== "object") return meta;
|
|
||||||
|
|
||||||
if (typeof cloned.requestXml === "string") delete cloned.requestXml;
|
|
||||||
if (typeof cloned.responseXml === "string") delete cloned.responseXml;
|
|
||||||
|
|
||||||
if (cloned.xml && typeof cloned.xml === "object") {
|
|
||||||
if (typeof cloned.xml.request === "string") delete cloned.xml.request;
|
|
||||||
if (typeof cloned.xml.response === "string") delete cloned.xml.response;
|
|
||||||
if (isEmpty(cloned.xml)) delete cloned.xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cloned.response?.xml && typeof cloned.response.xml === "object") {
|
|
||||||
if (typeof cloned.response.xml.request === "string") delete cloned.response.xml.request;
|
|
||||||
if (typeof cloned.response.xml.response === "string") delete cloned.response.xml.response;
|
|
||||||
if (isEmpty(cloned.response.xml)) delete cloned.response.xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cloned;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Safe deep clone for plain JSON structures.
|
|
||||||
* @param value
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
const safeClone = (value) => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(JSON.stringify(value));
|
|
||||||
} catch {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First non-empty string helper.
|
|
||||||
* @param value
|
|
||||||
* @returns {string|null}
|
|
||||||
*/
|
|
||||||
const firstString = (value) => {
|
|
||||||
if (typeof value !== "string") return null;
|
|
||||||
const trimmed = value.trim();
|
|
||||||
return trimmed ? trimmed : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy arbitrary text/object to clipboard.
|
|
||||||
* @param key
|
|
||||||
* @param value
|
|
||||||
* @param setCopied
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
const handleCopyAction = async (key, value, setCopied) => {
|
|
||||||
const text = typeof value === "string" ? value : safeStringify(value, 2);
|
|
||||||
if (!text) return;
|
|
||||||
const copied = await copyTextToClipboard(text);
|
|
||||||
if (!copied) return;
|
|
||||||
setCopied(key);
|
|
||||||
setTimeout(() => {
|
|
||||||
setCopied((prev) => (prev === key ? null : prev));
|
|
||||||
}, 1200);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clipboard helper (modern async Clipboard API).
|
|
||||||
* @param text
|
|
||||||
* @returns {Promise<boolean>}
|
|
||||||
*/
|
|
||||||
const copyTextToClipboard = async (text) => {
|
|
||||||
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON display block with optional syntax highlighting.
|
* JSON display block with optional syntax highlighting.
|
||||||
* @param data
|
* @param data
|
||||||
@@ -391,105 +210,6 @@ const JsonBlock = ({ data, colorize, isDarkMode }) => {
|
|||||||
return <pre style={preStyle}>{jsonText}</pre>;
|
return <pre style={preStyle}>{jsonText}</pre>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* XML display block with normalized indentation.
|
|
||||||
* @param title
|
|
||||||
* @param xmlText
|
|
||||||
* @param isDarkMode
|
|
||||||
* @returns {JSX.Element}
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
const XmlBlock = ({ title, xmlText, isDarkMode, colorize = false }) => {
|
|
||||||
const base = {
|
|
||||||
margin: "8px 0 0",
|
|
||||||
maxWidth: 720,
|
|
||||||
overflowX: "auto",
|
|
||||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
||||||
fontSize: 12,
|
|
||||||
lineHeight: 1.45,
|
|
||||||
padding: 8,
|
|
||||||
borderRadius: 6,
|
|
||||||
background: isDarkMode ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.04)",
|
|
||||||
border: isDarkMode ? "1px solid rgba(255,255,255,0.12)" : "1px solid rgba(0,0,0,0.08)",
|
|
||||||
color: isDarkMode ? "var(--card-text-fallback)" : "#141414",
|
|
||||||
whiteSpace: "pre"
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ marginTop: 6 }}>
|
|
||||||
<div style={{ fontSize: 11, fontWeight: 600 }}>{title}</div>
|
|
||||||
{colorize ? (
|
|
||||||
<pre style={base} dangerouslySetInnerHTML={{ __html: highlightXml(formatXml(xmlText)) }} />
|
|
||||||
) : (
|
|
||||||
<pre style={base}>{formatXml(xmlText)}</pre>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic XML pretty-printer.
|
|
||||||
* @param xml
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
const formatXml = (xml) => {
|
|
||||||
if (typeof xml !== "string") return "";
|
|
||||||
const normalized = xml.replace(/\r\n/g, "\n").replace(/>\s*</g, ">\n<").trim();
|
|
||||||
const lines = normalized.split("\n");
|
|
||||||
let indent = 0;
|
|
||||||
const out = [];
|
|
||||||
|
|
||||||
for (const rawLine of lines) {
|
|
||||||
const line = rawLine.trim();
|
|
||||||
if (!line) continue;
|
|
||||||
|
|
||||||
if (/^<\/[^>]+>/.test(line)) indent = Math.max(indent - 1, 0);
|
|
||||||
out.push(`${" ".repeat(indent)}${line}`);
|
|
||||||
|
|
||||||
const opens = (line.match(/<[^/!?][^>]*>/g) || []).length;
|
|
||||||
const closes = (line.match(/<\/[^>]+>/g) || []).length;
|
|
||||||
const selfClosing = (line.match(/<[^>]+\/>/g) || []).length;
|
|
||||||
const declaration = /^<\?xml/.test(line) ? 1 : 0;
|
|
||||||
|
|
||||||
indent += opens - closes - selfClosing - declaration;
|
|
||||||
if (indent < 0) indent = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return out.join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Syntax highlight pretty-printed XML text for HTML display.
|
|
||||||
* @param xmlText
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
const highlightXml = (xmlText) => {
|
|
||||||
const esc = String(xmlText || "")
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">");
|
|
||||||
const lines = esc.split("\n");
|
|
||||||
|
|
||||||
return lines
|
|
||||||
.map((line) => {
|
|
||||||
let out = line;
|
|
||||||
|
|
||||||
out = out.replace(/(<!--[\s\S]*?-->)/g, '<span class="xml-comment">$1</span>');
|
|
||||||
out = out.replace(/(<\?xml[\s\S]*?\?>)/g, '<span class="xml-decl">$1</span>');
|
|
||||||
|
|
||||||
out = out.replace(/(<\/?)([A-Za-z_][\w:.-]*)([\s\S]*?)(\/?>)/g, (_m, open, tag, attrs, close) => {
|
|
||||||
const coloredAttrs = attrs.replace(
|
|
||||||
/([A-Za-z_][\w:.-]*)(=)("[^"]*"|'[^']*'|"[\s\S]*?"|'[\s\S]*?')/g,
|
|
||||||
'<span class="xml-attr">$1</span>$2<span class="xml-value">$3</span>'
|
|
||||||
);
|
|
||||||
return `${open}<span class="xml-tag">${tag}</span>${coloredAttrs}${close}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return out;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syntax highlight JSON text for HTML display.
|
* Syntax highlight JSON text for HTML display.
|
||||||
* @param jsonText
|
* @param jsonText
|
||||||
|
|||||||
@@ -272,19 +272,11 @@ export default function CdkLikePostForm({ bodyshop, socket, job, logsRef, mode,
|
|||||||
name={[field.name, "name"]}
|
name={[field.name, "name"]}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select style={{ width: "100%" }} onSelect={(value) => handlePayerSelect(value, index)}>
|
||||||
showSearch={{
|
{bodyshop.cdk_configuration?.payers?.map((payer) => (
|
||||||
optionFilterProp: "label",
|
<Select.Option key={payer.name}>{payer.name}</Select.Option>
|
||||||
filterOption: (input, option) => option.label.toLowerCase().includes(input.toLowerCase())
|
))}
|
||||||
}}
|
</Select>
|
||||||
style={{ width: "100%" }}
|
|
||||||
onSelect={(value) => handlePayerSelect(value, index)}
|
|
||||||
options={bodyshop.cdk_configuration?.payers?.map((payer) => ({
|
|
||||||
key: payer.name,
|
|
||||||
value: payer.name,
|
|
||||||
label: payer.name
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ReloadOutlined } from "@ant-design/icons";
|
import { ReloadOutlined } from "@ant-design/icons";
|
||||||
import { Alert, Button, Form, Input, InputNumber, Modal, Radio, Select, Space, Typography } from "antd";
|
import { Alert, Button, Form, Input, InputNumber, Modal, Radio, Select, Space, Table, Typography } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
// Simple customer selector table
|
// Simple customer selector table
|
||||||
@@ -27,14 +26,7 @@ function CustomerSelectorTable({ customers, onSelect, isSubmitting }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ResponsiveTable
|
<Table columns={columns} dataSource={customers} rowKey="custNo" pagination={false} size="small" />
|
||||||
columns={columns}
|
|
||||||
mobileColumnKeys={["name", "select", "custNo", "vinOwner"]}
|
|
||||||
dataSource={customers}
|
|
||||||
rowKey="custNo"
|
|
||||||
pagination={false}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
<div style={{ marginTop: 16, display: "flex", gap: 8 }}>
|
<div style={{ marginTop: 16, display: "flex", gap: 8 }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
@@ -86,13 +86,11 @@ export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, b
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select>
|
||||||
options={[
|
<Select.Option key={currentUser.email}>{currentUser.email}</Select.Option>
|
||||||
{ key: currentUser.email, value: currentUser.email, label: currentUser.email },
|
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
|
||||||
{ key: bodyshop.email, value: bodyshop.email, label: bodyshop.email },
|
{bodyshop.md_from_emails && bodyshop.md_from_emails.map((e) => <Select.Option key={e}>{e}</Select.Option>)}
|
||||||
...(bodyshop.md_from_emails ? bodyshop.md_from_emails.map((e) => ({ key: e, value: e, label: e })) : [])
|
</Select>
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={
|
label={
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ export function EmailOverlayContainer({ emailConfig, modalVisible, toggleEmailOv
|
|||||||
<Modal
|
<Modal
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
open={modalVisible}
|
open={modalVisible}
|
||||||
mask={{ closable: false }}
|
maskClosable={false}
|
||||||
width={"80%"}
|
width={"80%"}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
title={t("emails.labels.emailpreview")}
|
title={t("emails.labels.emailpreview")}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Select, Space, Tag } from "antd";
|
import { Select, Space, Tag } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
//To be used as a form element only.
|
//To be used as a form element only.
|
||||||
|
|
||||||
const EmployeeSearchSelectEmail = ({ options, ...props }) => {
|
const EmployeeSearchSelectEmail = ({ options, ...props }) => {
|
||||||
@@ -11,24 +12,26 @@ const EmployeeSearchSelectEmail = ({ options, ...props }) => {
|
|||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "search"
|
optionFilterProp: "search"
|
||||||
}}
|
}}
|
||||||
|
// value={option}
|
||||||
style={{
|
style={{
|
||||||
width: 400
|
width: 400
|
||||||
}}
|
}}
|
||||||
options={options?.map((o) => ({
|
|
||||||
key: o.id,
|
|
||||||
value: o.user_email,
|
|
||||||
search: `${o.employee_number} ${o.first_name} ${o.last_name}`,
|
|
||||||
label: (
|
|
||||||
<Space>
|
|
||||||
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
|
||||||
<Tag color="green">
|
|
||||||
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
|
||||||
</Tag>
|
|
||||||
</Space>
|
|
||||||
)
|
|
||||||
}))}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{options
|
||||||
|
? options.map((o) => (
|
||||||
|
<Option key={o.id} value={o.user_email} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
||||||
|
<Space>
|
||||||
|
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
||||||
|
|
||||||
|
<Tag color="green">
|
||||||
|
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
</Option>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
</Select>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default EmployeeSearchSelectEmail;
|
export default EmployeeSearchSelectEmail;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Select, Space, Tag } from "antd";
|
import { Select, Space, Tag } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
//To be used as a form element only.
|
//To be used as a form element only.
|
||||||
|
|
||||||
const EmployeeSearchSelect = ({ options, showEmail, ...props }) => {
|
const EmployeeSearchSelect = ({ options, showEmail, ...props }) => {
|
||||||
@@ -11,29 +12,30 @@ const EmployeeSearchSelect = ({ options, showEmail, ...props }) => {
|
|||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "search"
|
optionFilterProp: "search"
|
||||||
}}
|
}}
|
||||||
|
// value={option}
|
||||||
style={{
|
style={{
|
||||||
width: 400
|
width: 400
|
||||||
}}
|
}}
|
||||||
options={options?.map((o) => ({
|
|
||||||
key: o.id,
|
|
||||||
value: o.id,
|
|
||||||
search: `${o.employee_number} ${o.first_name} ${o.last_name}`,
|
|
||||||
label: (
|
|
||||||
<Space size="small">
|
|
||||||
{`${o.employee_number ?? ""} ${o.first_name} ${o.last_name}`}
|
|
||||||
<Tag color="green" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
|
||||||
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
|
||||||
</Tag>
|
|
||||||
{showEmail && o.user_email ? (
|
|
||||||
<Tag color="blue" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
|
||||||
{o.user_email}
|
|
||||||
</Tag>
|
|
||||||
) : null}
|
|
||||||
</Space>
|
|
||||||
)
|
|
||||||
}))}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{options
|
||||||
|
? options.map((o) => (
|
||||||
|
<Option key={o.id} value={o.id} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
||||||
|
<Space size="small">
|
||||||
|
{`${o.employee_number ?? ""} ${o.first_name} ${o.last_name}`}
|
||||||
|
<Tag color="green" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
||||||
|
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
||||||
|
</Tag>
|
||||||
|
{showEmail && o.user_email ? (
|
||||||
|
<Tag color="blue" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
||||||
|
{o.user_email}
|
||||||
|
</Tag>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</Option>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
</Select>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default EmployeeSearchSelect;
|
export default EmployeeSearchSelect;
|
||||||
|
|||||||
@@ -39,13 +39,11 @@ export default function FormsFieldChanged({ form, skipPrompt }) {
|
|||||||
{errors.length > 0 && (
|
{errors.length > 0 && (
|
||||||
<AlertComponent
|
<AlertComponent
|
||||||
type="error"
|
type="error"
|
||||||
message={t("general.labels.validationerror")}
|
title={
|
||||||
description={
|
|
||||||
<div>
|
<div>
|
||||||
<ul>{errors.map((e, idx) => e.errors.map((e2, idx2) => <li key={`${idx}${idx2}`}>{e2}</li>))}</ul>
|
<ul>{errors.map((e, idx) => e.errors.map((e2, idx2) => <li key={`${idx}${idx2}`}>{e2}</li>))}</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
showIcon
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -184,29 +184,22 @@ export default function GlobalSearchOs() {
|
|||||||
return (
|
return (
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
options={data}
|
options={data}
|
||||||
defaultActiveFirstOption
|
onSearch={handleSearch}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key !== "Enter") return;
|
if (e.key !== "Enter") return;
|
||||||
const firstUrlForSearch = data?.[0]?.options?.[0]?.label?.props?.to;
|
const firstUrlForSearch = data?.[0]?.options?.[0]?.label?.props?.to;
|
||||||
if (!firstUrlForSearch) return;
|
if (!firstUrlForSearch) return;
|
||||||
navigate(firstUrlForSearch);
|
navigate(firstUrlForSearch);
|
||||||
}}
|
}}
|
||||||
|
defaultActiveFirstOption
|
||||||
|
onClear={() => setData([])}
|
||||||
>
|
>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
// className="global-search-autocomplete-fix"
|
|
||||||
size="large"
|
size="large"
|
||||||
placeholder={t("general.labels.globalsearch")}
|
placeholder={t("general.labels.globalsearch")}
|
||||||
enterButton
|
enterButton
|
||||||
allowClear
|
allowClear
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
if (!value) {
|
|
||||||
setData([]);
|
|
||||||
} else {
|
|
||||||
handleSearch(value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</AutoComplete>
|
</AutoComplete>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -160,6 +160,9 @@ export default function GlobalSearch() {
|
|||||||
return (
|
return (
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
options={options}
|
options={options}
|
||||||
|
showSearch={{
|
||||||
|
onSearch: handleSearch
|
||||||
|
}}
|
||||||
defaultActiveFirstOption
|
defaultActiveFirstOption
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key !== "Enter") return;
|
if (e.key !== "Enter") return;
|
||||||
@@ -169,13 +172,11 @@ export default function GlobalSearch() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
// className="global-search-autocomplete-fix"
|
|
||||||
size="large"
|
size="large"
|
||||||
placeholder={t("general.labels.globalsearch")}
|
placeholder={t("general.labels.globalsearch")}
|
||||||
enterButton
|
enterButton
|
||||||
allowClear
|
allowClear
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</AutoComplete>
|
</AutoComplete>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { EditFilled, FileAddFilled, SyncOutlined } from "@ant-design/icons";
|
import { EditFilled, FileAddFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Input, Space, Typography } from "antd";
|
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -10,7 +10,6 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
|||||||
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
|
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
|
||||||
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
|
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
|
||||||
import { pageLimit } from "../../utils/config";
|
import { pageLimit } from "../../utils/config";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
const mapStateToProps = createStructuredSelector({});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
@@ -186,11 +185,10 @@ export function JobsList({ refetch, loading, jobs, total, setInventoryUpsertCont
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(page || 1), total: total }}
|
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(page || 1), total: total }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["line_desc", "actual_price", "consumedbyjob", "actions"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={jobs}
|
dataSource={jobs}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -67,19 +67,16 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInsSelect = (value) => {
|
const handleInsSelect = (value, option) => {
|
||||||
const selectedVendor = bodyshop.md_ins_cos.find(s => s.name === value);
|
form.setFieldsValue({
|
||||||
if (selectedVendor) {
|
addr1: option.obj.name,
|
||||||
form.setFieldsValue({
|
addr2: option.obj.street1,
|
||||||
addr1: selectedVendor.name,
|
addr3: option.obj.street2,
|
||||||
addr2: selectedVendor.street1,
|
city: option.obj.city,
|
||||||
addr3: selectedVendor.street2,
|
state: option.obj.state,
|
||||||
city: selectedVendor.city,
|
zip: option.obj.zip,
|
||||||
state: selectedVendor.state,
|
vendorid: null
|
||||||
zip: selectedVendor.zip,
|
});
|
||||||
vendorid: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVendorSelect = (vendorid) => {
|
const handleVendorSelect = (vendorid) => {
|
||||||
@@ -106,13 +103,13 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
|
|||||||
<VendorSearchSelect options={VendorAutoCompleteData?.vendors} onSelect={handleVendorSelect} />
|
<VendorSearchSelect options={VendorAutoCompleteData?.vendors} onSelect={handleVendorSelect} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("bodyshop.fields.md_ins_co.name")} name="ins_co_id">
|
<Form.Item label={t("bodyshop.fields.md_ins_co.name")} name="ins_co_id">
|
||||||
<Select
|
<Select onSelect={handleInsSelect}>
|
||||||
onSelect={handleInsSelect}
|
{bodyshop.md_ins_cos.map((s) => (
|
||||||
options={bodyshop.md_ins_cos.map((s) => ({
|
<Select.Option key={s.name} obj={s} value={s.name}>
|
||||||
value: s.name,
|
{s.name}
|
||||||
label: s.name
|
</Select.Option>
|
||||||
}))}
|
))}
|
||||||
/>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
<Form.Item label={t("printcenter.jobs.3rdpartyfields.addr1")} name="addr1">
|
<Form.Item label={t("printcenter.jobs.3rdpartyfields.addr1")} name="addr1">
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { useQuery } from "@apollo/client/react";
|
import { useQuery } from "@apollo/client/react";
|
||||||
import { Button, Card, Col, Row, Tag } from "antd";
|
import { Button, Card, Col, Row, Table, Tag } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -164,24 +163,12 @@ export function JobAuditTrail({ bodyshop, jobId }) {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table loading={loading} columns={columns} rowKey="id" dataSource={data ? data.audit_trail : []} />
|
||||||
loading={loading}
|
|
||||||
columns={columns}
|
|
||||||
mobileColumnKeys={["status", "created", "useremail", "operation"]}
|
|
||||||
rowKey="id"
|
|
||||||
dataSource={data ? data.audit_trail : []}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Card title={t("jobs.labels.emailaudit")}>
|
<Card title={t("jobs.labels.emailaudit")}>
|
||||||
<ResponsiveTable
|
<Table loading={loading} columns={emailColumns} rowKey="id" dataSource={data ? data.email_audit_trail : []} />
|
||||||
loading={loading}
|
|
||||||
columns={emailColumns}
|
|
||||||
mobileColumnKeys={["status", "created", "useremail", "operation"]}
|
|
||||||
rowKey="id"
|
|
||||||
dataSource={data ? data.email_audit_trail : []}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Alert, Card } from "antd";
|
import { Alert, Card, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -69,15 +68,7 @@ export function JobCloseRGuardPpd({ job, warningCallback }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={t("jobs.labels.ppdnotexported")}>
|
<Card title={t("jobs.labels.ppdnotexported")}>
|
||||||
<ResponsiveTable
|
<Table dataSource={linesWithPPD} columns={columns} pagination={false} rowKey="id" bordered size="small" />
|
||||||
dataSource={linesWithPPD}
|
|
||||||
columns={columns}
|
|
||||||
mobileColumnKeys={["line_desc", "ppd", "act_price", "act_price_before_ppc"]}
|
|
||||||
pagination={false}
|
|
||||||
rowKey="id"
|
|
||||||
bordered
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
{linesWithPPD.length > 0 && (
|
{linesWithPPD.length > 0 && (
|
||||||
<Alert style={{ margin: "8px 0px" }} type="warning" title={t("jobs.labels.outstanding_ppd")} />
|
<Alert style={{ margin: "8px 0px" }} type="warning" title={t("jobs.labels.outstanding_ppd")} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import { Alert, Card } from "antd";
|
import { Alert, Card, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -63,15 +62,7 @@ export function JobCloseRGuardSublet({ job, warningCallback }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={t("jobs.labels.subletsnotcompleted")}>
|
<Card title={t("jobs.labels.subletsnotcompleted")}>
|
||||||
<ResponsiveTable
|
<Table dataSource={subletsNotDone} columns={columns} pagination={false} rowKey="id" bordered size="small" />
|
||||||
dataSource={subletsNotDone}
|
|
||||||
columns={columns}
|
|
||||||
mobileColumnKeys={["line_desc", "act_price", "part_qty", "notes"]}
|
|
||||||
pagination={false}
|
|
||||||
rowKey="id"
|
|
||||||
bordered
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
{subletsNotDone.length > 0 && (
|
{subletsNotDone.length > 0 && (
|
||||||
<Alert style={{ margin: "8px 0px" }} type="warning" title={t("jobs.labels.outstanding_sublets")} />
|
<Alert style={{ margin: "8px 0px" }} type="warning" title={t("jobs.labels.outstanding_sublets")} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Input, Space, Typography } from "antd";
|
import { Input, Space, Table, Typography } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
@@ -66,7 +65,7 @@ export default function JobCostingPartsTable({ data, summaryData }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
title={() => {
|
title={() => {
|
||||||
return (
|
return (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
@@ -88,19 +87,18 @@ export default function JobCostingPartsTable({ data, summaryData }) {
|
|||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
pagination={{ placement: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["cost_center", "sales", "costs", "gpdollars", "gppercent"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={filteredData}
|
dataSource={filteredData}
|
||||||
summary={() => (
|
summary={() => (
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
|
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell>{Dinero(summaryData.totalSales).toFormat()}</ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>{Dinero(summaryData.totalSales).toFormat()}</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell>{Dinero(summaryData.totalCost).toFormat()}</ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>{Dinero(summaryData.totalCost).toFormat()}</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell>{Dinero(summaryData.gpdollars).toFormat()}</ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>{Dinero(summaryData.gpdollars).toFormat()}</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell></ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell></Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,8 +58,10 @@ const span = {
|
|||||||
export function JobDetailCards({ bodyshop, setPrintCenterContext, insertAuditTrail }) {
|
export function JobDetailCards({ bodyshop, setPrintCenterContext, insertAuditTrail }) {
|
||||||
const { scenarioNotificationsOn } = useSocket();
|
const { scenarioNotificationsOn } = useSocket();
|
||||||
const [updateJob] = useMutation(UPDATE_JOB);
|
const [updateJob] = useMutation(UPDATE_JOB);
|
||||||
const screens = Grid.useBreakpoint();
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
|
.filter((screen) => !!screen[1])
|
||||||
|
.slice(-1)[0];
|
||||||
|
|
||||||
const bpoints = {
|
const bpoints = {
|
||||||
xs: "100%",
|
xs: "100%",
|
||||||
sm: "100%",
|
sm: "100%",
|
||||||
@@ -68,14 +70,7 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext, insertAuditTra
|
|||||||
xl: "75%",
|
xl: "75%",
|
||||||
xxl: "75%"
|
xxl: "75%"
|
||||||
};
|
};
|
||||||
|
const drawerPercentage = selectedBreakpoint ? bpoints[selectedBreakpoint[0]] : "100%";
|
||||||
let drawerPercentage = "100%";
|
|
||||||
if (screens.xxl) drawerPercentage = bpoints.xxl;
|
|
||||||
else if (screens.xl) drawerPercentage = bpoints.xl;
|
|
||||||
else if (screens.lg) drawerPercentage = bpoints.lg;
|
|
||||||
else if (screens.md) drawerPercentage = bpoints.md;
|
|
||||||
else if (screens.sm) drawerPercentage = bpoints.sm;
|
|
||||||
else if (screens.xs) drawerPercentage = bpoints.xs;
|
|
||||||
|
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
const { selected } = searchParams;
|
const { selected } = searchParams;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
import { Table } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
|
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
|
||||||
import PartsStatusPie from "../parts-status-pie/parts-status-pie.component";
|
import PartsStatusPie from "../parts-status-pie/parts-status-pie.component";
|
||||||
@@ -101,12 +101,7 @@ function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
|
|||||||
<div>
|
<div>
|
||||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
|
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
|
||||||
<PartsStatusPie joblines_status={joblines_status} />
|
<PartsStatusPie joblines_status={joblines_status} />
|
||||||
<ResponsiveTable
|
<Table rowKey="id" columns={columns} dataSource={filteredJobLines || []} />
|
||||||
rowKey="id"
|
|
||||||
columns={columns}
|
|
||||||
mobileColumnKeys={["status", "line_desc", "part_type", "part_qty"]}
|
|
||||||
dataSource={filteredJobLines || []}
|
|
||||||
/>
|
|
||||||
</CardTemplate>
|
</CardTemplate>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -690,7 +690,6 @@ export function JobLinesComponent({
|
|||||||
|
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
// mobileColumnKeys={["status", "line_desc", "actions", "line_no"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useQuery } from "@apollo/client/react";
|
import { useQuery } from "@apollo/client/react";
|
||||||
import { gql } from "@apollo/client";
|
import { gql } from "@apollo/client";
|
||||||
import { Badge, Card, Space, Tag } from "antd";
|
import { Badge, Card, Space, Table, Tag } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { isEmpty } from "lodash";
|
import { isEmpty } from "lodash";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
@@ -312,13 +311,12 @@ export function JobLifecycleComponent({ bodyshop, job, statuses }) {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
style={{
|
style={{
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["value", "start", "start_readable", "end"]}
|
|
||||||
dataSource={lifecycleData.lifecycle}
|
dataSource={lifecycleData.lifecycle}
|
||||||
rowKey="start"
|
rowKey="start"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -88,15 +88,17 @@ export function JoblineBulkAssign({ setSelectedLines, selectedLines, insertAudit
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "label",
|
optionFilterProp: "children",
|
||||||
filterOption: (input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
options={bodyshop.employee_teams.map((team) => ({
|
>
|
||||||
value: team.id,
|
{bodyshop.employee_teams.map((team) => (
|
||||||
label: team.name
|
<Select.Option value={team.id} key={team.id} name={team.name}>
|
||||||
}))}
|
{team.name}
|
||||||
/>
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -122,26 +122,22 @@ export function JobLineConvertToLabor({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select allowClear showSearch={{ optionFilterProp: "children" }}>
|
||||||
allowClear
|
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
||||||
showSearch
|
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
||||||
options={[
|
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
||||||
{ value: "LAA", label: t("joblines.fields.lbr_types.LAA") },
|
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
||||||
{ value: "LAB", label: t("joblines.fields.lbr_types.LAB") },
|
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
||||||
{ value: "LAD", label: t("joblines.fields.lbr_types.LAD") },
|
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
||||||
{ value: "LAE", label: t("joblines.fields.lbr_types.LAE") },
|
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
||||||
{ value: "LAF", label: t("joblines.fields.lbr_types.LAF") },
|
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
||||||
{ value: "LAG", label: t("joblines.fields.lbr_types.LAG") },
|
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
||||||
{ value: "LAM", label: t("joblines.fields.lbr_types.LAM") },
|
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
||||||
{ value: "LAR", label: t("joblines.fields.lbr_types.LAR") },
|
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
||||||
{ value: "LAS", label: t("joblines.fields.lbr_types.LAS") },
|
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
||||||
{ value: "LAU", label: t("joblines.fields.lbr_types.LAU") },
|
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
||||||
{ value: "LA1", label: t("joblines.fields.lbr_types.LA1") },
|
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
||||||
{ value: "LA2", label: t("joblines.fields.lbr_types.LA2") },
|
</Select>
|
||||||
{ value: "LA3", label: t("joblines.fields.lbr_types.LA3") },
|
|
||||||
{ value: "LA4", label: t("joblines.fields.lbr_types.LA4") }
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item shouldUpdate>
|
<Form.Item shouldUpdate>
|
||||||
|
|||||||
@@ -115,18 +115,19 @@ export function JobLineDispatchButton({
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "label",
|
optionFilterProp: "children",
|
||||||
filterOption: (input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
options={bodyshop.employees
|
>
|
||||||
|
{bodyshop.employees
|
||||||
.filter((emp) => emp.active)
|
.filter((emp) => emp.active)
|
||||||
.map((emp) => ({
|
.map((emp) => (
|
||||||
value: emp.id,
|
<Select.Option value={emp.id} key={emp.id} name={`${emp.first_name} ${emp.last_name}`}>
|
||||||
key: emp.id,
|
{`${emp.first_name} ${emp.last_name}`}
|
||||||
label: `${emp.first_name} ${emp.last_name}`
|
</Select.Option>
|
||||||
}))}
|
))}
|
||||||
/>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -64,12 +64,13 @@ export function JobLineStatusPopup({ bodyshop, jobline, disabled }) {
|
|||||||
onSelect={handleChange}
|
onSelect={handleChange}
|
||||||
onBlur={handleSave}
|
onBlur={handleSave}
|
||||||
onClear={() => handleChange(null)}
|
onClear={() => handleChange(null)}
|
||||||
options={Object.values(bodyshop.md_order_statuses).map((s, idx) => ({
|
>
|
||||||
key: idx,
|
{Object.values(bodyshop.md_order_statuses).map((s, idx) => (
|
||||||
value: s,
|
<Select.Option key={idx} value={s}>
|
||||||
label: s
|
{s}
|
||||||
}))}
|
</Select.Option>
|
||||||
/>
|
))}
|
||||||
|
</Select>
|
||||||
</LoadingSpinner>
|
</LoadingSpinner>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -75,12 +75,13 @@ export function JoblineTeamAssignment({ bodyshop, jobline, disabled, jobId, inse
|
|||||||
onSelect={handleChange}
|
onSelect={handleChange}
|
||||||
onBlur={handleSave}
|
onBlur={handleSave}
|
||||||
onClear={() => handleChange(null)}
|
onClear={() => handleChange(null)}
|
||||||
options={Object.values(bodyshop.employee_teams).map((s) => ({
|
>
|
||||||
key: s.id,
|
{Object.values(bodyshop.employee_teams).map((s, idx) => (
|
||||||
value: s.id,
|
<Select.Option key={idx} value={s.id}>
|
||||||
label: s.name
|
{s.name}
|
||||||
}))}
|
</Select.Option>
|
||||||
/>
|
))}
|
||||||
|
</Select>
|
||||||
</LoadingSpinner>
|
</LoadingSpinner>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -67,22 +67,22 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
|
|||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
<Form.Item label={t("joblines.fields.mod_lbr_ty")} name="mod_lbr_ty">
|
<Form.Item label={t("joblines.fields.mod_lbr_ty")} name="mod_lbr_ty">
|
||||||
<Select allowClear options={[
|
<Select allowClear>
|
||||||
{ value: "LAA", label: t("joblines.fields.lbr_types.LAA") },
|
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
||||||
{ value: "LAB", label: t("joblines.fields.lbr_types.LAB") },
|
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
||||||
{ value: "LAD", label: t("joblines.fields.lbr_types.LAD") },
|
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
||||||
{ value: "LAE", label: t("joblines.fields.lbr_types.LAE") },
|
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
||||||
{ value: "LAF", label: t("joblines.fields.lbr_types.LAF") },
|
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
||||||
{ value: "LAG", label: t("joblines.fields.lbr_types.LAG") },
|
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
||||||
{ value: "LAM", label: t("joblines.fields.lbr_types.LAM") },
|
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
||||||
{ value: "LAR", label: t("joblines.fields.lbr_types.LAR") },
|
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
||||||
{ value: "LAS", label: t("joblines.fields.lbr_types.LAS") },
|
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
||||||
{ value: "LAU", label: t("joblines.fields.lbr_types.LAU") },
|
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
||||||
{ value: "LA1", label: t("joblines.fields.lbr_types.LA1") },
|
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
||||||
{ value: "LA2", label: t("joblines.fields.lbr_types.LA2") },
|
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
||||||
{ value: "LA3", label: t("joblines.fields.lbr_types.LA3") },
|
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
||||||
{ value: "LA4", label: t("joblines.fields.lbr_types.LA4") }
|
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
||||||
]} />
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("joblines.fields.op_code_desc")} name="op_code_desc">
|
<Form.Item label={t("joblines.fields.op_code_desc")} name="op_code_desc">
|
||||||
<Input />
|
<Input />
|
||||||
@@ -128,17 +128,17 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
|
|||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow>
|
<LayoutFormRow>
|
||||||
<Form.Item label={t("joblines.fields.part_type")} name="part_type">
|
<Form.Item label={t("joblines.fields.part_type")} name="part_type">
|
||||||
<Select allowClear options={[
|
<Select allowClear>
|
||||||
{ value: "PAA", label: t("joblines.fields.part_types.PAA") },
|
<Select.Option value="PAA">{t("joblines.fields.part_types.PAA")}</Select.Option>
|
||||||
{ value: "PAC", label: t("joblines.fields.part_types.PAC") },
|
<Select.Option value="PAC">{t("joblines.fields.part_types.PAC")}</Select.Option>
|
||||||
{ value: "PAE", label: t("joblines.fields.part_types.PAE") },
|
<Select.Option value="PAE">{t("joblines.fields.part_types.PAE")}</Select.Option>
|
||||||
{ value: "PAL", label: t("joblines.fields.part_types.PAL") },
|
<Select.Option value="PAL">{t("joblines.fields.part_types.PAL")}</Select.Option>
|
||||||
{ value: "PAM", label: t("joblines.fields.part_types.PAM") },
|
<Select.Option value="PAM">{t("joblines.fields.part_types.PAM")}</Select.Option>
|
||||||
{ value: "PAN", label: t("joblines.fields.part_types.PAN") },
|
<Select.Option value="PAN">{t("joblines.fields.part_types.PAN")}</Select.Option>
|
||||||
{ value: "PAO", label: t("joblines.fields.part_types.PAO") },
|
<Select.Option value="PAO">{t("joblines.fields.part_types.PAO")}</Select.Option>
|
||||||
{ value: "PAR", label: t("joblines.fields.part_types.PAR") },
|
<Select.Option value="PAR">{t("joblines.fields.part_types.PAR")}</Select.Option>
|
||||||
{ value: "PAS", label: t("joblines.fields.part_types.PAS") }
|
<Select.Option value="PAS">{t("joblines.fields.part_types.PAS")}</Select.Option>
|
||||||
]} />
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("joblines.fields.oem_partno")} name="oem_partno">
|
<Form.Item label={t("joblines.fields.oem_partno")} name="oem_partno">
|
||||||
<Input />
|
<Input />
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { EditFilled } from "@ant-design/icons";
|
import { EditFilled } from "@ant-design/icons";
|
||||||
import { Button, Card, Space } from "antd";
|
import { Button, Card, Space, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -187,9 +186,8 @@ export function JobPayments({ job, bodyshop, setPaymentContext, setCardPaymentCo
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["date", "amount", "actions", "payer"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
@@ -201,18 +199,18 @@ export function JobPayments({ job, bodyshop, setPaymentContext, setCardPaymentCo
|
|||||||
expandedRowRender: (record) => <PaymentExpandedRowComponent record={record} bodyshop={bodyshop} />
|
expandedRowRender: (record) => <PaymentExpandedRowComponent record={record} bodyshop={bodyshop} />
|
||||||
}}
|
}}
|
||||||
summary={() => (
|
summary={() => (
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<strong>{t("payments.labels.totalpayments")}</strong>
|
<strong>{t("payments.labels.totalpayments")}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<strong>{total.toFormat()}</strong>
|
<strong>{total.toFormat()}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Checkbox, Typography } from "antd";
|
import { Checkbox, Table, Typography } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
@@ -80,12 +79,11 @@ export default function JobReconciliationBillsTable({ billLineState, invoiceLine
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
|
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="small"
|
size="small"
|
||||||
scroll={{ y: "60vh" }}
|
scroll={{ y: "60vh" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["line_desc", "from", "actual_price", "actual_cost"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={invoiceLineData}
|
dataSource={invoiceLineData}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Typography } from "antd";
|
import { Table, Typography } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
@@ -97,10 +96,9 @@ export default function JobReconcilitionPartsTable({ jobLineState, jobLineData }
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Typography.Title level={4}>{t("jobs.labels.lines")}</Typography.Title>
|
<Typography.Title level={4}>{t("jobs.labels.lines")}</Typography.Title>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["status", "line_desc", "total", "oem_partno"]}
|
|
||||||
size="small"
|
size="small"
|
||||||
scroll={{ y: "60vh" }}
|
scroll={{ y: "60vh" }}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE } from
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
const JobSearchSelect = ({
|
const JobSearchSelect = ({
|
||||||
disabled,
|
disabled,
|
||||||
convertedOnly = false,
|
convertedOnly = false,
|
||||||
@@ -85,24 +87,24 @@ const JobSearchSelect = ({
|
|||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
suffixIcon={(loading || idLoading) && <Spin />} // matches OLD spinner semantics
|
suffixIcon={(loading || idLoading) && <Spin />} // matches OLD spinner semantics
|
||||||
notFoundContent={loading ? <LoadingOutlined /> : null} // matches OLD (loading only)
|
notFoundContent={loading ? <LoadingOutlined /> : null} // matches OLD (loading only)
|
||||||
options={theOptions?.map((o) => ({
|
>
|
||||||
key: o.id,
|
{theOptions
|
||||||
value: o.id,
|
? theOptions.map((o) => (
|
||||||
status: o.status,
|
<Option key={o.id} value={o.id} status={o.status}>
|
||||||
label: (
|
<Space align="center">
|
||||||
<Space align="center">
|
<span>
|
||||||
<span>
|
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${o.ro_number || t("general.labels.na")} | ${OwnerNameDisplayFunction(
|
||||||
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${o.ro_number || t("general.labels.na")} | ${OwnerNameDisplayFunction(
|
o
|
||||||
o
|
)} | ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${o.v_model_desc || ""}`}
|
||||||
)} | ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${o.v_model_desc || ""}`}
|
</span>
|
||||||
</span>
|
<Tag>
|
||||||
<Tag>
|
<strong>{o.status}</strong>
|
||||||
<strong>{o.status}</strong>
|
</Tag>
|
||||||
</Tag>
|
</Space>
|
||||||
</Space>
|
</Option>
|
||||||
)
|
))
|
||||||
}))}
|
: null}
|
||||||
/>
|
</Select>
|
||||||
|
|
||||||
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
||||||
{idError ? <AlertComponent title={idError.message} type="error" /> : null}
|
{idError ? <AlertComponent title={idError.message} type="error" /> : null}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Space } from "antd";
|
import { Space, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -105,9 +104,8 @@ export default function JobTotalsTableLabor({ job }) {
|
|||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["total", "profitcenter_labor", "rate", "mod_lb_hrs"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
@@ -117,29 +115,29 @@ export default function JobTotalsTableLabor({ job }) {
|
|||||||
}}
|
}}
|
||||||
summary={() => (
|
summary={() => (
|
||||||
<>
|
<>
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<strong>{t("jobs.labels.labor_rates_subtotal")}</strong>
|
<strong>{t("jobs.labels.labor_rates_subtotal")}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
{(job.job_totals.rates.mapa.hours + job.job_totals.rates.mash.hours).toFixed(1)}
|
{(job.job_totals.rates.mapa.hours + job.job_totals.rates.mash.hours).toFixed(1)}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
{InstanceRenderManager({
|
{InstanceRenderManager({
|
||||||
imex: null,
|
imex: null,
|
||||||
rome: (
|
rome: (
|
||||||
<>
|
<>
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
<strong>{Dinero(job.job_totals.rates.rates_subtotal).toFormat()}</strong>
|
<strong>{Dinero(job.job_totals.rates.rates_subtotal).toFormat()}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<Space>
|
<Space>
|
||||||
{t("jobs.labels.mapa")}
|
{t("jobs.labels.mapa")}
|
||||||
{InstanceRenderManager({
|
{InstanceRenderManager({
|
||||||
@@ -158,34 +156,34 @@ export default function JobTotalsTableLabor({ job }) {
|
|||||||
})
|
})
|
||||||
})}
|
})}
|
||||||
</Space>
|
</Space>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
<CurrencyFormatter>{job.job_totals.rates.mapa.rate}</CurrencyFormatter>
|
<CurrencyFormatter>{job.job_totals.rates.mapa.rate}</CurrencyFormatter>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell>{job.job_totals.rates.mapa.hours.toFixed(1)}</ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>{job.job_totals.rates.mapa.hours.toFixed(1)}</Table.Summary.Cell>
|
||||||
{InstanceRenderManager({
|
{InstanceRenderManager({
|
||||||
imex: (
|
imex: (
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
{Dinero(job.job_totals.rates.mapa.total).toFormat()}
|
{Dinero(job.job_totals.rates.mapa.total).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
),
|
),
|
||||||
rome: (
|
rome: (
|
||||||
<>
|
<>
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
{Dinero(job.job_totals.rates.mapa.base).toFormat()}
|
{Dinero(job.job_totals.rates.mapa.base).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
{Dinero(job.job_totals.rates.mapa.adjustment).toFormat()}
|
{Dinero(job.job_totals.rates.mapa.adjustment).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
{Dinero(job.job_totals.rates.mapa.total).toFormat()}
|
{Dinero(job.job_totals.rates.mapa.total).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{t("jobs.labels.mash")}
|
{t("jobs.labels.mash")}
|
||||||
{InstanceRenderManager({
|
{InstanceRenderManager({
|
||||||
@@ -204,51 +202,51 @@ export default function JobTotalsTableLabor({ job }) {
|
|||||||
})
|
})
|
||||||
})}
|
})}
|
||||||
</Space>
|
</Space>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
<CurrencyFormatter>{job.job_totals.rates.mash.rate}</CurrencyFormatter>
|
<CurrencyFormatter>{job.job_totals.rates.mash.rate}</CurrencyFormatter>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell>{job.job_totals.rates.mash.hours.toFixed(1)}</ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>{job.job_totals.rates.mash.hours.toFixed(1)}</Table.Summary.Cell>
|
||||||
{InstanceRenderManager({
|
{InstanceRenderManager({
|
||||||
imex: (
|
imex: (
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
{Dinero(job.job_totals.rates.mash.total).toFormat()}
|
{Dinero(job.job_totals.rates.mash.total).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
),
|
),
|
||||||
rome: (
|
rome: (
|
||||||
<>
|
<>
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
{Dinero(job.job_totals.rates.mash.base).toFormat()}
|
{Dinero(job.job_totals.rates.mash.base).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
{Dinero(job.job_totals.rates.mash.adjustment).toFormat()}
|
{Dinero(job.job_totals.rates.mash.adjustment).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
{Dinero(job.job_totals.rates.mash.total).toFormat()}
|
{Dinero(job.job_totals.rates.mash.total).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<strong>{t("jobs.labels.rates_subtotal")}</strong>
|
<strong>{t("jobs.labels.rates_subtotal")}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
{InstanceRenderManager({
|
{InstanceRenderManager({
|
||||||
imex: null,
|
imex: null,
|
||||||
rome: (
|
rome: (
|
||||||
<>
|
<>
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
<ResponsiveTable.Summary.Cell />
|
<Table.Summary.Cell />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
<strong>{Dinero(job.job_totals.rates.subtotal).toFormat()}</strong>
|
<strong>{Dinero(job.job_totals.rates.subtotal).toFormat()}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
import { Table } from "antd";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -65,9 +65,8 @@ export default function JobTotalsTableOther({ job }) {
|
|||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["total", "key"]}
|
|
||||||
rowKey="key"
|
rowKey="key"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
@@ -77,24 +76,24 @@ export default function JobTotalsTableOther({ job }) {
|
|||||||
}}
|
}}
|
||||||
summary={() => (
|
summary={() => (
|
||||||
<>
|
<>
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<strong>{t("jobs.labels.additionaltotal")}</strong>
|
<strong>{t("jobs.labels.additionaltotal")}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
|
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
<strong>{Dinero(job.job_totals.additional.total).toFormat()}</strong>
|
<strong>{Dinero(job.job_totals.additional.total).toFormat()}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<strong>{t("jobs.labels.subletstotal")}</strong>
|
<strong>{t("jobs.labels.subletstotal")}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
|
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
<strong>{Dinero(job.job_totals.parts.sublets.total).toFormat()}</strong>
|
<strong>{Dinero(job.job_totals.parts.sublets.total).toFormat()}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
import { Table } from "antd";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -64,9 +64,8 @@ export default function JobTotalsTableParts({ job }) {
|
|||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["total", "id"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
@@ -76,38 +75,36 @@ export default function JobTotalsTableParts({ job }) {
|
|||||||
}}
|
}}
|
||||||
summary={() => (
|
summary={() => (
|
||||||
<>
|
<>
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>{t("jobs.labels.prt_dsmk_total")}</ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>{t("jobs.labels.prt_dsmk_total")}</Table.Summary.Cell>
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
{Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()}
|
{Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()}
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
|
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<strong>{t("jobs.labels.partstotal")}</strong>
|
<strong>{t("jobs.labels.partstotal")}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
|
|
||||||
<ResponsiveTable.Summary.Cell align="right">
|
<Table.Summary.Cell align="right">
|
||||||
<strong>{Dinero(job.job_totals.parts.parts.total).toFormat()}</strong>
|
<strong>{Dinero(job.job_totals.parts.parts.total).toFormat()}</strong>
|
||||||
</ResponsiveTable.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
{
|
{
|
||||||
//TODO:AIO This shoudl only be in the US version. need to verify whether this causes problems for the CA version.
|
//TODO:AIO This shoudl only be in the US version. need to verify whether this causes problems for the CA version.
|
||||||
insuranceAdjustments.length > 0 && (
|
insuranceAdjustments.length > 0 && (
|
||||||
<ResponsiveTable.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<ResponsiveTable.Summary.Cell colSpan={24}>
|
<Table.Summary.Cell colSpan={24}>{t("jobs.labels.profileadjustments")}</Table.Summary.Cell>
|
||||||
{t("jobs.labels.profileadjustments")}
|
</Table.Summary.Row>
|
||||||
</ResponsiveTable.Summary.Cell>
|
|
||||||
</ResponsiveTable.Summary.Row>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{insuranceAdjustments.map((adj, idx) => (
|
{insuranceAdjustments.map((adj, idx) => (
|
||||||
<ResponsiveTable.Summary.Row key={idx}>
|
<Table.Summary.Row key={idx}>
|
||||||
<ResponsiveTable.Summary.Cell>{t(`jobs.fields.${adj.id.toLowerCase()}`)}</ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell>{t(`jobs.fields.${adj.id.toLowerCase()}`)}</Table.Summary.Cell>
|
||||||
|
|
||||||
<ResponsiveTable.Summary.Cell align="right">{adj.amount.toFormat()}</ResponsiveTable.Summary.Cell>
|
<Table.Summary.Cell align="right">{adj.amount.toFormat()}</Table.Summary.Cell>
|
||||||
</ResponsiveTable.Summary.Row>
|
</Table.Summary.Row>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
import { Table } from "antd";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -245,9 +245,8 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["total", "key"]}
|
|
||||||
rowKey="key"
|
rowKey="key"
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
|
|||||||
@@ -59,12 +59,13 @@ export function JobsAdminClass({ bodyshop, job }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select>
|
||||||
options={bodyshop.md_classes.map((s) => ({
|
{bodyshop.md_classes.map((s) => (
|
||||||
value: s,
|
<Select.Option key={s} value={s}>
|
||||||
label: s
|
{s}
|
||||||
}))}
|
</Select.Option>
|
||||||
/>
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DownloadOutlined, SyncOutlined } from "@ant-design/icons";
|
import { DownloadOutlined, SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Input, Space } from "antd";
|
import { Button, Card, Input, Space, Table } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -9,7 +9,6 @@ import { useNotification } from "../../contexts/Notifications/notificationContex
|
|||||||
import { selectPartnerVersion } from "../../redux/application/application.selectors";
|
import { selectPartnerVersion } from "../../redux/application/application.selectors";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils.js";
|
import { logImEXEvent } from "../../firebase/firebase.utils.js";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -142,11 +141,10 @@ export function JobsAvailableScan({ partnerVersion, refetch }) {
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["cieca_id", "owner", "vehicle", "actions"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DeleteFilled, DownloadOutlined, PlusCircleFilled, SyncOutlined } from "@ant-design/icons";
|
import { DeleteFilled, DownloadOutlined, PlusCircleFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client/react";
|
import { useMutation } from "@apollo/client/react";
|
||||||
import { Alert, Button, Card, Input, Space } from "antd";
|
import { Alert, Button, Card, Input, Space, Table } from "antd";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -13,7 +13,6 @@ import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
|||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils.js";
|
import { logImEXEvent } from "../../firebase/firebase.utils.js";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -210,14 +209,7 @@ export function JobsAvailableComponent({ bodyshop, loading, data, refetch, addJo
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table loading={loading} columns={columns} rowKey="id" dataSource={availableJobs} onChange={handleTableChange} />
|
||||||
loading={loading}
|
|
||||||
columns={columns}
|
|
||||||
mobileColumnKeys={["cieca_id", "job_id", "ownr_name", "vehicle_info", "actions"]}
|
|
||||||
rowKey="id"
|
|
||||||
dataSource={availableJobs}
|
|
||||||
onChange={handleTableChange}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,11 +141,13 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
|||||||
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
disabled={jobRO}
|
disabled={jobRO}
|
||||||
options={bodyshop.md_responsibility_centers.profits.map((p) => ({
|
>
|
||||||
value: p.name,
|
{bodyshop.md_responsibility_centers.profits.map((p) => (
|
||||||
label: p.name
|
<Select.Option key={p.name} value={p.name}>
|
||||||
}))}
|
{p.name}
|
||||||
/>
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -169,11 +171,13 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
|||||||
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
disabled={jobRO}
|
disabled={jobRO}
|
||||||
options={bodyshop.md_responsibility_centers.profits.map((p) => ({
|
>
|
||||||
value: p.name,
|
{bodyshop.md_responsibility_centers.profits.map((p) => (
|
||||||
label: p.name
|
<Select.Option key={p.name} value={p.name}>
|
||||||
}))}
|
{p.name}
|
||||||
/>
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMutation } from "@apollo/client/react";
|
|||||||
import { Button, Divider, Form, Input, Modal, Select, Space, Switch } from "antd";
|
import { Button, Divider, Form, Input, Modal, Select, Space, Switch } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { some } from "lodash";
|
import { some } from "lodash";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -19,10 +19,10 @@ import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
|||||||
import RREarlyROForm from "../dms-post-form/rr-early-ro-form";
|
import RREarlyROForm from "../dms-post-form/rr-early-ro-form";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
jobRO: selectJobReadOnly
|
jobRO: selectJobReadOnly
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -37,17 +37,16 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) {
|
export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [earlyRoCreated, setEarlyRoCreated] = useState(!!job?.dms_id); // Track early RO creation state
|
||||||
const [earlyRoCreated, setEarlyRoCreated] = useState(!!job?.dms_id);
|
const [earlyRoCreatedThisSession, setEarlyRoCreatedThisSession] = useState(false); // Track if created in THIS modal session
|
||||||
const [earlyRoCreatedThisSession, setEarlyRoCreatedThisSession] = useState(false);
|
|
||||||
|
|
||||||
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
|
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
const allFormValues = Form.useWatch([], form);
|
const allFormValues = Form.useWatch([], form);
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket(); // Extract socket from context
|
||||||
|
|
||||||
|
// Get Fortellis treatment for proper DMS mode detection
|
||||||
const {
|
const {
|
||||||
treatments: { Fortellis }
|
treatments: { Fortellis }
|
||||||
} = useTreatmentsWithConfig({
|
} = useTreatmentsWithConfig({
|
||||||
@@ -56,64 +55,16 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
splitKey: bodyshop?.imexshopid
|
splitKey: bodyshop?.imexshopid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if bodyshop has Reynolds integration using the proper getDmsMode function
|
||||||
const dmsMode = getDmsMode(bodyshop, Fortellis.treatment);
|
const dmsMode = getDmsMode(bodyshop, Fortellis.treatment);
|
||||||
const isReynoldsMode = dmsMode === DMS_MAP.reynolds;
|
const isReynoldsMode = dmsMode === DMS_MAP.reynolds;
|
||||||
|
|
||||||
const insuranceOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
(bodyshop?.md_ins_cos ?? []).map((s) => ({
|
|
||||||
value: s.name,
|
|
||||||
label: s.name
|
|
||||||
})),
|
|
||||||
[bodyshop?.md_ins_cos]
|
|
||||||
);
|
|
||||||
|
|
||||||
const classOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
(bodyshop?.md_classes ?? []).map((s) => ({
|
|
||||||
value: s,
|
|
||||||
label: s
|
|
||||||
})),
|
|
||||||
[bodyshop?.md_classes]
|
|
||||||
);
|
|
||||||
|
|
||||||
const referralOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
(bodyshop?.md_referral_sources ?? []).map((s) => ({
|
|
||||||
value: s,
|
|
||||||
label: s
|
|
||||||
})),
|
|
||||||
[bodyshop?.md_referral_sources]
|
|
||||||
);
|
|
||||||
|
|
||||||
const csrOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
(bodyshop?.employees ?? [])
|
|
||||||
.filter((emp) => emp.active)
|
|
||||||
.map((emp) => ({
|
|
||||||
value: emp.id,
|
|
||||||
label: `${emp.first_name} ${emp.last_name}`
|
|
||||||
})),
|
|
||||||
[bodyshop?.employees]
|
|
||||||
);
|
|
||||||
|
|
||||||
const categoryOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
(bodyshop?.md_categories ?? []).map((s) => ({
|
|
||||||
value: s,
|
|
||||||
label: s
|
|
||||||
})),
|
|
||||||
[bodyshop?.md_categories]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleConvert = async ({ employee_csr, category, ...values }) => {
|
const handleConvert = async ({ employee_csr, category, ...values }) => {
|
||||||
if (parentFormIsFieldsTouched()) {
|
if (parentFormIsFieldsTouched()) {
|
||||||
alert(t("jobs.labels.savebeforeconversion"));
|
alert(t("jobs.labels.savebeforeconversion"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const res = await mutationConvertJob({
|
const res = await mutationConvertJob({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
@@ -127,11 +78,13 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (values.ca_gst_registrant) {
|
if (values.ca_gst_registrant) {
|
||||||
await axios.post("/job/totalsssu", { id: job.id });
|
await axios.post("/job/totalsssu", {
|
||||||
|
id: job.id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res.errors) {
|
if (!res.errors) {
|
||||||
refetch?.();
|
refetch();
|
||||||
notification.success({
|
notification.success({
|
||||||
title: t("jobs.successes.converted")
|
title: t("jobs.successes.converted")
|
||||||
});
|
});
|
||||||
@@ -144,20 +97,19 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitDisabled = useCallback(() => some(allFormValues, (v) => v === undefined), [allFormValues]);
|
const submitDisabled = useCallback(() => some(allFormValues, (v) => v === undefined), [allFormValues]);
|
||||||
|
|
||||||
const handleEarlyROSuccess = (result) => {
|
const handleEarlyROSuccess = (result) => {
|
||||||
setEarlyRoCreated(true);
|
setEarlyRoCreated(true); // Mark early RO as created
|
||||||
setEarlyRoCreatedThisSession(true);
|
setEarlyRoCreatedThisSession(true); // Mark as created in this session
|
||||||
notification.success({
|
notification.success({
|
||||||
title: t("jobs.successes.early_ro_created"),
|
title: t("jobs.successes.early_ro_created"),
|
||||||
description: `RO Number: ${result.roNumber || "N/A"}`
|
description: `RO Number: ${result.roNumber || "N/A"}`
|
||||||
});
|
});
|
||||||
|
// Delay refetch to keep success message visible for 2 seconds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
refetch?.();
|
refetch?.();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@@ -178,28 +130,29 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
disabled={job.converted || jobRO}
|
disabled={job.converted || jobRO}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEarlyRoCreated(!!job?.dms_id);
|
setEarlyRoCreated(!!job?.dms_id); // Initialize state based on current job
|
||||||
setEarlyRoCreatedThisSession(false);
|
setEarlyRoCreatedThisSession(false); // Reset session state when opening modal
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("jobs.actions.convert")}
|
{t("jobs.actions.convert")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* Convert Job Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={handleModalClose}
|
onCancel={handleModalClose}
|
||||||
closable={!(earlyRoCreatedThisSession && !job.converted)}
|
closable={!(earlyRoCreatedThisSession && !job.converted)} // Only restrict if created in THIS session
|
||||||
mask={{ closable: !(earlyRoCreatedThisSession && !job.converted) }}
|
maskClosable={!(earlyRoCreatedThisSession && !job.converted)} // Only restrict if created in THIS session
|
||||||
title={t("jobs.actions.convert")}
|
title={t("jobs.actions.convert")}
|
||||||
footer={null}
|
footer={null}
|
||||||
width={700}
|
width={700}
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
|
{/* Standard Convert Form */}
|
||||||
<Form
|
<Form
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={form}
|
form={form}
|
||||||
preserve={false}
|
|
||||||
onFinish={handleConvert}
|
onFinish={handleConvert}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
driveable: true,
|
driveable: true,
|
||||||
@@ -211,6 +164,7 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
referral_source_extra: job.referral_source_extra ?? ""
|
referral_source_extra: job.referral_source_extra ?? ""
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Show Reynolds Early RO section at the top if applicable */}
|
||||||
{isReynoldsMode && !job.dms_id && !earlyRoCreated && (
|
{isReynoldsMode && !job.dms_id && !earlyRoCreated && (
|
||||||
<>
|
<>
|
||||||
<RREarlyROForm
|
<RREarlyROForm
|
||||||
@@ -227,78 +181,127 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name={["ins_co_nm"]}
|
name={["ins_co_nm"]}
|
||||||
label={t("jobs.fields.ins_co_nm")}
|
label={t("jobs.fields.ins_co_nm")}
|
||||||
rules={[{ required: true }]}
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select showSearch>
|
||||||
showSearch={{
|
{bodyshop.md_ins_cos.map((s, i) => (
|
||||||
optionFilterProp:'label'
|
<Select.Option key={i} value={s.name}>
|
||||||
}}
|
{s.name}
|
||||||
options={insuranceOptions}
|
</Select.Option>
|
||||||
/>
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{bodyshop.enforce_class && (
|
{bodyshop.enforce_class && (
|
||||||
<Form.Item name="class" label={t("jobs.fields.class")} rules={[{ required: bodyshop.enforce_class }]}>
|
<Form.Item
|
||||||
<Select options={classOptions} />
|
name={"class"}
|
||||||
|
label={t("jobs.fields.class")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: bodyshop.enforce_class
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
{bodyshop.md_classes.map((s) => (
|
||||||
|
<Select.Option key={s} value={s}>
|
||||||
|
{s}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{bodyshop.enforce_referral && (
|
{bodyshop.enforce_referral && (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="referral_source"
|
name={"referral_source"}
|
||||||
label={t("jobs.fields.referralsource")}
|
label={t("jobs.fields.referralsource")}
|
||||||
rules={[{ required: bodyshop.enforce_referral }]}
|
rules={[
|
||||||
|
{
|
||||||
|
required: bodyshop.enforce_referral
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Select options={referralOptions} />
|
<Select>
|
||||||
|
{bodyshop.md_referral_sources.map((s) => (
|
||||||
|
<Select.Option key={s} value={s}>
|
||||||
|
{s}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{bodyshop.enforce_conversion_csr && (
|
{bodyshop.enforce_conversion_csr && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="employee_csr"
|
name={"employee_csr"}
|
||||||
label={t(
|
label={t(
|
||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
imex: "jobs.fields.employee_csr",
|
imex: "jobs.fields.employee_csr",
|
||||||
rome: "jobs.fields.employee_csr_writer"
|
rome: "jobs.fields.employee_csr_writer"
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
rules={[{ required: bodyshop.enforce_conversion_csr }]}
|
rules={[
|
||||||
|
{
|
||||||
|
required: bodyshop.enforce_conversion_csr
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: 'label',
|
optionFilterProp: "children",
|
||||||
filterOption: (input, option) =>
|
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
|
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
|
>
|
||||||
options={csrOptions}
|
{bodyshop.employees
|
||||||
/>
|
.filter((emp) => emp.active)
|
||||||
|
.map((emp) => (
|
||||||
|
<Select.Option value={emp.id} key={emp.id} name={`${emp.first_name} ${emp.last_name}`}>
|
||||||
|
{`${emp.first_name} ${emp.last_name}`}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{bodyshop.enforce_conversion_category && (
|
{bodyshop.enforce_conversion_category && (
|
||||||
<Form.Item name="category" label={t("jobs.fields.category")} rules={[{ required: bodyshop.enforce_conversion_category }]}>
|
<Form.Item
|
||||||
<Select allowClear options={categoryOptions} />
|
name={"category"}
|
||||||
|
label={t("jobs.fields.category")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: bodyshop.enforce_conversion_category
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select allowClear>
|
||||||
|
{bodyshop.md_categories.map((s) => (
|
||||||
|
<Select.Option key={s} value={s}>
|
||||||
|
{s}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
||||||
<Form.Item label={t("jobs.fields.ca_gst_registrant")} name="ca_gst_registrant" valuePropName="checked">
|
<Form.Item label={t("jobs.fields.ca_gst_registrant")} name="ca_gst_registrant" valuePropName="checked">
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.driveable")} name="driveable" valuePropName="checked">
|
<Form.Item label={t("jobs.fields.driveable")} name="driveable" valuePropName="checked">
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.towin")} name="towin" valuePropName="checked">
|
<Form.Item label={t("jobs.fields.towin")} name="towin" valuePropName="checked">
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -313,7 +316,6 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
>
|
>
|
||||||
{t("jobs.actions.convert")}
|
{t("jobs.actions.convert")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button onClick={handleModalClose} disabled={earlyRoCreatedThisSession && !job.converted}>
|
<Button onClick={handleModalClose} disabled={earlyRoCreatedThisSession && !job.converted}>
|
||||||
{t("general.actions.close")}
|
{t("general.actions.close")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||||
<Select
|
<Select onChange={handleInsCoChange}>
|
||||||
onChange={handleInsCoChange}
|
{bodyshop.md_ins_cos.map((s) => (
|
||||||
options={bodyshop.md_ins_cos.map((s) => ({
|
<Select.Option key={s.name} value={s.name}>
|
||||||
value: s.name,
|
{s.name}
|
||||||
label: s.name
|
</Select.Option>
|
||||||
}))}
|
))}
|
||||||
/>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
||||||
<Input />
|
<Input />
|
||||||
@@ -192,12 +192,13 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.referralsource")} name="referral_source">
|
<Form.Item label={t("jobs.fields.referralsource")} name="referral_source">
|
||||||
<Select
|
<Select>
|
||||||
options={bodyshop.md_referral_sources.map((s) => ({
|
{bodyshop.md_referral_sources.map((s) => (
|
||||||
value: s,
|
<Select.Option key={s} value={s}>
|
||||||
label: s
|
{s}
|
||||||
}))}
|
</Select.Option>
|
||||||
/>
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
||||||
<Input />
|
<Input />
|
||||||
@@ -220,13 +221,10 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<CurrencyInput min={0} />
|
<CurrencyInput min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
||||||
<Select
|
<Select allowClear>
|
||||||
allowClear
|
<Select.Option value="W">{t("jobs.labels.deductible.waived")}</Select.Option>
|
||||||
options={[
|
<Select.Option value="Y">{t("jobs.labels.deductible.stands")}</Select.Option>
|
||||||
{ value: "W", label: t("jobs.labels.deductible.waived") },
|
</Select>
|
||||||
{ value: "Y", label: t("jobs.labels.deductible.stands") }
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.depreciation_taxes")} name="depreciation_taxes">
|
<Form.Item label={t("jobs.fields.depreciation_taxes")} name="depreciation_taxes">
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Card, Input } from "antd";
|
import { Card, Input, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
||||||
@@ -96,12 +95,11 @@ export default function JobsCreateOwnerInfoSearchComponent({ loading, owners })
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["ownr_ln", "ownr_ph1", "ownr_ph2", "ownr_fn"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={owners}
|
dataSource={owners}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { PlusOutlined, SearchOutlined } from "@ant-design/icons";
|
import { PlusOutlined, SearchOutlined } from "@ant-design/icons";
|
||||||
import { Button, Input, Popover } from "antd";
|
import { Button, Input, Popover, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import PredefinedVehicles from "./predefined-vehicles.js";
|
import PredefinedVehicles from "./predefined-vehicles.js";
|
||||||
@@ -23,9 +22,9 @@ export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
|
|||||||
|
|
||||||
const popContent = () => (
|
const popContent = () => (
|
||||||
<div>
|
<div>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
size="small"
|
size="small"
|
||||||
title={() => <Input.Search onSearch={(value) => setSearch(value)} enterButton />}
|
title={() => <Input.Search onSearch={(value) => setSearch(value)} enterButton/>}
|
||||||
dataSource={filteredPredefinedVehicles}
|
dataSource={filteredPredefinedVehicles}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
@@ -62,7 +61,6 @@ export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
mobileColumnKeys={["make", "model", "select"]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Card, Input, Space } from "antd";
|
import { Card, Input, Space, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -69,12 +68,11 @@ export default function JobsCreateVehicleInfoSearchComponent({ loading, vehicles
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
pagination={{ placement: "top" }}
|
pagination={{ placement: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["v_vin", "description", "plate"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={vehicles}
|
dataSource={vehicles}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -43,19 +43,20 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
||||||
<Select disabled={jobRO} options={[
|
<Select disabled={jobRO}>
|
||||||
{ value: "W", label: t("jobs.labels.deductible.waived") },
|
<Select.Option value="W">{t("jobs.labels.deductible.waived")}</Select.Option>
|
||||||
{ value: "Y", label: t("jobs.labels.deductible.stands") }
|
<Select.Option value="Y">{t("jobs.labels.deductible.stands")}</Select.Option>
|
||||||
]} />
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
||||||
<CurrencyInput disabled={jobRO} min={0} />
|
<CurrencyInput disabled={jobRO} min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ded_note")} name="ded_note">
|
<Form.Item label={t("jobs.fields.ded_note")} name="ded_note">
|
||||||
<Select disabled={jobRO} options={bodyshop.md_ded_notes.map((n) => ({
|
<Select disabled={jobRO}>
|
||||||
value: n,
|
{bodyshop.md_ded_notes.map((n, index) => (
|
||||||
label: n
|
<Select.Option key={index}>{n}</Select.Option>
|
||||||
}))} />
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
@@ -65,10 +66,13 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||||
<Select disabled={jobRO} onChange={handleInsCoChange} options={bodyshop.md_ins_cos.map((s) => ({
|
<Select disabled={jobRO} onChange={handleInsCoChange}>
|
||||||
value: s.name,
|
{bodyshop.md_ins_cos.map((s) => (
|
||||||
label: s.name
|
<Select.Option key={s.name} value={s.name}>
|
||||||
}))} />
|
{s.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
@@ -119,19 +123,25 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select disabled={jobRO} allowClear options={bodyshop.md_referral_sources.map((s) => ({
|
<Select disabled={jobRO} allowClear>
|
||||||
value: s,
|
{bodyshop.md_referral_sources.map((s) => (
|
||||||
label: s
|
<Select.Option key={s} value={s}>
|
||||||
}))} />
|
{s}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
|
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
|
||||||
<Select disabled={jobRO} allowClear options={bodyshop.appt_alt_transport.map((s) => ({
|
<Select disabled={jobRO} allowClear>
|
||||||
value: s,
|
{bodyshop.appt_alt_transport.map((s) => (
|
||||||
label: s
|
<Select.Option key={s} value={s}>
|
||||||
}))} />
|
{s}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
@@ -233,10 +243,13 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow header={t("jobs.forms.other")}>
|
<FormRow header={t("jobs.forms.other")}>
|
||||||
<Form.Item label={t("jobs.fields.category")} name="category">
|
<Form.Item label={t("jobs.fields.category")} name="category">
|
||||||
<Select disabled={jobRO} allowClear options={bodyshop.md_categories.map((s) => ({
|
<Select disabled={jobRO} allowClear>
|
||||||
value: s,
|
{bodyshop.md_categories.map((s) => (
|
||||||
label: s
|
<Select.Option key={s} value={s}>
|
||||||
}))} />
|
{s}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.selling_dealer")} name="selling_dealer">
|
<Form.Item label={t("jobs.fields.selling_dealer")} name="selling_dealer">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export function JobsDetailHeaderActions({
|
|||||||
okText,
|
okText,
|
||||||
cancelText,
|
cancelText,
|
||||||
centered: true,
|
centered: true,
|
||||||
mask: { closable: false },
|
maskClosable: false,
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
closeConfirmById(id);
|
closeConfirmById(id);
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
@@ -714,12 +714,13 @@ export function JobsDetailHeaderActions({
|
|||||||
<FormDateTimePickerComponent />
|
<FormDateTimePickerComponent />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("appointments.fields.color")} name="color">
|
<Form.Item label={t("appointments.fields.color")} name="color">
|
||||||
<Select
|
<Select>
|
||||||
options={bodyshop.appt_colors.map((col) => ({
|
{bodyshop.appt_colors.map((col, idx) => (
|
||||||
value: col.color.hex,
|
<Select.Option key={idx} value={col.color.hex}>
|
||||||
label: col.label
|
{col.label}
|
||||||
}))}
|
</Select.Option>
|
||||||
/>
|
))}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -133,16 +133,14 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
|||||||
</FormRow>
|
</FormRow>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
<Divider titlePlacement="left" orientation="horizontal" style={{ marginTop: ".8rem", float: "right" }}>
|
||||||
<FormRow
|
{t("jobs.forms.laborrates")}
|
||||||
extra={
|
</Divider>
|
||||||
<Space>
|
<Space>
|
||||||
<JobsDetailRatesChangeButton form={form} disabled={jobRO} />
|
<JobsDetailRatesChangeButton form={form} disabled={jobRO} />
|
||||||
{InstanceRenderManager({ imex: <JobsMarkPstExempt form={form} /> })}
|
{InstanceRenderManager({ imex: <JobsMarkPstExempt form={form} /> })}
|
||||||
</Space>
|
</Space>
|
||||||
}
|
<FormRow noDivider>
|
||||||
header={t("jobs.forms.laborrates")}
|
|
||||||
>
|
|
||||||
<Form.Item label={t("jobs.fields.labor_rate_desc")} name="labor_rate_desc">
|
<Form.Item label={t("jobs.fields.labor_rate_desc")} name="labor_rate_desc">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Checkbox, Divider, Input, Space } from "antd";
|
import { Button, Checkbox, Divider, Input, Space, Table } from "antd";
|
||||||
import ResponsiveTable from "../responsive-table/responsive-table.component";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -172,7 +171,7 @@ export default function JobsFindModalComponent({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ResponsiveTable
|
<Table
|
||||||
title={() => (
|
title={() => (
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
{t("jobs.labels.existing_jobs")}
|
{t("jobs.labels.existing_jobs")}
|
||||||
@@ -192,7 +191,6 @@ export default function JobsFindModalComponent({
|
|||||||
)}
|
)}
|
||||||
pagination={{ placement: "bottom" }}
|
pagination={{ placement: "bottom" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
mobileColumnKeys={["ro_number", "owner", "status", "vehicle"]}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={jobsListLoading}
|
loading={jobsListLoading}
|
||||||
dataSource={jobsList}
|
dataSource={jobsList}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user