Compare commits
162 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cac0f9594 | ||
|
|
9b67148522 | ||
|
|
6b501e4619 | ||
|
|
42f1d6fa13 | ||
|
|
3f247a9227 | ||
|
|
63b914731b | ||
|
|
23f8f69bbe | ||
|
|
fc3ea2bdf8 | ||
|
|
96e970faf7 | ||
|
|
c133195607 | ||
|
|
d75ea2b1a6 | ||
|
|
ec0fd840e4 | ||
|
|
971a81fc27 | ||
|
|
19050d31f7 | ||
|
|
e605433379 | ||
|
|
b9ebb70b7a | ||
|
|
79ed6f2388 | ||
|
|
785449a986 | ||
|
|
0b7d469e0e | ||
|
|
a57156756e | ||
|
|
c4c30d98d4 | ||
|
|
d4e8803b13 | ||
|
|
1f2786ddec | ||
|
|
e90cda07e4 | ||
|
|
9793daa04c | ||
|
|
117ced8fe7 | ||
|
|
e7909205d1 | ||
|
|
18028a70ab | ||
|
|
eeb8d8d26f | ||
|
|
23659fc412 | ||
|
|
ba65057782 | ||
|
|
60a859cac8 | ||
|
|
0cfe26093c | ||
|
|
d085a9c7c9 | ||
|
|
ec518a0593 | ||
|
|
1cd64ab6f1 | ||
|
|
26836f662a | ||
|
|
111f280674 | ||
|
|
6e88faa9d8 | ||
|
|
1ca8b2a78d | ||
|
|
cd2a7cad7f | ||
|
|
ed16156957 | ||
|
|
8dc1f7e08f | ||
|
|
2d3c13c587 | ||
|
|
5486907639 | ||
|
|
9233cef23a | ||
|
|
c16eafe892 | ||
|
|
b479684fe4 | ||
|
|
4201f61548 | ||
|
|
d04fc76840 | ||
|
|
0f84adc752 | ||
|
|
fbefd80959 | ||
|
|
6a691b54c8 | ||
|
|
fc75717d32 | ||
|
|
1459c6e993 | ||
|
|
f50292f9bf | ||
|
|
5b81912bd3 | ||
|
|
3c98a94c38 | ||
|
|
1d98de6d4d | ||
|
|
0ce5d9063a | ||
|
|
3b84e1d6ec | ||
|
|
d62f6e2116 | ||
|
|
71a26cc4ac | ||
|
|
32441e9406 | ||
|
|
e6dade1206 | ||
|
|
43d34cae07 | ||
|
|
a72a7948fe | ||
|
|
a24f6639a1 | ||
|
|
b2a0af32e9 | ||
|
|
cc58d14d32 | ||
|
|
9ce419b949 | ||
|
|
5053816be7 | ||
|
|
30ca34ea93 | ||
|
|
68d1a404b3 | ||
|
|
85e82b85ea | ||
|
|
23467280b4 | ||
|
|
aedad1c48f | ||
|
|
05cc4dd188 | ||
|
|
ea6351ea06 | ||
|
|
87d3ceb408 | ||
|
|
d08dd2b506 | ||
|
|
8a047d14a1 | ||
|
|
e103772aa4 | ||
|
|
c332699dc8 | ||
|
|
25e6e61d10 | ||
|
|
cdcd6b636a | ||
|
|
7879591bcf | ||
|
|
7fc6556866 | ||
|
|
3f5489ce7e | ||
|
|
5a90854861 | ||
|
|
8347a8c098 | ||
|
|
2bf074d85a | ||
|
|
50d47cd679 | ||
|
|
3a4e06eaa2 | ||
|
|
4be71726d4 | ||
|
|
c78db7eb08 | ||
|
|
e4dc711481 | ||
|
|
5114138c67 | ||
|
|
68b8743002 | ||
|
|
8f312bfffb | ||
|
|
7e7e109cfe | ||
|
|
05e5545466 | ||
|
|
ddb0990645 | ||
|
|
04dec6d91c | ||
|
|
a883b817b0 | ||
|
|
b7423aebf6 | ||
|
|
ee70aeb952 | ||
|
|
74d95e7cbb | ||
|
|
f6f6fab5ba | ||
|
|
699ffc822a | ||
|
|
4e35f5402c | ||
|
|
9b997d0924 | ||
|
|
d705f8211e | ||
|
|
03761bbb2a | ||
|
|
4d0794e90e | ||
|
|
e615c4a55b | ||
|
|
51eb3423f3 | ||
|
|
f6318666d9 | ||
|
|
544d4b8136 | ||
|
|
edf4846d55 | ||
|
|
f3754de843 | ||
|
|
3d920ad151 | ||
|
|
575f056360 | ||
|
|
716d9affb5 | ||
|
|
b01dd52da2 | ||
|
|
c75fddc2c0 | ||
|
|
db0c16f31d | ||
|
|
b286ab2439 | ||
|
|
fa57828ebd | ||
|
|
8052767002 | ||
|
|
932f572fb5 | ||
|
|
328a64eb90 | ||
|
|
c661fce8f1 | ||
|
|
60d1396011 | ||
|
|
3b647dfd37 | ||
|
|
50fe588949 | ||
|
|
0ced053d21 | ||
|
|
b8cf4a4d75 | ||
|
|
ff72657a82 | ||
|
|
92a96fdae6 | ||
|
|
b1a96d55ad | ||
|
|
49657816c6 | ||
|
|
7094b6ffbf | ||
|
|
ed7c2574eb | ||
|
|
45a9e37342 | ||
|
|
9e6a458203 | ||
|
|
55a279a700 | ||
|
|
82e2e332cf | ||
|
|
103d7c2bb2 | ||
|
|
f5f0b75617 | ||
|
|
c163554c3f | ||
|
|
bd75f593c2 | ||
|
|
fbc1866363 | ||
|
|
6480f7f2aa | ||
|
|
4cb3a79429 | ||
|
|
ca521eaeba | ||
|
|
f287ba2dac | ||
|
|
ff46bbbb3f | ||
|
|
9803841617 | ||
|
|
4dffbfe6fa | ||
|
|
1b5cddd371 | ||
|
|
6a7005299a |
24
.dockerignore
Normal file
24
.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
||||
# Directories to exclude
|
||||
.circleci
|
||||
.idea
|
||||
.platform
|
||||
.vscode
|
||||
_reference
|
||||
client
|
||||
redis/dockerdata
|
||||
hasura
|
||||
node_modules
|
||||
# Files to exclude
|
||||
.ebignore
|
||||
.editorconfig
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
.prettierrc.js
|
||||
Dockerfile
|
||||
README.MD
|
||||
bodyshop_translations.babel
|
||||
docker-compose.yml
|
||||
ecosystem.config.js
|
||||
|
||||
# Optional: Exclude logs and temporary files
|
||||
*.log
|
||||
0
.localstack/.gitkeep
Normal file
0
.localstack/.gitkeep
Normal file
@@ -1 +1,2 @@
|
||||
client_max_body_size 50M;
|
||||
client_max_body_size 50M;
|
||||
client_body_buffer_size 5M;
|
||||
|
||||
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -14,6 +14,21 @@
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceRoot}/client/src"
|
||||
},
|
||||
{
|
||||
"name": "Attach to Node.js in Docker",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"address": "localhost",
|
||||
"port": 9229,
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "/app",
|
||||
"protocol": "inspector",
|
||||
"restart": true,
|
||||
"sourceMaps": true,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
||||
# Use Amazon Linux 2023 as the base image
|
||||
FROM amazonlinux:2023
|
||||
|
||||
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
|
||||
RUN dnf install -y git \
|
||||
&& curl -sL https://rpm.nodesource.com/setup_20.x | bash - \
|
||||
&& dnf install -y nodejs \
|
||||
&& dnf clean all
|
||||
|
||||
|
||||
# Install dependencies required by node-canvas
|
||||
RUN dnf install -y \
|
||||
gcc \
|
||||
gcc-c++ \
|
||||
cairo-devel \
|
||||
pango-devel \
|
||||
libjpeg-turbo-devel \
|
||||
giflib-devel \
|
||||
libpng-devel \
|
||||
make \
|
||||
python3 \
|
||||
python3-pip \
|
||||
&& dnf clean all
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# This is because our test route uses a git commit hash
|
||||
RUN git config --global --add safe.directory /app
|
||||
|
||||
# Copy package.json and package-lock.json
|
||||
COPY package.json ./
|
||||
|
||||
# Install Nodemon
|
||||
RUN npm install -g nodemon
|
||||
|
||||
# Install dependencies
|
||||
RUN npm i --no-package-lock
|
||||
|
||||
# Copy the rest of your application code
|
||||
COPY . .
|
||||
|
||||
# Expose the port your app runs on (adjust if necessary)
|
||||
EXPOSE 4000 9229
|
||||
|
||||
# Start the application
|
||||
CMD ["nodemon", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]
|
||||
64
_reference/Documents/dockerreadme.md
Normal file
64
_reference/Documents/dockerreadme.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Setting up External Networking and Static IP for WSL2 using Hyper-V
|
||||
|
||||
This guide will walk you through the steps to configure your WSL2 (Windows Subsystem for Linux) instance to use an external Hyper-V virtual switch, enabling it to connect directly to your local network. Additionally, you'll learn how to assign a static IP address to your WSL2 instance.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Windows 11**
|
||||
2. **Docker Desktop For Windows (Latest Version)
|
||||
|
||||
# Docker Setup
|
||||
Inside the root of the project exists the `docker-compose.yaml` file, you can simply run
|
||||
`docker-compose up` to launch the backend.
|
||||
|
||||
Things to note:
|
||||
- When installing NPM packages, you will need to rebuild the `node-app` container
|
||||
- Making changes to the server files will restart the `node-app`
|
||||
|
||||
# Local Stack
|
||||
- LocalStack Front end (Optional) - https://apps.microsoft.com/detail/9ntrnft9zws2?hl=en-us&gl=US
|
||||
- http://localhost:4566/_aws/ses will allow you to see emails sent
|
||||
|
||||
# Docker Commands
|
||||
|
||||
## General `docker-compose` Commands:
|
||||
1. Bring up the services, force a rebuild of all services, and do not use the cache: `docker-compose up --build --no-cache`
|
||||
2. Start Containers in Detached Mode: This will run the containers in the background (detached mode): `docker-compose up -d`
|
||||
3. Stop and Remove Containers: Stops and removes the containers gracefully: `docker-compose down`
|
||||
4. Stop containers without removing them: `docker-compose stop`
|
||||
5. Remove Containers, Volumes, and Networks: `docker-compose down --volumes`
|
||||
6. Force rebuild of containers: `docker-compose build --no-cache`
|
||||
7. View running Containers: `docker-compose ps`
|
||||
8. View a specific containers logs: `docker-compose logs <container-name>`
|
||||
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
|
||||
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
|
||||
|
||||
## Volume Management Commands
|
||||
1. List Docker volumes: `docker volume ls`
|
||||
2. Remove Unused volumes `docker volume prune`
|
||||
3. Remove specific volumes `docker volume rm <volume-name>`
|
||||
4. Inspect a volume: `docker volume inspect <volume-name>`
|
||||
|
||||
## Container Image Management Commands:
|
||||
1. List running containers: `docker ps`
|
||||
2. List all containers: `docker os -a`
|
||||
3. Remove Stopped containers: `docker container prune`
|
||||
4. Remove a specific container: `docker container rm <container-name>`
|
||||
5. Remove a specific image: `docker rmi <image-name>:<version>`
|
||||
6. Remove all unused images: `docker image prune -a`
|
||||
|
||||
## Network Management Commands:
|
||||
1. List networks: `docker network ls`
|
||||
2. Inspect a specific network: `docker network inspect <network-name>`
|
||||
3. Remove a specific network: `docker network rm <network-name>`
|
||||
4. Remove unused networks: `docker network prune`
|
||||
|
||||
## Debugging and maintenance:
|
||||
1. Enter a Running container: `docker exec -it <container name> /bin/bash` (could also be `/bin/sh` or for example `redis-cli` on a redis node)
|
||||
2. View container resource usage: `docker stats`
|
||||
3. Check Disk space used by Docker: `docker system df`
|
||||
4. Remove all unused Data (Nuclear option): `docker system prune`
|
||||
|
||||
## Specific examples
|
||||
1. To simulate a Clean state, one should run `docker system prune` followed by `docker volume prune -a`
|
||||
2. You can run `docker-compose up` without the `-d` option, and you will get what is identical to the experience you were used to, this includes being able to control-c and bring the entire stack down
|
||||
1
_reference/localEmailViewer/.gitignore
vendored
Normal file
1
_reference/localEmailViewer/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
7
_reference/localEmailViewer/README.md
Normal file
7
_reference/localEmailViewer/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
This will connect to your dockers local stack session and render the email in HTML.
|
||||
|
||||
```shell
|
||||
node index.js
|
||||
```
|
||||
|
||||
http://localhost:3334
|
||||
116
_reference/localEmailViewer/index.js
Normal file
116
_reference/localEmailViewer/index.js
Normal file
@@ -0,0 +1,116 @@
|
||||
// index.js
|
||||
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import {simpleParser} from 'mailparser';
|
||||
|
||||
const app = express();
|
||||
const PORT = 3334;
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:4566/_aws/ses');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
const messagesHtml = await parseMessages(data.messages);
|
||||
res.send(renderHtml(messagesHtml));
|
||||
} catch (error) {
|
||||
console.error('Error fetching messages:', error);
|
||||
res.status(500).send('Error fetching messages');
|
||||
}
|
||||
});
|
||||
|
||||
async function parseMessages(messages) {
|
||||
const parsedMessages = await Promise.all(
|
||||
messages.map(async (message, index) => {
|
||||
try {
|
||||
const parsed = await simpleParser(message.RawData);
|
||||
return `
|
||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: lightgray">
|
||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: white">
|
||||
<div class="mb-2">
|
||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">From:</span> ${message.Source}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Region:</span> ${message.Region}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
||||
</div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
${parsed.html || parsed.textAsHtml || 'No HTML content available'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error('Error parsing email:', error);
|
||||
return `
|
||||
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
||||
<div class="mb-2">
|
||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">From:</span> ${message.Source}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Region:</span> ${message.Region}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
||||
</div>
|
||||
<div class="text-red-500">
|
||||
Error parsing email content
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})
|
||||
);
|
||||
return parsedMessages.join('');
|
||||
}
|
||||
|
||||
function renderHtml(messagesHtml) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email Messages Viewer</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f4f6;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.prose {
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container bg-white shadow-lg rounded-lg p-6">
|
||||
<h1 class="text-2xl font-bold text-center mb-6">Email Messages Viewer</h1>
|
||||
<div id="messages-container">
|
||||
${messagesHtml}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on http://localhost:${PORT}`);
|
||||
});
|
||||
1214
_reference/localEmailViewer/package-lock.json
generated
Normal file
1214
_reference/localEmailViewer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
_reference/localEmailViewer/package.json
Normal file
18
_reference/localEmailViewer/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "localemailviewer",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"express": "^4.21.1",
|
||||
"mailparser": "^3.7.1",
|
||||
"node-fetch": "^3.3.2"
|
||||
}
|
||||
}
|
||||
20
certs/cert.pem
Normal file
20
certs/cert.pem
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWzCCAkOgAwIBAgIUD/QBSAXy/AlJ/cS4DaPWJLpChxgwDQYJKoZIhvcNAQEL
|
||||
BQAwPTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSEwHwYDVQQKDBhJbnRlcm5l
|
||||
dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjQwOTA5MTU0MjA1WhcNMjUwOTA5MTU0MjA1
|
||||
WjA9MQswCQYDVQQGEwJDQTELMAkGA1UECAwCT04xITAfBgNVBAoMGEludGVybmV0
|
||||
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AKSd0l7NJCNBwvtPU+dVPQkteg0AfC3sGqRnZMQteCRVa2oIgC4NoF3A9BK/yHbF
|
||||
ZF25OnXTck5vzc8yb3v73ndfTD9ASKNoiaZE84/GFBsxqlKR8cs0qVwzuAsdijMv
|
||||
vlMPNlMRyE1Rb7nR6HXGkPXNyxgMko03NXPkvIje9zRudm0Lf8L4q/hPyPkS7Mrm
|
||||
/uQfAAJe+xFcupkEX2XY7r0x1C+z6E8lA1UcuhK3SHdW7CWYqp1vU5/dnnUiXwCa
|
||||
GiC6Y1bCJB0pDAVISzy3JUDdINZdiqGR+y8ho3pstChf2mp/76s3N9eG9KA/qaFK
|
||||
BrGk2PvCoZ8/Aj1aMsRYFHECAwEAAaNTMFEwHQYDVR0OBBYEFDLJ2fbWP4VUJgOp
|
||||
PSs+NGHcVgRmMB8GA1UdIwQYMBaAFDLJ2fbWP4VUJgOpPSs+NGHcVgRmMA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABfv5ut/y03atq0NMB0jeDY4
|
||||
AvW4ukk0k1svyqxFZCw9o7m2lHb/IjmVrZG1Sj4JWrrSv0s02ccb26/t6vazNa5L
|
||||
Powe3eyfHgfjTZJmgs8hyeMwKS0wWk/SPuu9JDhIJakiquqD+UVBGkHpP+XYvhDv
|
||||
vhS2XRlW+aEjpUmr1oCyyrc6WbzrYRNadqEsn/AxwcMyUbht3Ugjkg+OpidcTIQp
|
||||
5lv63waKo6I1vQofzBQ3L7JYsKo8kC0vAP7wkLxvzBii335uZJzzpFYFVOyVNezi
|
||||
dJdazPbRYbXz4LjltdEn/SNfRuKX8ZRiN2OSo7OfSrZaMTS87SfCSFJGgQM8Yrk=
|
||||
-----END CERTIFICATE-----
|
||||
27
certs/id_rsa
Normal file
27
certs/id_rsa
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAQEAvNl5fuVmLNv72BZNxnTqX5CHf5Xi8UxjYaYxHITSCx7blnhpVYLd
|
||||
qXvcOWXzbsfjch/den73QiW4n2FYz75oGMhUGlOYzdWKA9I9Sj09Qy1R06RhwDiZGd5qaM
|
||||
swEeXpkNmi2u4Qd2kJeDfUQUigjC09V81O/vrniGtQAJScfiG/itdm+Ufn09Z4MYk0HWjq
|
||||
iDokNEskoEPsibYIrb+Q6vdtuPkZO+wU/smXhPtgw5ST6oQdmm/gVNsRg5XNzxrire+z1G
|
||||
WatnnVL3hPnnfpnf8W589dyms7GGJwhPerSGTN1bn0T4+9C69Cd7LBJtxiuFdRmdlGLLLP
|
||||
RR48Rur71wAAA9AEfVsdBH1bHQAAAAdzc2gtcnNhAAABAQC82Xl+5WYs2/vYFk3GdOpfkI
|
||||
d/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN
|
||||
1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1
|
||||
AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DD
|
||||
lJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3Vuf
|
||||
RPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvXAAAAAwEAAQAAAQAQTosSLQbMmtY9S3e9
|
||||
yjyusdExcCTfhyQRu4MEHmfws+JsNMuLqbgwOVTD1AzYJQR7x0qdmDcLjCxL/uDnV16vvS
|
||||
Sd/Vf1dhnryIyoS29tzI0DRG94ZKq7tBvmHp1w/jRT4KcSVnovhW9e5Rs74+SRFhr06PKI
|
||||
S+wQOIv48Nwue9+QUMsMCpWgKXHx7SHNTHvnAfqdhi9O29SWlMA+v+mELZ5Cl+HU0UTt2I
|
||||
A1BxOe1N8FjN7KE2viJexsl3is1PuqMkpLl/wyHBJTVzUadl6DRALJQIm7/YO5goE72YOV
|
||||
Lpo27do3zjhC87dlKdATvZUzfKV0LuUVdxq/PNDZMUbBAAAAgQDShAqDZiDrdTUaGXfUVm
|
||||
QzcnVNbh2/KgZh4uux9QNHST562W6cnN7qxoRwVrM4BCOk1Kl73QQZW4nDvXX3PVC5j038
|
||||
8AXkcBHS9j9f4h72ue7D2jqlbHFa7aGU9zYgk9mbBF+GX3tDntkAIQjLtwOLfj1iiJ/clX
|
||||
mHFUAY1V4L8AAAAIEA3E4t/v0yU5D9AOI0r17UNYqfeyDoKAEDR4QbbFjO1l0kLnEJy7Zx
|
||||
Mhj18GilYg2y0P0v8dSM/oWXS8Hua2t5i9Exlv6gHhGlQ80mwYcVGIxewZ/pPeCPw0U+kt
|
||||
EKUjt09m9Oe7+6xHQsTBj9hY8/vqPmQwRalZFcLdhHiDiVKTcAAACBANtykaPXdVzEFx7D
|
||||
UOlsjVL7zM0EVOFXf9JJQ6BhazhmsEI2PYt3IpgGMo8cXkoUofAOIYjf421AabN1BqSO5J
|
||||
XTMxM0ZV3JmLLi804Mu9h1iFrVTBdLYOMJdc2VCo1EwHWpo9SXOyjxce/znvcIOU04aZhu
|
||||
TaPg816X+E+gw5JhAAAAFGRhdmVARGF2ZVJpY2hlci1JTUVYAQIDBAUG
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
certs/id_rsa.pub
Normal file
1
certs/id_rsa.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC82Xl+5WYs2/vYFk3GdOpfkId/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DDlJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3VufRPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvX dave@DaveRicher-IMEX
|
||||
28
certs/key.pem
Normal file
28
certs/key.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkndJezSQjQcL7
|
||||
T1PnVT0JLXoNAHwt7BqkZ2TELXgkVWtqCIAuDaBdwPQSv8h2xWRduTp103JOb83P
|
||||
Mm97+953X0w/QEijaImmRPOPxhQbMapSkfHLNKlcM7gLHYozL75TDzZTEchNUW+5
|
||||
0eh1xpD1zcsYDJKNNzVz5LyI3vc0bnZtC3/C+Kv4T8j5EuzK5v7kHwACXvsRXLqZ
|
||||
BF9l2O69MdQvs+hPJQNVHLoSt0h3VuwlmKqdb1Of3Z51Il8AmhogumNWwiQdKQwF
|
||||
SEs8tyVA3SDWXYqhkfsvIaN6bLQoX9pqf++rNzfXhvSgP6mhSgaxpNj7wqGfPwI9
|
||||
WjLEWBRxAgMBAAECggEAUNpHYlLFxh9dokujPUMreF+Cy/IKDBAkQc2au5RNpyLh
|
||||
YDIOqw/8TTAhcTgLQPLQygvZP9f8E7RsVLFD+pSJ/v2qmIJ9au1Edor1Sg+S/oxV
|
||||
SLrwFMunx2aLpcH7iAqSI3+cQg7A3+D4zD7iOz6tIl3Su9wo+v073tFhHKTOrEwv
|
||||
Qgr9Jf3viIiKV1ym+uQEVQndocfsj46FnFpXTQ2qs7kAF6FgAOLDGfQQwzkiqEBD
|
||||
NsqsDmbYIx6foZL+DEz1ZVO2M5B+xxpbNK82KwuQilVpimW8ui4LZHCe+RIFzt9+
|
||||
BK6KGlLpSEwTFliivI3nahy18JzskZsfyah0CPZlQQKBgQDVv+A0qIPGvOP3Sx+9
|
||||
HyeQCV23SkvvSvw8p8pMB0gvwv63YdJ7N/rJzBGS6YUHFWWZZgEeTgkJ6VJvoe0r
|
||||
8JL1el9uSUa7f0eayjmFBOGuzpktNVdIn2Tg7A9MWA4JqPNNC69RMOh86ewGD4J3
|
||||
a8Hz2a1bHxAmy/AZt2ukypY6eQKBgQDFJ7kqeOPkRBz9WbALRgVIXo8YWf5di0sQ
|
||||
r0HC03GAISHQ725A2IFBPHJWeqj0jaMiIZD0y+Obgp7KAskrJaLfsd7Ug775kFfw
|
||||
oUI9UAl6kRuPKvm3BaVAm46SQm+56VsgxTi73YN0NUp75THHZgAJjepF9zSpVJxq
|
||||
VY9DjEGruQKBgQCQCpGIatcCol/tUg69X7VFd0pULhkl1J5OMbQ9r9qRdRI5eg5h
|
||||
QsQaIQ7mtb8TmvOwf/DY/zVQHI+U8sXlCmW+TwzoQTENQSR7xzMj1LpRFqBaustr
|
||||
AR72A537kItFLzll/i3SxOam5uxK2UDOQSuerF4KPdCglGXkrpo3nt3F4QKBgQCa
|
||||
RArPAOjQo7tLQfJN3+wiRFsTYtd1uphx5bA/EdOtvj8HjVFnzADXWsTchf3N3UXY
|
||||
XwtdgGwIMpys1KEz8a8P+c2x26SDAj7NOmDqOMYx8Xju/WGHpBM6Cn30U6e4gK+d
|
||||
ZLSPyzQgqdIuP5hDvbwpvbGiLVw3Ys1BJtGCuSxpgQJ/eHnRiuSi5Zq5jGg+GpA+
|
||||
FEEc9NCy772rR+4uzEOqyIwqewffqzSuVWuIsY/8MP3fh+NDxl/mU6cB5QVeD54Z
|
||||
JZUKwmpM26muiM6WvVnM4ExPdSGA2+l4pZjby/KKd6F/w0tgZ1jb9Pb2/0vN3qVA
|
||||
Y4U4XNTMt2fxUACqiL4SHA==
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
|
||||
@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=PROMANAGER
|
||||
|
||||
@@ -9,7 +9,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_COUNTRY=USA
|
||||
|
||||
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
/dev-dist
|
||||
|
||||
@@ -12,6 +12,6 @@ module.exports = defineConfig({
|
||||
setupNodeEvents(on, config) {
|
||||
return require("./cypress/plugins/index.js")(on, config);
|
||||
},
|
||||
baseUrl: "http://localhost:3000"
|
||||
baseUrl: "https://localhost:3000"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<meta name="theme-color" content="#1690ff"/>
|
||||
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
||||
<!-- TODO:AIo Update the individual logos for each.-->
|
||||
<link rel="apple-touch-icon" href="public/logo192.png"/>
|
||||
<link rel="apple-touch-icon" href="/logo192.png"/>
|
||||
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
|
||||
2123
client/package-lock.json
generated
2123
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,37 +9,37 @@
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@ant-design/pro-layout": "^7.19.12",
|
||||
"@apollo/client": "^3.11.4",
|
||||
"@emotion/is-prop-valid": "^1.3.0",
|
||||
"@fingerprintjs/fingerprintjs": "^4.4.3",
|
||||
"@apollo/client": "^3.11.8",
|
||||
"@emotion/is-prop-valid": "^1.3.1",
|
||||
"@fingerprintjs/fingerprintjs": "^4.5.0",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@sentry/cli": "^2.33.1",
|
||||
"@sentry/cli": "^2.36.2",
|
||||
"@sentry/react": "^7.114.0",
|
||||
"@splitsoftware/splitio-react": "^1.12.1",
|
||||
"@splitsoftware/splitio-react": "^1.13.0",
|
||||
"@tanem/react-nprogress": "^5.0.51",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"antd": "^5.20.1",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^3.3.0",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.7.4",
|
||||
"axios": "^1.7.7",
|
||||
"classnames": "^2.5.1",
|
||||
"css-box-model": "^1.2.1",
|
||||
"dayjs": "^1.11.12",
|
||||
"dayjs": "^1.11.13",
|
||||
"dayjs-business-days2": "^1.2.2",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^10.12.5",
|
||||
"firebase": "^10.13.2",
|
||||
"graphql": "^16.9.0",
|
||||
"i18next": "^23.12.3",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"libphonenumber-js": "^1.11.5",
|
||||
"libphonenumber-js": "^1.11.9",
|
||||
"logrocket": "^8.1.2",
|
||||
"markerjs2": "^2.32.1",
|
||||
"markerjs2": "^2.32.2",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-url": "^8.0.1",
|
||||
"object-hash": "^3.0.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"query-string": "^9.1.0",
|
||||
"raf-schd": "^4.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-big-calendar": "^1.13.2",
|
||||
"react-big-calendar": "^1.14.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^7.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -58,15 +58,15 @@
|
||||
"react-icons": "^5.3.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-number-format": "^5.4.0",
|
||||
"react-number-format": "^5.4.2",
|
||||
"react-popopo": "^2.1.9",
|
||||
"react-product-fruits": "^2.2.6",
|
||||
"react-product-fruits": "^2.2.61",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-resizable": "^3.0.5",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"react-virtuoso": "^4.10.1",
|
||||
"react-virtuoso": "^4.10.4",
|
||||
"recharts": "^2.12.7",
|
||||
"redux": "^5.0.1",
|
||||
"redux-actions": "^3.0.3",
|
||||
@@ -74,12 +74,12 @@
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.77.8",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"styled-components": "^6.1.12",
|
||||
"sass": "^1.79.3",
|
||||
"socket.io-client": "^4.8.0",
|
||||
"styled-components": "^6.1.13",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"use-memo-one": "^1.1.3",
|
||||
"userpilot": "^1.3.5",
|
||||
"userpilot": "^1.3.6",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
"web-vitals": "^3.5.2"
|
||||
},
|
||||
@@ -91,6 +91,9 @@
|
||||
"start:imex": "dotenvx run --env-file=.env.development.imex -- vite",
|
||||
"start:rome": "dotenvx run --env-file=.env.development.rome -- vite",
|
||||
"start:promanager": "dotenvx run --env-file=.env.development.promanager -- vite",
|
||||
"preview:imex": "dotenvx run --env-file=.env.development.imex -- vite preview",
|
||||
"preview:rome": "dotenvx run --env-file=.env.development.rome -- vite preview",
|
||||
"preview:promanager": "dotenvx run --env-file=.env.development.promanager -- vite preview",
|
||||
"build:test:imex": "env-cmd -f .env.test.imex npm run build",
|
||||
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
|
||||
"build:test:promanager": "env-cmd -f .env.test.promanager npm run build",
|
||||
@@ -131,29 +134,29 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@dotenvx/dotenvx": "^1.7.0",
|
||||
"@dotenvx/dotenvx": "^1.14.1",
|
||||
"@emotion/babel-plugin": "^11.12.0",
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@sentry/webpack-plugin": "^2.22.2",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@sentry/webpack-plugin": "^2.22.4",
|
||||
"@testing-library/cypress": "^10.0.2",
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"chalk": "^5.3.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.13.3",
|
||||
"cypress": "^13.14.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"memfs": "^4.11.1",
|
||||
"memfs": "^4.12.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"react-error-overlay": "6.0.11",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^5.4.0",
|
||||
"vite": "^5.4.7",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-pwa": "^0.20.1",
|
||||
"vite-plugin-pwa": "^0.20.5",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"workbox-window": "^7.1.0"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import { ApolloProvider } from "@apollo/client";
|
||||
import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import dayjs from "../utils/day";
|
||||
import "dayjs/locale/en";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
@@ -19,8 +17,6 @@ if (import.meta.env.DEV) {
|
||||
Userpilot.initialize("NX-69145f08");
|
||||
}
|
||||
|
||||
dayjs.locale("en");
|
||||
|
||||
const config = {
|
||||
core: {
|
||||
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
||||
|
||||
@@ -21,6 +21,7 @@ import "./App.styles.scss";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||
@@ -201,7 +202,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
path="/manage/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
<SocketProvider bodyshop={bodyshop}>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
</SocketProvider>
|
||||
</ErrorBoundary>
|
||||
}
|
||||
>
|
||||
@@ -211,7 +214,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
path="/tech/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
<SocketProvider bodyshop={bodyshop}>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
</SocketProvider>
|
||||
</ErrorBoundary>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
|
||||
@@ -175,7 +175,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
@@ -177,7 +177,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Button, Card, Input, Space, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
||||
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import JobMarkSelectedExported from "../jobs-mark-selected-exported/jobs-mark-selected-exported";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
@@ -201,7 +201,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top" }}
|
||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -7,10 +7,10 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CiecaSelect from "../../utils/Ciecaselect";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -72,7 +72,14 @@ export function BillEnterModalLinesComponent({
|
||||
<BillLineSearchSelect
|
||||
disabled={disabled}
|
||||
options={lineData}
|
||||
style={{ width: "100%", minWidth: "10rem" }}
|
||||
style={{
|
||||
width: "20rem",
|
||||
maxWidth: "20rem",
|
||||
minWidth: "10rem",
|
||||
whiteSpace: "normal",
|
||||
height: "auto",
|
||||
minHeight: "32px" // default height of Ant Design inputs
|
||||
}}
|
||||
allowRemoved={form.getFieldValue("is_credit_memo") || false}
|
||||
onSelect={(value, opt) => {
|
||||
setFieldsValue({
|
||||
@@ -105,7 +112,7 @@ export function BillEnterModalLinesComponent({
|
||||
title: t("billlines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
editable: true,
|
||||
|
||||
width: "20rem",
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}line_desc`,
|
||||
@@ -119,7 +126,7 @@ export function BillEnterModalLinesComponent({
|
||||
]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Input disabled={disabled} />
|
||||
formInput: (record, index) => <Input.TextArea disabled={disabled} autoSize />
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.quantity"),
|
||||
|
||||
@@ -11,7 +11,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
showSearch
|
||||
popupMatchSelectWidth={false}
|
||||
popupMatchSelectWidth={true}
|
||||
optionLabelProp={"name"}
|
||||
// optionFilterProp="line_desc"
|
||||
filterOption={(inputValue, option) => {
|
||||
@@ -43,7 +43,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
|
||||
label: (
|
||||
<>
|
||||
<div style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
||||
<span>
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
@@ -57,7 +57,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}))
|
||||
]}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { WarningFilled } from "@ant-design/icons";
|
||||
import { Form, Input, InputNumber, Space } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
//import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
|
||||
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
|
||||
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
|
||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import {
|
||||
default as DateTimePicker,
|
||||
default as FormDateTimePicker
|
||||
} from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
@@ -18,10 +20,10 @@ import ContractFormJobPrefill from "./contract-form-job-prefill.component";
|
||||
export default function ContractFormComponent({ form, create = false, selectedJobState, selectedCar }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<FormFieldsChanged form={form} />
|
||||
<>
|
||||
{!create && <FormFieldsChanged form={form} />}
|
||||
<LayoutFormRow>
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item
|
||||
label={t("contracts.fields.status")}
|
||||
name="status"
|
||||
@@ -50,7 +52,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<Form.Item label={t("contracts.fields.scheduledreturn")} name="scheduledreturn">
|
||||
<FormDateTimePicker />
|
||||
</Form.Item>
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item label={t("contracts.fields.actualreturn")} name="actualreturn">
|
||||
<FormDateTimePicker />
|
||||
</Form.Item>
|
||||
@@ -122,7 +124,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
}}
|
||||
</Form.Item>
|
||||
)}
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item label={t("contracts.fields.kmend")} name="kmend">
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
@@ -145,25 +147,21 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
>
|
||||
<CourtesyCarFuelSlider />
|
||||
</Form.Item>
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item label={t("contracts.fields.fuelin")} name="fuelin" span={8}>
|
||||
<CourtesyCarFuelSlider />
|
||||
</Form.Item>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<div>
|
||||
<Space wrap>
|
||||
{selectedJobState && (
|
||||
<div>
|
||||
<ContractFormJobPrefill jobId={selectedJobState && selectedJobState[0]} form={form} />
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
//<ContractLicenseDecodeButton form={form} />
|
||||
}
|
||||
</Space>
|
||||
</div>
|
||||
<LayoutFormRow header={t("contracts.labels.driverinformation")}>
|
||||
<Space wrap>
|
||||
{create && selectedJobState && (
|
||||
<ContractFormJobPrefill jobId={selectedJobState && selectedJobState[0]} form={form} />
|
||||
)}
|
||||
{/* {<ContractLicenseDecodeButton form={form} />} */}
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow noDivider={true}>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.driver_dlnumber")}
|
||||
name="driver_dlnumber"
|
||||
@@ -183,9 +181,8 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
const dlExpiresBeforeReturn = dayjs(form.getFieldValue("driver_dlexpiry")).isBefore(
|
||||
dayjs(form.getFieldValue("scheduledreturn"))
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.driver_dlexpiry")}
|
||||
name="driver_dlexpiry"
|
||||
@@ -204,11 +201,10 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<span>{t("contracts.labels.dlexpirebeforereturn")}</span>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("contracts.fields.driver_dlst")} name="driver_dlst">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
@@ -315,6 +311,6 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,8 +40,6 @@ export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "TRACE":
|
||||
return "pink";
|
||||
case "DEBUG":
|
||||
return "orange";
|
||||
case "INFO":
|
||||
|
||||
@@ -8,7 +8,7 @@ import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { acceptEula } from "../../redux/user/user.actions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
import "./eula.styles.scss";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
@@ -208,7 +208,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) => {
|
||||
if (day(value).isSame(day(), "day")) {
|
||||
if (dayjs(value).isSame(dayjs(), "day")) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(t("eula.messages.date_accepted")));
|
||||
|
||||
@@ -4,19 +4,37 @@ import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dayjs from "../../utils/day";
|
||||
import { fuzzyMatchDate } from "./formats.js";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const DateTimePicker = ({
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
id,
|
||||
onlyFuture,
|
||||
onlyToday,
|
||||
isDateOnly = false,
|
||||
bodyshop,
|
||||
...restProps
|
||||
}) => {
|
||||
const [isManualInput, setIsManualInput] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newDate) => {
|
||||
if (!newDate) return;
|
||||
if (onChange) {
|
||||
onChange(newDate || null);
|
||||
onChange(bodyshop?.timezone ? dayjs(newDate).tz(bodyshop.timezone, true) : newDate);
|
||||
}
|
||||
setIsManualInput(false);
|
||||
},
|
||||
[onChange]
|
||||
[onChange, bodyshop?.timezone]
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
@@ -102,4 +120,4 @@ DateTimePicker.propTypes = {
|
||||
isDateOnly: PropTypes.bool
|
||||
};
|
||||
|
||||
export default React.memo(DateTimePicker);
|
||||
export default connect(mapStateToProps, null)(DateTimePicker);
|
||||
|
||||
@@ -3,13 +3,15 @@ import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
export default function GlobalSearchOs() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(false);
|
||||
|
||||
@@ -177,7 +179,18 @@ export default function GlobalSearchOs() {
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
|
||||
<AutoComplete
|
||||
options={data}
|
||||
onSearch={handleSearch}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const firstUrlForSearch = data?.[0]?.options?.[0]?.label?.props?.to;
|
||||
if (!firstUrlForSearch) return;
|
||||
navigate(firstUrlForSearch);
|
||||
}}
|
||||
defaultActiveFirstOption
|
||||
onClear={() => setData([])}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AutoComplete, Divider, Input, Space } from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -13,6 +13,7 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
|
||||
export default function GlobalSearch() {
|
||||
const { t } = useTranslation();
|
||||
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const executeSearch = (v) => {
|
||||
if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v);
|
||||
@@ -20,7 +21,6 @@ export default function GlobalSearch() {
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
console.log("Handle Search");
|
||||
debouncedExecuteSearch({ variables: { search: value } });
|
||||
};
|
||||
|
||||
@@ -156,7 +156,17 @@ export default function GlobalSearch() {
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
|
||||
<AutoComplete
|
||||
options={options}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const firstUrlForSearch = options?.[0]?.options?.[0]?.label?.props?.to;
|
||||
if (!firstUrlForSearch) return;
|
||||
navigate(firstUrlForSearch);
|
||||
}}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -116,18 +116,15 @@ function Header({
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const deleteBetaCookie = () => {
|
||||
const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
|
||||
if (cookieExists) {
|
||||
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
|
||||
console.log(`betaSwitchImex cookie deleted`);
|
||||
} else {
|
||||
console.log(`betaSwitchImex cookie does not exist`);
|
||||
}
|
||||
};
|
||||
|
||||
deleteBetaCookie();
|
||||
// const deleteBetaCookie = () => {
|
||||
// const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
|
||||
// if (cookieExists) {
|
||||
// const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
// document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// deleteBetaCookie();
|
||||
|
||||
const accountingChildren = [];
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import ScheduleEventColor from "./schedule-event.color.component";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -127,6 +128,9 @@ export function ScheduleEventComponent({
|
||||
{(event.job && event.job.alt_transport) || ""}
|
||||
<ScheduleAtChange job={event && event.job} />
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.comment")} valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
<ProductionListColumnComment record={event && event.job} />
|
||||
</DataLabel>
|
||||
<ScheduleEventNote event={event} />
|
||||
</div>
|
||||
) : (
|
||||
@@ -316,6 +320,7 @@ export function ScheduleEventComponent({
|
||||
})`}
|
||||
|
||||
{event.job && event.job.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>}
|
||||
{event?.job?.comment && `C: ${event.job.comment}`}
|
||||
</Space>
|
||||
) : (
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import axios from "axios";
|
||||
import { Badge, Card, Space, Table, Tag } from "antd";
|
||||
import { gql, useQuery } from "@apollo/client";
|
||||
@@ -72,7 +72,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
render: (text) => DateTimeFormatterFunction(text),
|
||||
sorter: (a, b) => day(a.start).unix() - day(b.start).unix()
|
||||
sorter: (a, b) => dayjs(a.start).unix() - dayjs(b.start).unix()
|
||||
},
|
||||
{
|
||||
title: t("job_lifecycle.columns.relative_start"),
|
||||
@@ -90,7 +90,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
}
|
||||
return isEmpty(a.end) ? 1 : -1;
|
||||
}
|
||||
return day(a.end).unix() - day(b.end).unix();
|
||||
return dayjs(a.end).unix() - dayjs(b.end).unix();
|
||||
},
|
||||
render: (text) => (isEmpty(text) ? t("job_lifecycle.content.not_available") : DateTimeFormatterFunction(text))
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, notification, Popover, Select, Space } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -48,7 +48,7 @@ export function JobLineDispatchButton({
|
||||
const result = await dispatchLines({
|
||||
variables: {
|
||||
partsDispatch: {
|
||||
dispatched_at: day(),
|
||||
dispatched_at: dayjs(),
|
||||
employeeid: values.employeeid,
|
||||
jobid: job.id,
|
||||
dispatched_by: currentUser.email,
|
||||
@@ -138,7 +138,11 @@ export function JobLineDispatchButton({
|
||||
|
||||
return (
|
||||
<Popover open={visible} content={popMenu}>
|
||||
<Button disabled={selectedLines.length === 0 || jobRO || disabled} loading={loading} onClick={() => setVisible(true)}>
|
||||
<Button
|
||||
disabled={selectedLines.length === 0 || jobRO || disabled}
|
||||
loading={loading}
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
{t("joblines.actions.dispatchparts", { count: selectedLines.length })}
|
||||
</Button>
|
||||
</Popover>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { notification } from "antd";
|
||||
import Axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -7,13 +10,10 @@ import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_NEW_JOB_LINE, UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
||||
import Axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobLineEditModal: selectJobLineEditModal,
|
||||
@@ -82,13 +82,15 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
|
||||
variables: {
|
||||
lineId: jobLineEditModal.context.id,
|
||||
line: {
|
||||
...values,
|
||||
prt_dsmk_m: Dinero({
|
||||
amount: Math.round(values.act_price * 100)
|
||||
...UndefinedToNull({
|
||||
...values,
|
||||
prt_dsmk_m: Dinero({
|
||||
amount: Math.round(values.act_price * 100)
|
||||
})
|
||||
.percentage(Math.abs(values.prt_dsmk_p || 0))
|
||||
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
|
||||
.toFormat(0.0)
|
||||
})
|
||||
.percentage(Math.abs(values.prt_dsmk_p || 0))
|
||||
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
|
||||
.toFormat(0.0)
|
||||
}
|
||||
},
|
||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"]
|
||||
|
||||
@@ -219,7 +219,7 @@ export function JobsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Col, notification, Row, Table } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_PARTS_DISPATCH_LINE } from "../../graphql/parts-dispatch.queries";
|
||||
@@ -11,7 +11,7 @@ export default function PartsDispatchExpander({ dispatch, job }) {
|
||||
const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE);
|
||||
|
||||
const handleAccept = async ({ partsDispatchLineId }) => {
|
||||
const accepted_at = day();
|
||||
const accepted_at = dayjs();
|
||||
const result = await updateDispatchLine({
|
||||
variables: { id: partsDispatchLineId, line: { accepted_at } },
|
||||
optimisticResponse: {
|
||||
|
||||
@@ -242,7 +242,8 @@ export function PartsOrderListTableComponent({
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (text, record) => recordActions(record, true)
|
||||
render: (text, record) => recordActions(record, true),
|
||||
id: "parts-order-list-table-actions"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ export function PayableExportAll({
|
||||
);
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -180,7 +180,7 @@ export function PaymentsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import Board from "./trello-board/index";
|
||||
import { Button, notification, Skeleton, Space } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, notification, Skeleton, Space } from "antd";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
@@ -15,14 +17,13 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
|
||||
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
|
||||
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
||||
import ProductionListPrint from "../production-list-table/production-list-print.component.jsx";
|
||||
import CardColorLegend from "./production-board-kanban-card-color-legend.component.jsx";
|
||||
import "./production-board-kanban.styles.scss";
|
||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
||||
import Board from "./trello-board/index";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -185,7 +186,7 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
||||
const cardSettings = useMemo(() => {
|
||||
const kanbanSettings = associationSettings?.kanban_settings;
|
||||
return mergeWithDefaults(kanbanSettings);
|
||||
}, [associationSettings]);
|
||||
}, [associationSettings?.kanban_settings]);
|
||||
|
||||
const handleSettingsChange = () => {
|
||||
setFilter(defaultFilters);
|
||||
@@ -214,6 +215,7 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
||||
bodyshop={bodyshop}
|
||||
data={data}
|
||||
/>
|
||||
<ProductionListPrint />
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useQuery, useSubscription } from "@apollo/client";
|
||||
import React, { useContext, useEffect, useMemo, useRef } from "react";
|
||||
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
QUERY_JOBS_IN_PRODUCTION,
|
||||
SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||
SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW
|
||||
@@ -10,6 +11,8 @@ import {
|
||||
import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -17,7 +20,20 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionType = "direct" }) {
|
||||
const fired = useRef(false); // useRef to keep track of whether the subscription fired
|
||||
const fired = useRef(false);
|
||||
const client = useApolloClient();
|
||||
const { socket } = useContext(SocketContext); // Get the socket from context
|
||||
const reconnectTimeout = useRef(null); // To store the reconnect timeout
|
||||
const disconnectTime = useRef(null); // To track disconnection time
|
||||
const acceptableReconnectTime = 2000; // 2 seconds threshold
|
||||
|
||||
const {
|
||||
treatments: { Websocket_Production }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Websocket_Production"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const combinedStatuses = useMemo(
|
||||
() => [
|
||||
@@ -34,9 +50,12 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
|
||||
onError: (error) => console.error(`Error fetching jobs in production: ${error.message}`)
|
||||
});
|
||||
|
||||
const subscriptionEnabled = Websocket_Production?.treatment === "off";
|
||||
|
||||
const { data: updatedJobs } = useSubscription(
|
||||
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||
{
|
||||
skip: !subscriptionEnabled,
|
||||
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
||||
}
|
||||
);
|
||||
@@ -46,22 +65,112 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
|
||||
onError: (error) => console.error(`Error fetching Kanban settings: ${error.message}`)
|
||||
});
|
||||
|
||||
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
|
||||
|
||||
useEffect(() => {
|
||||
if (!updatedJobs) {
|
||||
if (subscriptionEnabled) {
|
||||
if (!updatedJobs) {
|
||||
return;
|
||||
}
|
||||
if (!fired.current) {
|
||||
fired.current = true;
|
||||
return;
|
||||
}
|
||||
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
||||
}
|
||||
}, [updatedJobs, refetch, subscriptionEnabled]);
|
||||
|
||||
// Socket.IO implementation for users with Split treatment "off"
|
||||
useEffect(() => {
|
||||
if (subscriptionEnabled || !socket || !bodyshop || !bodyshop.id) {
|
||||
return;
|
||||
}
|
||||
if (!fired.current) {
|
||||
fired.current = true;
|
||||
return;
|
||||
}
|
||||
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
||||
}, [updatedJobs, refetch]);
|
||||
|
||||
const handleJobUpdates = async (jobChangedData) => {
|
||||
const jobId = jobChangedData.id;
|
||||
|
||||
// Access the existing cache for QUERY_JOBS_IN_PRODUCTION
|
||||
const existingJobsCache = client.readQuery({
|
||||
query: QUERY_JOBS_IN_PRODUCTION
|
||||
});
|
||||
|
||||
const existingJobs = existingJobsCache?.jobs || [];
|
||||
|
||||
// Check if the job already exists in the cached jobs
|
||||
const existingJob = existingJobs.find((job) => job.id === jobId);
|
||||
|
||||
if (existingJob) {
|
||||
// If the job exists, we update the cache without making any additional queries
|
||||
client.writeQuery({
|
||||
query: QUERY_JOBS_IN_PRODUCTION,
|
||||
data: {
|
||||
jobs: existingJobs.map((job) =>
|
||||
job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job
|
||||
)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If the job doesn't exist, fetch it from the server and then add it to the cache
|
||||
try {
|
||||
const { data: jobData } = await client.query({
|
||||
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
variables: { id: jobId },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
// Add the job to the existing cached jobs
|
||||
client.writeQuery({
|
||||
query: QUERY_JOBS_IN_PRODUCTION,
|
||||
data: {
|
||||
jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error fetching job ${jobId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
// Capture the disconnection time
|
||||
disconnectTime.current = Date.now();
|
||||
};
|
||||
|
||||
const handleReconnect = () => {
|
||||
const reconnectTime = Date.now();
|
||||
const disconnectionDuration = reconnectTime - disconnectTime.current;
|
||||
|
||||
// Only refetch if disconnection was longer than the acceptable reconnect time
|
||||
if (disconnectionDuration >= acceptableReconnectTime) {
|
||||
if (!reconnectTimeout.current) {
|
||||
reconnectTimeout.current = setTimeout(() => {
|
||||
const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds
|
||||
setTimeout(() => {
|
||||
if (refetch) refetch().catch((err) => console.error(`Issue `));
|
||||
reconnectTimeout.current = null; // Clear the timeout reference after refetch
|
||||
}, randomDelay);
|
||||
}, acceptableReconnectTime);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for 'job-changed', 'disconnect', and 'connect' events
|
||||
socket.on("production-job-updated", handleJobUpdates);
|
||||
socket.on("disconnect", handleDisconnect);
|
||||
socket.on("connect", handleReconnect);
|
||||
|
||||
// Clean up on unmount or when dependencies change
|
||||
return () => {
|
||||
socket.off("production-job-updated", handleJobUpdates);
|
||||
socket.off("disconnect", handleDisconnect);
|
||||
socket.off("connect", handleReconnect);
|
||||
if (reconnectTimeout.current) {
|
||||
clearTimeout(reconnectTimeout.current);
|
||||
}
|
||||
};
|
||||
}, [subscriptionEnabled, socket, bodyshop, client, refetch]);
|
||||
|
||||
const filteredAssociationSettings = useMemo(() => {
|
||||
return associationSettings?.associations[0] || null;
|
||||
}, [associationSettings]);
|
||||
}, [associationSettings?.associations]);
|
||||
|
||||
return (
|
||||
<ProductionBoardKanbanComponent
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
|
||||
.production-alert {
|
||||
background: transparent;
|
||||
border: none;
|
||||
@@ -70,3 +69,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clone.is-dragging .ant-card {
|
||||
border: #1890ff 2px solid !important;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,66 @@
|
||||
import InstanceRenderManager from "../../../utils/instanceRenderMgr.js";
|
||||
|
||||
const statisticsItems = [
|
||||
{ id: 0, name: "totalHrs", label: "total_hours_in_production" },
|
||||
{ id: 1, name: "totalAmountInProduction", label: "total_amount_in_production" },
|
||||
{ id: 2, name: "totalLAB", label: "total_lab_in_production" },
|
||||
{ id: 3, name: "totalLAR", label: "total_lar_in_production" },
|
||||
{ id: 4, name: "jobsInProduction", label: "jobs_in_production" },
|
||||
{ id: 5, name: "totalHrsOnBoard", label: "total_hours_on_board" },
|
||||
{ id: 6, name: "totalAmountOnBoard", label: "total_amount_on_board" },
|
||||
{ id: 7, name: "totalLABOnBoard", label: "total_lab_on_board" },
|
||||
{ id: 8, name: "totalLAROnBoard", label: "total_lar_on_board" },
|
||||
{ id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" },
|
||||
{ id: 10, name: "tasksOnBoard", label: "tasks_on_board" },
|
||||
|
||||
{
|
||||
id: 5,
|
||||
name: "totalHrsOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_hours_in_view",
|
||||
rome: "total_hours_on_board",
|
||||
promanager: "total_hours_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "totalAmountOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_amount_in_view",
|
||||
rome: "total_amount_on_board",
|
||||
promanager: "total_amount_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "totalLABOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_lab_in_view",
|
||||
rome: "total_lab_on_board",
|
||||
promanager: "total_lab_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "totalLAROnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_lar_in_view",
|
||||
rome: "total_lar_on_board",
|
||||
promanager: "total_lar_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "jobsOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_jobs_in_view",
|
||||
rome: "total_jobs_on_board",
|
||||
promanager: "total_jobs_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "tasksOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "tasks_in_view",
|
||||
rome: "tasks_on_board",
|
||||
promanager: "tasks_on_board"
|
||||
})
|
||||
},
|
||||
{ id: 11, name: "tasksInProduction", label: "tasks_in_production" }
|
||||
];
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ function getFurthestAway({ pageBorderBox, draggable, candidates }) {
|
||||
const axis = candidate.axis;
|
||||
const target = patch(
|
||||
candidate.axis.line,
|
||||
// use the current center of the dragging item on the main axis
|
||||
pageBorderBox.center[axis.line],
|
||||
// use the center of the list on the main axis
|
||||
candidate.page.borderBox.center[axis.line],
|
||||
// use the center of the list on the cross axis
|
||||
candidate.page.borderBox.center[axis.crossAxisLine]
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import getBodyElement from "../get-body-element";
|
||||
const isEqual = (base) => (value) => base === value;
|
||||
const isScroll = isEqual("scroll");
|
||||
const isAuto = isEqual("auto");
|
||||
const isOverlay = isEqual("overlay");
|
||||
const isVisible = isEqual("visible");
|
||||
const isEither = (overflow, fn) => fn(overflow.overflowX) || fn(overflow.overflowY);
|
||||
const isBoth = (overflow, fn) => fn(overflow.overflowX) && fn(overflow.overflowY);
|
||||
@@ -14,7 +15,7 @@ const isElementScrollable = (el) => {
|
||||
overflowX: style.overflowX,
|
||||
overflowY: style.overflowY
|
||||
};
|
||||
return isEither(overflow, isScroll) || isEither(overflow, isAuto);
|
||||
return isEither(overflow, isScroll) || isEither(overflow, isAuto) || isEither(overflow, isOverlay);
|
||||
};
|
||||
|
||||
// Special case for a body element
|
||||
|
||||
@@ -8,7 +8,7 @@ function getSelector(contextId) {
|
||||
return `[${attributes.dragHandle.contextId}="${contextId}"]`;
|
||||
}
|
||||
|
||||
function findClosestDragHandleFromEvent(contextId, event) {
|
||||
export function findClosestDragHandleFromEvent(contextId, event) {
|
||||
const target = event.target;
|
||||
if (!isElement(target)) {
|
||||
warning("event.target must be a Element");
|
||||
|
||||
@@ -240,11 +240,14 @@ export default function useTouchSensor(api) {
|
||||
y: clientY
|
||||
};
|
||||
|
||||
const handle = api.findClosestDragHandle(event);
|
||||
invariant(handle, "Touch sensor unable to find drag handle");
|
||||
|
||||
// unbind this event handler
|
||||
unbindEventsRef.current();
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
startPendingDrag(actions, point);
|
||||
startPendingDrag(actions, point, handle);
|
||||
}
|
||||
}),
|
||||
// not including stop or startPendingDrag as it is not defined initially
|
||||
@@ -288,7 +291,7 @@ export default function useTouchSensor(api) {
|
||||
}
|
||||
}, [stop]);
|
||||
const bindCapturingEvents = useCallback(
|
||||
function bindCapturingEvents() {
|
||||
function bindCapturingEvents(target) {
|
||||
const options = {
|
||||
capture: true,
|
||||
passive: false
|
||||
@@ -307,7 +310,7 @@ export default function useTouchSensor(api) {
|
||||
// Old behaviour:
|
||||
// https://gist.github.com/parris/dda613e3ae78f14eb2dc9fa0f4bfce3d
|
||||
// https://stackoverflow.com/questions/33298828/touch-move-event-dont-fire-after-touch-start-target-is-removed
|
||||
const unbindTarget = bindEvents(window, getHandleBindings(args), options);
|
||||
const unbindTarget = bindEvents(target, getHandleBindings(args), options);
|
||||
const unbindWindow = bindEvents(window, getWindowBindings(args), options);
|
||||
unbindEventsRef.current = function unbindAll() {
|
||||
unbindTarget();
|
||||
@@ -330,7 +333,7 @@ export default function useTouchSensor(api) {
|
||||
[getPhase, setPhase]
|
||||
);
|
||||
const startPendingDrag = useCallback(
|
||||
function startPendingDrag(actions, point) {
|
||||
function startPendingDrag(actions, point, target) {
|
||||
invariant(getPhase().type === "IDLE", "Expected to move from IDLE to PENDING drag");
|
||||
const longPressTimerId = setTimeout(startDragging, timeForLongPress);
|
||||
setPhase({
|
||||
@@ -339,7 +342,7 @@ export default function useTouchSensor(api) {
|
||||
actions,
|
||||
longPressTimerId
|
||||
});
|
||||
bindCapturingEvents();
|
||||
bindCapturingEvents(target);
|
||||
},
|
||||
[bindCapturingEvents, getPhase, setPhase, startDragging]
|
||||
);
|
||||
|
||||
@@ -23,7 +23,9 @@ import getBorderBoxCenterPosition from "../get-border-box-center-position";
|
||||
import { warning } from "../../dev-warning";
|
||||
import useLayoutEffect from "../use-isomorphic-layout-effect";
|
||||
import { noop } from "../../empty";
|
||||
import findClosestDraggableIdFromEvent from "./find-closest-draggable-id-from-event";
|
||||
import findClosestDraggableIdFromEvent, {
|
||||
findClosestDragHandleFromEvent
|
||||
} from "./find-closest-draggable-id-from-event";
|
||||
import findDraggable from "../get-elements/find-draggable";
|
||||
import bindEvents from "../event-bindings/bind-events";
|
||||
|
||||
@@ -339,6 +341,9 @@ export default function useSensorMarshal({ contextId, store, registry, customSen
|
||||
}),
|
||||
[contextId, lockAPI, registry, store]
|
||||
);
|
||||
|
||||
const findClosestDragHandle = useCallback((event) => findClosestDragHandleFromEvent(contextId, event), [contextId]);
|
||||
|
||||
const findClosestDraggableId = useCallback((event) => findClosestDraggableIdFromEvent(contextId, event), [contextId]);
|
||||
const findOptionsForDraggable = useCallback(
|
||||
(id) => {
|
||||
@@ -370,9 +375,18 @@ export default function useSensorMarshal({ contextId, store, registry, customSen
|
||||
findClosestDraggableId,
|
||||
findOptionsForDraggable,
|
||||
tryReleaseLock,
|
||||
isLockClaimed
|
||||
isLockClaimed,
|
||||
findClosestDragHandle
|
||||
}),
|
||||
[canGetLock, tryGetLock, findClosestDraggableId, findOptionsForDraggable, tryReleaseLock, isLockClaimed]
|
||||
[
|
||||
canGetLock,
|
||||
tryGetLock,
|
||||
findClosestDraggableId,
|
||||
findOptionsForDraggable,
|
||||
tryReleaseLock,
|
||||
isLockClaimed,
|
||||
findClosestDragHandle
|
||||
]
|
||||
);
|
||||
|
||||
// Bad ass
|
||||
|
||||
@@ -83,7 +83,13 @@ const getFinalStyles = (contextId) => {
|
||||
return {
|
||||
selector: getSelector(attributes.draggable.contextId),
|
||||
styles: {
|
||||
dragging: transition,
|
||||
dragging: `
|
||||
${transition}
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
`,
|
||||
dropAnimating: transition,
|
||||
userCancel: transition
|
||||
}
|
||||
|
||||
@@ -67,7 +67,9 @@ export default function useStyleMarshal(contextId, nonce) {
|
||||
const remove = (ref) => {
|
||||
const current = ref.current;
|
||||
invariant(current, "Cannot unmount ref as it is not set");
|
||||
getHead().removeChild(current);
|
||||
if (getHead().contains(current)) {
|
||||
getHead().removeChild(current);
|
||||
}
|
||||
ref.current = null;
|
||||
};
|
||||
remove(alwaysRef);
|
||||
|
||||
@@ -21,25 +21,26 @@ export function ProductionListColumnStatus({ record, bodyshop, insertAuditTrail
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSetStatus = async (e) => {
|
||||
logImEXEvent("production_change_status");
|
||||
// e.stopPropagation();
|
||||
setLoading(true);
|
||||
const { key } = e;
|
||||
await updateJob({
|
||||
variables: {
|
||||
jobId: record.id,
|
||||
job: {
|
||||
status: key
|
||||
if (bodyshop.md_ro_statuses.production_statuses.includes(record.status) && !bodyshop.md_ro_statuses.post_production_statuses.includes(record.status)) {
|
||||
logImEXEvent("production_change_status");
|
||||
// e.stopPropagation();
|
||||
setLoading(true);
|
||||
const { key } = e;
|
||||
await updateJob({
|
||||
variables: {
|
||||
jobId: record.id,
|
||||
job: {
|
||||
status: key
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: record.id,
|
||||
operation: AuditTrailMapping.jobstatuschange(key),
|
||||
type: "jobstatuschange"
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: record.id,
|
||||
operation: AuditTrailMapping.jobstatuschange(key),
|
||||
type: "jobstatuschange"
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const menu = {
|
||||
|
||||
@@ -457,41 +457,42 @@ export function ProductionListConfigManager({
|
||||
value={activeView}
|
||||
disabled={open || isAddingNewProfile} // Disable the Select box when the popover is open or adding a new profile
|
||||
>
|
||||
{bodyshop.production_config
|
||||
.slice()
|
||||
.sort((a, b) =>
|
||||
a.name === t("production.constants.main_profile")
|
||||
? -1
|
||||
: b.name === t("production.constants.main_profile")
|
||||
? 1
|
||||
: 0
|
||||
) //
|
||||
.map((config) => (
|
||||
<Select.Option key={config.name} label={config.name}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span
|
||||
style={{
|
||||
flex: 1,
|
||||
maxWidth: "80%",
|
||||
marginRight: "1rem",
|
||||
textOverflow: "ellipsis"
|
||||
}}
|
||||
>
|
||||
{config.name}
|
||||
</span>
|
||||
{config.name !== t("production.constants.main_profile") && (
|
||||
<Popconfirm
|
||||
placement="right"
|
||||
title={t("general.labels.areyousure")}
|
||||
onConfirm={() => handleTrash(config.name)}
|
||||
onCancel={(e) => e.stopPropagation()}
|
||||
{bodyshop?.production_config &&
|
||||
bodyshop.production_config
|
||||
.slice()
|
||||
.sort((a, b) =>
|
||||
a.name === t("production.constants.main_profile")
|
||||
? -1
|
||||
: b.name === t("production.constants.main_profile")
|
||||
? 1
|
||||
: 0
|
||||
) //
|
||||
.map((config) => (
|
||||
<Select.Option key={config.name} label={config.name}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span
|
||||
style={{
|
||||
flex: 1,
|
||||
maxWidth: "80%",
|
||||
marginRight: "1rem",
|
||||
textOverflow: "ellipsis"
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined onClick={(e) => e.stopPropagation()} />
|
||||
</Popconfirm>
|
||||
)}
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
{config.name}
|
||||
</span>
|
||||
{config.name !== t("production.constants.main_profile") && (
|
||||
<Popconfirm
|
||||
placement="right"
|
||||
title={t("general.labels.areyousure")}
|
||||
onConfirm={() => handleTrash(config.name)}
|
||||
onCancel={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DeleteOutlined onClick={(e) => e.stopPropagation()} />
|
||||
</Popconfirm>
|
||||
)}
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
<Select.Option key="add_new" label={t("production.labels.addnewprofile")}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<PlusOutlined style={{ marginRight: "0.5rem" }} />
|
||||
|
||||
@@ -51,8 +51,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
|
||||
const initialColumnsRef = useRef(
|
||||
(initialStateRef.current &&
|
||||
bodyshop.production_config
|
||||
.find((p) => p.name === defaultView)
|
||||
bodyshop?.production_config
|
||||
?.find((p) => p.name === defaultView)
|
||||
?.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
@@ -76,7 +76,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
const { t } = useTranslation();
|
||||
|
||||
const matchingColumnConfig = useMemo(() => {
|
||||
return bodyshop.production_config.find((p) => p.name === defaultView);
|
||||
return bodyshop?.production_config?.find((p) => p.name === defaultView);
|
||||
}, [bodyshop.production_config, defaultView]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useContext, useEffect, useState, useRef } from "react";
|
||||
import {
|
||||
QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
QUERY_EXACT_JOBS_IN_PRODUCTION,
|
||||
@@ -9,19 +9,46 @@ import {
|
||||
} from "../../graphql/jobs.queries";
|
||||
import ProductionListTable from "./production-list-table.component";
|
||||
import _ from "lodash";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
export default function ProductionListTableContainer({ subscriptionType = "direct" }) {
|
||||
export default function ProductionListTableContainer({ bodyshop, subscriptionType = "direct" }) {
|
||||
const client = useApolloClient();
|
||||
const { socket } = useContext(SocketContext);
|
||||
const [joblist, setJoblist] = useState([]);
|
||||
const reconnectTimeout = useRef(null); // To store the reconnect timeout
|
||||
const disconnectTime = useRef(null); // To store the time of disconnection
|
||||
|
||||
const acceptableReconnectTime = 2000; // 2 seconds threshold
|
||||
|
||||
// Get Split treatment
|
||||
const {
|
||||
treatments: { Websocket_Production }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Websocket_Production"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
|
||||
// Determine if subscription is enabled
|
||||
const subscriptionEnabled = Websocket_Production?.treatment === "off";
|
||||
|
||||
// Use GraphQL query
|
||||
const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, {
|
||||
pollInterval: 3600000,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
const client = useApolloClient();
|
||||
const [joblist, setJoblist] = useState([]);
|
||||
|
||||
// Use GraphQL subscription when subscription is enabled
|
||||
const { data: updatedJobs } = useSubscription(
|
||||
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION
|
||||
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||
{
|
||||
skip: !subscriptionEnabled
|
||||
}
|
||||
);
|
||||
|
||||
// Update joblist when data changes
|
||||
useEffect(() => {
|
||||
if (!(data && data.jobs)) return;
|
||||
setJoblist(
|
||||
@@ -31,34 +58,134 @@ export default function ProductionListTableContainer({ subscriptionType = "direc
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
// Handle updates from GraphQL subscription
|
||||
useEffect(() => {
|
||||
if (!updatedJobs || joblist.length === 0) return;
|
||||
if (subscriptionEnabled) {
|
||||
if (!updatedJobs || joblist.length === 0) return;
|
||||
|
||||
const jobDiff = _.differenceWith(
|
||||
joblist,
|
||||
updatedJobs.jobs,
|
||||
(a, b) => a.id === b.id && a.updated_at === b.updated_at
|
||||
);
|
||||
const jobDiff = _.differenceWith(
|
||||
joblist,
|
||||
updatedJobs.jobs,
|
||||
(a, b) => a.id === b.id && a.updated_at === b.updated_at
|
||||
);
|
||||
|
||||
if (jobDiff.length > 1) {
|
||||
getUpdatedJobsData(jobDiff.map((j) => j.id));
|
||||
} else if (jobDiff.length === 1) {
|
||||
jobDiff.forEach((job) => {
|
||||
getUpdatedJobData(job.id);
|
||||
});
|
||||
if (jobDiff.length > 1) {
|
||||
getUpdatedJobsData(jobDiff.map((j) => j.id));
|
||||
} else if (jobDiff.length === 1) {
|
||||
jobDiff.forEach((job) => {
|
||||
getUpdatedJobData(job.id);
|
||||
});
|
||||
}
|
||||
|
||||
setJoblist(updatedJobs.jobs);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [updatedJobs, subscriptionEnabled]);
|
||||
|
||||
// Handle updates from Socket.IO when subscription is disabled
|
||||
useEffect(() => {
|
||||
if (subscriptionEnabled || !socket || !bodyshop || !bodyshop.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
setJoblist(updatedJobs.jobs);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [updatedJobs]);
|
||||
const handleJobUpdates = async (jobChangedData) => {
|
||||
const jobId = jobChangedData.id;
|
||||
|
||||
// Access the existing cache for QUERY_JOBS_IN_PRODUCTION
|
||||
const existingJobsCache = client.readQuery({
|
||||
query: QUERY_JOBS_IN_PRODUCTION
|
||||
});
|
||||
|
||||
const existingJobs = existingJobsCache?.jobs || [];
|
||||
|
||||
// Check if the job already exists in the cached jobs
|
||||
const existingJob = existingJobs.find((job) => job.id === jobId);
|
||||
|
||||
if (existingJob) {
|
||||
// If the job exists, we update the cache without making any additional queries
|
||||
client.writeQuery({
|
||||
query: QUERY_JOBS_IN_PRODUCTION,
|
||||
data: {
|
||||
jobs: existingJobs.map((job) =>
|
||||
job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job
|
||||
)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If the job doesn't exist, fetch it from the server and then add it to the cache
|
||||
try {
|
||||
const { data: jobData } = await client.query({
|
||||
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
variables: { id: jobId },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
// Add the job to the existing cached jobs
|
||||
client.writeQuery({
|
||||
query: QUERY_JOBS_IN_PRODUCTION,
|
||||
data: {
|
||||
jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error fetching job ${jobId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
// Capture the time when the disconnection happens
|
||||
disconnectTime.current = Date.now();
|
||||
};
|
||||
|
||||
const handleReconnect = () => {
|
||||
// Calculate how long the disconnection lasted
|
||||
const reconnectTime = Date.now();
|
||||
const disconnectionDuration = reconnectTime - disconnectTime.current;
|
||||
|
||||
// If disconnection lasted less than acceptable reconnect time, do nothing
|
||||
if (disconnectionDuration < acceptableReconnectTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule a refetch with a random delay between 10 and 30 seconds
|
||||
if (!reconnectTimeout.current) {
|
||||
reconnectTimeout.current = setTimeout(() => {
|
||||
const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds
|
||||
setTimeout(() => {
|
||||
if (refetch) refetch();
|
||||
reconnectTimeout.current = null; // Clear the timeout reference after refetch
|
||||
}, randomDelay);
|
||||
}, acceptableReconnectTime);
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for 'production-job-updated', 'disconnect', and 'connect' events
|
||||
socket.on("production-job-updated", handleJobUpdates);
|
||||
socket.on("disconnect", handleDisconnect);
|
||||
socket.on("connect", handleReconnect);
|
||||
|
||||
// Clean up on unmount or when dependencies change
|
||||
return () => {
|
||||
socket.off("production-job-updated", handleJobUpdates);
|
||||
socket.off("disconnect", handleDisconnect);
|
||||
socket.off("connect", handleReconnect);
|
||||
if (reconnectTimeout.current) {
|
||||
clearTimeout(reconnectTimeout.current);
|
||||
}
|
||||
};
|
||||
}, [subscriptionEnabled, socket, bodyshop, client, refetch]);
|
||||
|
||||
// Functions to fetch updated job data
|
||||
const getUpdatedJobData = async (jobId) => {
|
||||
client.query({
|
||||
await client.query({
|
||||
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
variables: { id: jobId }
|
||||
variables: { id: jobId },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
};
|
||||
const getUpdatedJobsData = async (jobIds) => {
|
||||
|
||||
const getUpdatedJobsData = (jobIds) => {
|
||||
client.query({
|
||||
query: QUERY_EXACT_JOBS_IN_PRODUCTION,
|
||||
variables: { ids: jobIds }
|
||||
|
||||
@@ -120,14 +120,6 @@ var formats = {
|
||||
};
|
||||
|
||||
const localizer = (dayjsLib) => {
|
||||
// load dayjs plugins
|
||||
dayjsLib.extend(isBetween);
|
||||
dayjsLib.extend(isSameOrAfter);
|
||||
dayjsLib.extend(isSameOrBefore);
|
||||
dayjsLib.extend(localeData);
|
||||
dayjsLib.extend(localizedFormat);
|
||||
dayjsLib.extend(minMax);
|
||||
dayjsLib.extend(utc);
|
||||
var locale = function locale(dj, c) {
|
||||
return c ? dj.locale(c) : dj;
|
||||
};
|
||||
@@ -136,7 +128,8 @@ const localizer = (dayjsLib) => {
|
||||
// then use the timezone aware version
|
||||
|
||||
//TODO This was the issue entirely...
|
||||
// var dayjs = dayjsLib.tz ? dayjsLib.tz : dayjsLib;
|
||||
// var dayjs = dayjsLib.tz ? dayjsLib.tz : dayjsLib;
|
||||
|
||||
var dayjs = dayjsLib;
|
||||
|
||||
function getTimezoneOffset(date) {
|
||||
|
||||
@@ -20,6 +20,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
problemJobs: selectProblemJobs
|
||||
});
|
||||
|
||||
const localizer = local(dayjs);
|
||||
|
||||
export function ScheduleCalendarWrapperComponent({
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { Button, Card, Space, Switch, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import dayjs from "../../utils/day";
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
CheckCircleOutlined,
|
||||
@@ -15,9 +8,16 @@ import {
|
||||
PlusCircleFilled,
|
||||
SyncOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
||||
import { Button, Card, Space, Switch, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
/**
|
||||
* Task List Component
|
||||
@@ -140,6 +140,17 @@ function TaskListComponent({
|
||||
render: (text, record) => <DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||
});
|
||||
|
||||
columns.push({
|
||||
title: t("tasks.fields.created_by"),
|
||||
dataIndex: "created_by",
|
||||
key: "created_by",
|
||||
width: "8%",
|
||||
defaultSortOrder: "descend",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "created_by" && sortorder,
|
||||
render: (text, record) => record.created_by
|
||||
});
|
||||
|
||||
if (!onlyMine) {
|
||||
columns.push({
|
||||
title: t("tasks.fields.assigned_to"),
|
||||
@@ -155,65 +166,70 @@ function TaskListComponent({
|
||||
});
|
||||
}
|
||||
|
||||
if (showRo) {
|
||||
columns.push({
|
||||
title: t("tasks.fields.job.ro_number"),
|
||||
dataIndex: ["job", "ro_number"],
|
||||
key: "job.ro_number",
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.job ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?tab=tasks`}>{record.job.ro_number || t("general.labels.na")}</Link>
|
||||
) : (
|
||||
t("general.labels.na")
|
||||
)
|
||||
});
|
||||
}
|
||||
columns.push({
|
||||
title: t("tasks.fields.related_items"),
|
||||
key: "related_items",
|
||||
width: "12%",
|
||||
render: (text, record) => {
|
||||
const items = [];
|
||||
|
||||
// Job
|
||||
if (showRo && record.job) {
|
||||
items.push(
|
||||
<Link key="job" to={`/manage/jobs/${record.job.id}?tab=tasks`}>
|
||||
{t("tasks.fields.job.ro_number")}: {record.job.ro_number}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
if (showRo && !record.job) {
|
||||
items.push(`${t("tasks.fields.job.ro_number")}: ${t("general.labels.na")}`);
|
||||
}
|
||||
|
||||
// Jobline
|
||||
if (record.jobline?.line_desc) {
|
||||
items.push(
|
||||
<span key="jobline">
|
||||
{t("tasks.fields.jobline")}: {record.jobline.line_desc}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Parts Order
|
||||
if (record.parts_order) {
|
||||
const { order_number, vendor } = record.parts_order;
|
||||
const partsOrderText =
|
||||
order_number && vendor?.name ? `${order_number} - ${vendor.name}` : t("general.labels.na");
|
||||
items.push(
|
||||
<Link
|
||||
key="parts_order"
|
||||
to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}
|
||||
>
|
||||
{t("tasks.fields.parts_order")}: {partsOrderText}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Bill
|
||||
if (record.bill) {
|
||||
const { invoice_number, vendor } = record.bill;
|
||||
const billText = invoice_number && vendor?.name ? `${invoice_number} - ${vendor.name}` : t("general.labels.na");
|
||||
items.push(
|
||||
<Link key="bill" to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
||||
{t("tasks.fields.bill")}: {billText}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return items.length > 0 ? <Space direction="vertical">{items}</Space> : null;
|
||||
}
|
||||
});
|
||||
|
||||
columns.push(
|
||||
{
|
||||
title: t("tasks.fields.jobline"),
|
||||
dataIndex: ["jobline", "id"],
|
||||
key: "jobline.id",
|
||||
width: "8%",
|
||||
render: (text, record) => record?.jobline?.line_desc || ""
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.parts_order"),
|
||||
dataIndex: ["parts_order", "id"],
|
||||
key: "part_order.id",
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.parts_order ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
|
||||
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
|
||||
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.bill"),
|
||||
dataIndex: ["bill", "id"],
|
||||
key: "bill.id",
|
||||
width: "10%",
|
||||
render: (text, record) =>
|
||||
record.bill ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
||||
{record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name
|
||||
? `${record.bill.invoice_number} - ${record.bill.vendor.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.title"),
|
||||
dataIndex: "title",
|
||||
key: "title",
|
||||
minWidth: "20%",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "title" && sortorder
|
||||
},
|
||||
@@ -247,7 +263,7 @@ function TaskListComponent({
|
||||
{
|
||||
title: t("tasks.fields.actions"),
|
||||
key: "toggleCompleted",
|
||||
width: "5%",
|
||||
width: "8%",
|
||||
render: (text, record) => (
|
||||
<Space direction="horizontal">
|
||||
<Button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -29,7 +29,7 @@ export function TimeTicketsCommit({ bodyshop, currentUser, timeticket, disabled,
|
||||
? { commited_by: null, committed_at: null }
|
||||
: {
|
||||
commited_by: currentUser.email,
|
||||
committed_at: day()
|
||||
committed_at: dayjs()
|
||||
};
|
||||
|
||||
const result = await updateTimeTicket({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -34,7 +34,7 @@ export function TimeTicketsCommit({
|
||||
timeticketIds: timetickets.map((ticket) => ticket.id),
|
||||
timeticket: {
|
||||
commited_by: currentUser.email,
|
||||
committed_at: day()
|
||||
committed_at: dayjs()
|
||||
}
|
||||
},
|
||||
update(cache) {
|
||||
@@ -47,7 +47,7 @@ export function TimeTicketsCommit({
|
||||
return {
|
||||
...ticket,
|
||||
commited_by: currentUser.email,
|
||||
committed_at: day()
|
||||
committed_at: dayjs()
|
||||
};
|
||||
}
|
||||
return ticket;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import _ from "lodash";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -49,7 +49,7 @@ export function TtApproveButton({
|
||||
})),
|
||||
approvalIds: selectedTickets,
|
||||
approvalUpdate: {
|
||||
approved_at: day(),
|
||||
approved_at: dayjs(),
|
||||
approved_by: currentUser.email
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { connect } from "react-redux";
|
||||
import { GlobalOutlined } from "@ant-design/icons";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import React from "react";
|
||||
import { selectWssStatus } from "../../redux/application/application.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
wssStatus: selectWssStatus
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(WssStatusDisplay);
|
||||
|
||||
export function WssStatusDisplay({ wssStatus }) {
|
||||
console.log("🚀 ~ WssStatusDisplay ~ wssStatus:", wssStatus);
|
||||
return <GlobalOutlined style={{ color: wssStatus === "connected" ? "green" : "red", marginRight: ".5rem" }} />;
|
||||
}
|
||||
13
client/src/contexts/SocketIO/socketContext.jsx
Normal file
13
client/src/contexts/SocketIO/socketContext.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React, { createContext } from "react";
|
||||
import useSocket from "./useSocket"; // Import the custom hook
|
||||
|
||||
// Create the SocketContext
|
||||
const SocketContext = createContext(null);
|
||||
|
||||
export const SocketProvider = ({ children, bodyshop }) => {
|
||||
const { socket, clientId } = useSocket(bodyshop);
|
||||
|
||||
return <SocketContext.Provider value={{ socket, clientId }}> {children}</SocketContext.Provider>;
|
||||
};
|
||||
|
||||
export default SocketContext;
|
||||
88
client/src/contexts/SocketIO/useSocket.js
Normal file
88
client/src/contexts/SocketIO/useSocket.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import SocketIO from "socket.io-client";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import { store } from "../../redux/store";
|
||||
import { setWssStatus } from "../../redux/application/application.actions";
|
||||
const useSocket = (bodyshop) => {
|
||||
const socketRef = useRef(null);
|
||||
const [clientId, setClientId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = auth.onIdTokenChanged(async (user) => {
|
||||
if (user) {
|
||||
const newToken = await user.getIdToken();
|
||||
|
||||
if (socketRef.current) {
|
||||
// Send new token to server
|
||||
socketRef.current.emit("update-token", newToken);
|
||||
} else if (bodyshop && bodyshop.id) {
|
||||
// Initialize the socket
|
||||
const endpoint = import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "";
|
||||
|
||||
const socketInstance = SocketIO(endpoint, {
|
||||
path: "/wss",
|
||||
withCredentials: true,
|
||||
auth: { token: newToken },
|
||||
reconnectionAttempts: Infinity,
|
||||
reconnectionDelay: 2000,
|
||||
reconnectionDelayMax: 10000
|
||||
});
|
||||
|
||||
socketRef.current = socketInstance;
|
||||
|
||||
const handleBodyshopMessage = (message) => {
|
||||
if (!import.meta.env.DEV) return;
|
||||
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
|
||||
};
|
||||
|
||||
const handleConnect = () => {
|
||||
console.log("Socket connected:", socketInstance.id);
|
||||
socketInstance.emit("join-bodyshop-room", bodyshop.id);
|
||||
setClientId(socketInstance.id);
|
||||
store.dispatch(setWssStatus("connected"))
|
||||
};
|
||||
|
||||
const handleReconnect = (attempt) => {
|
||||
console.log(`Socket reconnected after ${attempt} attempts`);
|
||||
store.dispatch(setWssStatus("connected"))
|
||||
};
|
||||
|
||||
const handleConnectionError = (err) => {
|
||||
console.error("Socket connection error:", err);
|
||||
store.dispatch(setWssStatus("error"))
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
console.log("Socket disconnected");
|
||||
store.dispatch(setWssStatus("disconnected"))
|
||||
};
|
||||
|
||||
socketInstance.on("connect", handleConnect);
|
||||
socketInstance.on("reconnect", handleReconnect);
|
||||
socketInstance.on("connect_error", handleConnectionError);
|
||||
socketInstance.on("disconnect", handleDisconnect);
|
||||
socketInstance.on("bodyshop-message", handleBodyshopMessage);
|
||||
}
|
||||
} else {
|
||||
// User is not authenticated
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
socketRef.current = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up the listener on unmount
|
||||
return () => {
|
||||
unsubscribe();
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
socketRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [bodyshop]);
|
||||
|
||||
return { socket: socketRef.current, clientId };
|
||||
};
|
||||
|
||||
export default useSocket;
|
||||
@@ -48,6 +48,7 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
|
||||
v_model_desc
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
comment
|
||||
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
|
||||
@@ -23,17 +23,14 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
|
||||
|
||||
export const socket = SocketIO(
|
||||
import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : window.location.origin,
|
||||
{
|
||||
path: "/ws",
|
||||
withCredentials: true,
|
||||
auth: async (callback) => {
|
||||
const token = auth.currentUser && (await auth.currentUser.getIdToken());
|
||||
callback({ token });
|
||||
}
|
||||
export const socket = SocketIO(import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "", {
|
||||
path: "/ws",
|
||||
withCredentials: true,
|
||||
auth: async (callback) => {
|
||||
const token = auth.currentUser && (await auth.currentUser.getIdToken());
|
||||
callback({ token });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -126,7 +123,6 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
socket.emit("set-log-level", value);
|
||||
}}
|
||||
>
|
||||
<Select.Option key="TRACE">TRACE</Select.Option>
|
||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
||||
<Select.Option key="INFO">INFO</Select.Option>
|
||||
<Select.Option key="WARNING">WARNING</Select.Option>
|
||||
|
||||
@@ -34,7 +34,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
|
||||
|
||||
export const socket = SocketIO(
|
||||
import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "http://localhost:4000", // for dev testing,
|
||||
import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "", // for dev testing,
|
||||
{
|
||||
path: "/ws",
|
||||
withCredentials: true,
|
||||
@@ -173,7 +173,6 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
socket.emit("set-log-level", value);
|
||||
}}
|
||||
>
|
||||
<Select.Option key="TRACE">TRACE</Select.Option>
|
||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
||||
<Select.Option key="INFO">INFO</Select.Option>
|
||||
<Select.Option key="WARNING">WARNING</Select.Option>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FloatButton, Layout, Spin } from "antd";
|
||||
// import preval from "preval.macro";
|
||||
import React, { lazy, Suspense, useEffect, useState } from "react";
|
||||
import React, { lazy, Suspense, useContext, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, Route, Routes } from "react-router-dom";
|
||||
@@ -19,11 +19,13 @@ import PartnerPingComponent from "../../components/partner-ping/partner-ping.com
|
||||
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
||||
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
|
||||
import { requestForToken } from "../../firebase/firebase.utils";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
|
||||
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import "./manage.page.styles.scss";
|
||||
import WssStatusDisplayComponent from "../../components/wss-status-display/wss-status-display.component.jsx";
|
||||
|
||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||
|
||||
@@ -110,6 +112,7 @@ const mapDispatchToProps = (dispatch) => ({});
|
||||
export function Manage({ conflict, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [chatVisible] = useState(false);
|
||||
const { socket, clientId } = useContext(SocketContext);
|
||||
|
||||
useEffect(() => {
|
||||
const widgetId = InstanceRenderManager({
|
||||
@@ -129,6 +132,7 @@ export function Manage({ conflict, bodyshop }) {
|
||||
promanager: t("titles.promanager")
|
||||
});
|
||||
}, [t]);
|
||||
|
||||
const AppRouteTable = (
|
||||
<Suspense
|
||||
fallback={
|
||||
@@ -569,6 +573,13 @@ export function Manage({ conflict, bodyshop }) {
|
||||
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />;
|
||||
else PageContent = AppRouteTable;
|
||||
|
||||
const broadcastMessage = () => {
|
||||
if (socket && bodyshop && bodyshop.id) {
|
||||
console.log(`Broadcasting message to bodyshop ${bodyshop.id}:`);
|
||||
socket.emit("broadcast-to-bodyshop", bodyshop.id, `Hello from ${clientId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />}
|
||||
@@ -594,7 +605,8 @@ export function Manage({ conflict, bodyshop }) {
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex" }}>
|
||||
<div>
|
||||
<WssStatusDisplayComponent />
|
||||
<div onClick={broadcastMessage}>
|
||||
{`${InstanceRenderManager({
|
||||
imex: t("titles.imexonline"),
|
||||
rome: t("titles.romeonline"),
|
||||
|
||||
@@ -26,7 +26,7 @@ export function ProductionListComponent({ bodyshop }) {
|
||||
return (
|
||||
<>
|
||||
<NoteUpsertModal />
|
||||
<ProductionListTable subscriptionType={Production_Use_View.treatment} />
|
||||
<ProductionListTable bodyshop={bodyshop} subscriptionType={Production_Use_View.treatment} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,3 +67,7 @@ export const setUpdateAvailable = (isUpdateAvailable) => ({
|
||||
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
|
||||
payload: isUpdateAvailable
|
||||
});
|
||||
export const setWssStatus = (status) => ({
|
||||
type: ApplicationActionTypes.SET_WSS_STATUS,
|
||||
payload: status
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import ApplicationActionTypes from "./application.types";
|
||||
const INITIAL_STATE = {
|
||||
loading: false,
|
||||
online: true,
|
||||
wssStatus: "disconnected",
|
||||
updateAvailable: false,
|
||||
breadcrumbs: [],
|
||||
recentItems: [],
|
||||
@@ -87,6 +88,9 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
|
||||
return { ...state, problemJobs: action.payload };
|
||||
}
|
||||
case ApplicationActionTypes.SET_WSS_STATUS: {
|
||||
return { ...state, wssStatus: action.payload };
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -22,3 +22,4 @@ export const selectJobReadOnly = createSelector([selectApplication], (applicatio
|
||||
export const selectOnline = createSelector([selectApplication], (application) => application.online);
|
||||
export const selectProblemJobs = createSelector([selectApplication], (application) => application.problemJobs);
|
||||
export const selectUpdateAvailable = createSelector([selectApplication], (application) => application.updateAvailable);
|
||||
export const selectWssStatus = createSelector([selectApplication], (application) => application.wssStatus);
|
||||
|
||||
@@ -12,6 +12,7 @@ const ApplicationActionTypes = {
|
||||
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
||||
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
||||
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
|
||||
SET_WSS_STATUS: "SET_WSS_STATUS"
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from "../../firebase/firebase.utils";
|
||||
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import {
|
||||
checkInstanceId,
|
||||
@@ -96,7 +96,7 @@ export function* isUserAuthenticated() {
|
||||
const eulaQuery = yield client.query({
|
||||
query: QUERY_EULA,
|
||||
variables: {
|
||||
now: day()
|
||||
now: dayjs()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -242,6 +242,10 @@ export function* signInSuccessSaga({ payload }) {
|
||||
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
|
||||
window.$crisp.push(["set", "session:segments", [["user"]]]);
|
||||
},
|
||||
rome: () => {
|
||||
window.$zoho.salesiq.visitor.name(payload.displayName || payload.email);
|
||||
window.$zoho.salesiq.visitor.email(payload.email);
|
||||
},
|
||||
promanager: () => {
|
||||
Userpilot.identify(payload.email, {
|
||||
email: payload.email
|
||||
@@ -310,8 +314,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
try {
|
||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||
try {
|
||||
//console.log("Setting shop timezone.");
|
||||
// dayjs.tz.setDefault(payload.timezone);
|
||||
dayjs.tz.setDefault(payload.timezone);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
@@ -371,6 +374,9 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
if (authRecord[0] && authRecord[0].user.validemail) {
|
||||
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||
}
|
||||
},
|
||||
rome: () => {
|
||||
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
"jobclosedwithbypass": "Job was invoiced using the master bypass password. ",
|
||||
"jobconverted": "Job converted and assigned number {{ro_number}}.",
|
||||
"jobdelivery": "Job intake completed. Status set to {{status}}. Actual completion is {{actual_completion}}.",
|
||||
"jobexported": "",
|
||||
"jobexported": "Job has been exported",
|
||||
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
|
||||
"jobimported": "Job imported.",
|
||||
"jobinproductionchange": "Job production status set to {{inproduction}}",
|
||||
@@ -2849,15 +2849,21 @@
|
||||
"jobs_in_production": "Jobs in Production",
|
||||
"tasks_in_production": "Tasks in Production",
|
||||
"tasks_on_board": "Tasks on Board",
|
||||
"tasks_in_view": "Tasks in View",
|
||||
"total_amount_in_production": "Dollars in Production",
|
||||
"total_amount_on_board": "Dollars on Board",
|
||||
"total_amount_in_view": "Dollars in View",
|
||||
"total_hours_in_production": "Hours in Production",
|
||||
"total_hours_on_board": "Hours on Board",
|
||||
"total_hours_in_view": "Hours in View",
|
||||
"total_jobs_on_board": "Jobs on Board",
|
||||
"total_jobs_in_view": "Jobs in View",
|
||||
"total_lab_in_production": "Body Hours in Production",
|
||||
"total_lab_on_board": "Body Hours on Board",
|
||||
"total_lab_in_view": "Body Hours in View",
|
||||
"total_lar_in_production": "Refinish Hours in Production",
|
||||
"total_lar_on_board": "Refinish Hours on Board"
|
||||
"total_lar_on_board": "Refinish Hours on Board",
|
||||
"total_lar_in_view": "Refinish Hours in View"
|
||||
},
|
||||
"statistics_title": "Statistics"
|
||||
},
|
||||
@@ -2869,15 +2875,21 @@
|
||||
"tasks": "Tasks",
|
||||
"tasks_in_production": "Tasks in Production",
|
||||
"tasks_on_board": "Tasks on Board",
|
||||
"tasks_in_view": "Tasks in View",
|
||||
"total_amount_in_production": "Dollars in Production",
|
||||
"total_amount_on_board": "Dollars on Board",
|
||||
"total_amount_in_view": "Dollars in View",
|
||||
"total_hours_in_production": "Hours in Production",
|
||||
"total_hours_on_board": "Hours on Board",
|
||||
"total_hours_in_view": "Hours in View",
|
||||
"total_jobs_on_board": "Jobs on Board",
|
||||
"total_jobs_in_view": "Jobs in View",
|
||||
"total_lab_in_production": "Body Hours in Production",
|
||||
"total_lab_on_board": "Body Hours on Board",
|
||||
"total_lab_in_view": "Body Hours in View",
|
||||
"total_lar_in_production": "Refinish Hours in Production",
|
||||
"total_lar_on_board": "Refinish Hours on Board"
|
||||
"total_lar_on_board": "Refinish Hours on Board",
|
||||
"total_lar_in_view": "Refinish Hours in View"
|
||||
},
|
||||
"successes": {
|
||||
"removed": "Job removed from production."
|
||||
@@ -3032,6 +3044,7 @@
|
||||
"production_by_target_date": "Production by Target Date",
|
||||
"production_by_technician": "Production by Technician",
|
||||
"production_by_technician_one": "Production filtered by Technician",
|
||||
"production_not_production_status": "Production not in Production Status",
|
||||
"production_over_time": "Production Level over Time",
|
||||
"psr_by_make": "Percent of Sales by Vehicle Make",
|
||||
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
|
||||
@@ -3174,6 +3187,7 @@
|
||||
"billid": "Bill",
|
||||
"completed": "Completed",
|
||||
"created_at": "Created At",
|
||||
"created_by": "Created By",
|
||||
"description": "Description",
|
||||
"due_date": "Due Date",
|
||||
"job": {
|
||||
@@ -3190,6 +3204,7 @@
|
||||
"medium": "Medium"
|
||||
},
|
||||
"priority": "Priority",
|
||||
"related_items": "Related Items",
|
||||
"remind_at": "Remind At",
|
||||
"title": "Title"
|
||||
},
|
||||
|
||||
@@ -2849,15 +2849,21 @@
|
||||
"jobs_in_production": "",
|
||||
"tasks_in_production": "",
|
||||
"tasks_on_board": "",
|
||||
"tasks_in_view": "",
|
||||
"total_amount_in_production": "",
|
||||
"total_amount_on_board": "",
|
||||
"total_amount_in_view": "",
|
||||
"total_hours_in_production": "",
|
||||
"total_hours_on_board": "",
|
||||
"total_hours_in_view": "",
|
||||
"total_jobs_on_board": "",
|
||||
"total_jobs_in_view": "",
|
||||
"total_lab_in_production": "",
|
||||
"total_lab_on_board": "",
|
||||
"total_lab_in_view": "",
|
||||
"total_lar_in_production": "",
|
||||
"total_lar_on_board": ""
|
||||
"total_lar_on_board": "",
|
||||
"total_lar_in_view": ""
|
||||
},
|
||||
"statistics_title": ""
|
||||
},
|
||||
@@ -2869,15 +2875,21 @@
|
||||
"tasks": "",
|
||||
"tasks_in_production": "",
|
||||
"tasks_on_board": "",
|
||||
"tasks_in_view": "",
|
||||
"total_amount_in_production": "",
|
||||
"total_amount_on_board": "",
|
||||
"total_amount_in_view": "",
|
||||
"total_hours_in_production": "",
|
||||
"total_hours_on_board": "",
|
||||
"total_hours_in_view": "",
|
||||
"total_jobs_on_board": "",
|
||||
"total_jobs_in_view": "",
|
||||
"total_lab_in_production": "",
|
||||
"total_lab_on_board": "",
|
||||
"total_lab_in_view": "",
|
||||
"total_lar_in_production": "",
|
||||
"total_lar_on_board": ""
|
||||
"total_lar_on_board": "",
|
||||
"total_lar_in_view": ""
|
||||
},
|
||||
"successes": {
|
||||
"removed": ""
|
||||
@@ -3032,6 +3044,7 @@
|
||||
"production_by_target_date": "",
|
||||
"production_by_technician": "",
|
||||
"production_by_technician_one": "",
|
||||
"production_not_production_status": "",
|
||||
"production_over_time": "",
|
||||
"psr_by_make": "",
|
||||
"purchase_return_ratio_grouped_by_vendor_detail": "",
|
||||
@@ -3174,6 +3187,7 @@
|
||||
"billid": "",
|
||||
"completed": "",
|
||||
"created_at": "",
|
||||
"created_by": "",
|
||||
"description": "",
|
||||
"due_date": "",
|
||||
"job": {
|
||||
@@ -3190,6 +3204,7 @@
|
||||
"medium": ""
|
||||
},
|
||||
"priority": "",
|
||||
"related_items": "",
|
||||
"remind_at": "",
|
||||
"title": ""
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user