Compare commits
1 Commits
feature/IO
...
hotfix/202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3775789e9d |
@@ -11,6 +11,7 @@ node_modules
|
|||||||
# Files to exclude
|
# Files to exclude
|
||||||
.ebignore
|
.ebignore
|
||||||
.editorconfig
|
.editorconfig
|
||||||
|
.eslintrc.json
|
||||||
.gitignore
|
.gitignore
|
||||||
.prettierrc.js
|
.prettierrc.js
|
||||||
Dockerfile
|
Dockerfile
|
||||||
@@ -18,6 +19,6 @@ README.MD
|
|||||||
bodyshop_translations.babel
|
bodyshop_translations.babel
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
ecosystem.config.js
|
ecosystem.config.js
|
||||||
eslint.config.mjs
|
|
||||||
# Optional: Exclude logs and temporary files
|
# Optional: Exclude logs and temporary files
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
19
.eslintrc.json
Normal file
19
.eslintrc.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"globals": {
|
||||||
|
"Atomics": "readonly",
|
||||||
|
"SharedArrayBuffer": "readonly"
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off"
|
||||||
|
},
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
@@ -1,96 +1,116 @@
|
|||||||
// index.js
|
// index.js
|
||||||
|
|
||||||
import express from "express";
|
import express from 'express';
|
||||||
import fetch from "node-fetch";
|
import fetch from 'node-fetch';
|
||||||
import { simpleParser } from "mailparser";
|
import {simpleParser} from 'mailparser';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = 3334;
|
const PORT = 3334;
|
||||||
|
|
||||||
app.get("/", async (req, res) => {
|
app.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("http://localhost:4566/_aws/ses");
|
const response = await fetch('http://localhost:4566/_aws/ses');
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Network response was not 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');
|
||||||
}
|
}
|
||||||
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) {
|
async function parseMessages(messages) {
|
||||||
const parsedMessages = await Promise.all(
|
const parsedMessages = await Promise.all(
|
||||||
messages.map(async (message, index) => {
|
messages.map(async (message, index) => {
|
||||||
try {
|
try {
|
||||||
const parsed = await simpleParser(message.RawData);
|
const parsed = await simpleParser(message.RawData);
|
||||||
return `
|
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: lightgray">
|
||||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: white">
|
<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">
|
||||||
<div class="mb-2"><span class="font-semibold">From:</span> ${message.Source}</div>
|
<span class="font-bold text-lg">Message ${index + 1}</span>
|
||||||
<div class="mb-2"><span class="font-semibold">To:</span> ${parsed.to.text || "No To Address"}</div>
|
</div>
|
||||||
<div class="mb-2"><span class="font-semibold">Subject:</span> ${parsed.subject || "No Subject"}</div>
|
<div class="mb-2">
|
||||||
<div class="mb-2"><span class="font-semibold">Region:</span> ${message.Region}</div>
|
<span class="font-semibold">From:</span> ${message.Source}
|
||||||
<div class="mb-2"><span class="font-semibold">Timestamp:</span> ${message.Timestamp}</div>
|
</div>
|
||||||
</div>
|
<div class="mb-2">
|
||||||
<div class="prose">${parsed.html || parsed.textAsHtml || "No HTML content available"}</div>
|
<span class="font-semibold">Region:</span> ${message.Region}
|
||||||
</div>
|
</div>
|
||||||
`;
|
<div class="mb-2">
|
||||||
} catch (error) {
|
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
||||||
console.error("Error parsing email:", error);
|
</div>
|
||||||
return `
|
</div>
|
||||||
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
<div class="prose">
|
||||||
<div class="mb-2"><span class="font-bold text-lg">Message ${index + 1}</span></div>
|
${parsed.html || parsed.textAsHtml || 'No HTML content available'}
|
||||||
<div class="mb-2"><span class="font-semibold">From:</span> ${message.Source}</div>
|
</div>
|
||||||
<div class="mb-2"><span class="font-semibold">Region:</span> ${message.Region}</div>
|
</div>
|
||||||
<div class="mb-2"><span class="font-semibold">Timestamp:</span> ${message.Timestamp}</div>
|
`;
|
||||||
<div class="text-red-500">Error parsing email content</div>
|
} catch (error) {
|
||||||
</div>
|
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>
|
||||||
return parsedMessages.join("");
|
</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) {
|
function renderHtml(messagesHtml) {
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Email Messages Viewer</title>
|
<title>Email Messages Viewer</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-color: #f3f4f6;
|
background-color: #f3f4f6;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 50px auto;
|
margin: 50px auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
.prose {
|
.prose {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container bg-white shadow-lg rounded-lg p-6">
|
<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>
|
<h1 class="text-2xl font-bold text-center mb-6">Email Messages Viewer</h1>
|
||||||
<div id="messages-container">${messagesHtml}</div>
|
<div id="messages-container">
|
||||||
</div>
|
${messagesHtml}
|
||||||
</body>
|
</div>
|
||||||
</html>
|
</div>
|
||||||
`;
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server is running on http://localhost:${PORT}`);
|
console.log(`Server is running on http://localhost:${PORT}`);
|
||||||
});
|
});
|
||||||
42
_reference/localEmailViewer/package-lock.json
generated
42
_reference/localEmailViewer/package-lock.json
generated
@@ -10,7 +10,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"mailparser": "^3.7.4",
|
"mailparser": "^3.7.2",
|
||||||
"node-fetch": "^3.3.2"
|
"node-fetch": "^3.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -634,9 +634,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/libmime": {
|
"node_modules/libmime": {
|
||||||
"version": "5.3.7",
|
"version": "5.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.6.tgz",
|
||||||
"integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==",
|
"integrity": "sha512-j9mBC7eiqi6fgBPAGvKCXJKJSIASanYF4EeA4iBzSG0HxQxmXnR3KbyWqTn4CwsKSebqCv2f5XZfAO6sKzgvwA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"encoding-japanese": "2.2.0",
|
"encoding-japanese": "2.2.0",
|
||||||
@@ -661,31 +661,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mailparser": {
|
"node_modules/mailparser": {
|
||||||
"version": "3.7.4",
|
"version": "3.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.2.tgz",
|
||||||
"integrity": "sha512-Beh4yyR4jLq3CZZ32asajByrXnW8dLyKCAQD3WvtTiBnMtFWhxO+wa93F6sJNjDmfjxXs4NRNjw3XAGLqZR3Vg==",
|
"integrity": "sha512-iI0p2TCcIodR1qGiRoDBBwboSSff50vQAWytM5JRggLfABa4hHYCf3YVujtuzV454xrOP352VsAPIzviqMTo4Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"encoding-japanese": "2.2.0",
|
"encoding-japanese": "2.2.0",
|
||||||
"he": "1.2.0",
|
"he": "1.2.0",
|
||||||
"html-to-text": "9.0.5",
|
"html-to-text": "9.0.5",
|
||||||
"iconv-lite": "0.6.3",
|
"iconv-lite": "0.6.3",
|
||||||
"libmime": "5.3.7",
|
"libmime": "5.3.6",
|
||||||
"linkify-it": "5.0.0",
|
"linkify-it": "5.0.0",
|
||||||
"mailsplit": "5.4.5",
|
"mailsplit": "5.4.2",
|
||||||
"nodemailer": "7.0.4",
|
"nodemailer": "6.9.16",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"tlds": "1.259.0"
|
"tlds": "1.255.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mailsplit": {
|
"node_modules/mailsplit": {
|
||||||
"version": "5.4.5",
|
"version": "5.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.2.tgz",
|
||||||
"integrity": "sha512-oMfhmvclR689IIaQmIcR5nODnZRRVwAKtqFT407TIvmhX2OLUBnshUTcxzQBt3+96sZVDud9NfSe1NxAkUNXEQ==",
|
"integrity": "sha512-4cczG/3Iu3pyl8JgQ76dKkisurZTmxMrA4dj/e8d2jKYcFTZ7MxOzg1gTioTDMPuFXwTrVuN/gxhkrO7wLg7qA==",
|
||||||
"license": "(MIT OR EUPL-1.1+)",
|
"license": "(MIT OR EUPL-1.1+)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libbase64": "1.3.0",
|
"libbase64": "1.3.0",
|
||||||
"libmime": "5.3.7",
|
"libmime": "5.3.6",
|
||||||
"libqp": "2.1.1"
|
"libqp": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -793,9 +793,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemailer": {
|
"node_modules/nodemailer": {
|
||||||
"version": "7.0.4",
|
"version": "6.9.16",
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz",
|
||||||
"integrity": "sha512-9O00Vh89/Ld2EcVCqJ/etd7u20UhME0f/NToPfArwPEe1Don1zy4mAIz6ariRr7mJ2RDxtaDzN0WJVdVXPtZaw==",
|
"integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==",
|
||||||
"license": "MIT-0",
|
"license": "MIT-0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -1114,9 +1114,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tlds": {
|
"node_modules/tlds": {
|
||||||
"version": "1.259.0",
|
"version": "1.255.0",
|
||||||
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.259.0.tgz",
|
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz",
|
||||||
"integrity": "sha512-AldGGlDP0PNgwppe2quAvuBl18UcjuNtOnDuUkqhd6ipPqrYYBt3aTxK1QTsBVknk97lS2JcafWMghjGWFtunw==",
|
"integrity": "sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tlds": "bin.js"
|
"tlds": "bin.js"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"mailparser": "^3.7.4",
|
"mailparser": "^3.7.2",
|
||||||
"node-fetch": "^3.3.2"
|
"node-fetch": "^3.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
# PATCH /integrations/parts-management/job/:id/status
|
|
||||||
|
|
||||||
Update (patch) the status of a job created under parts management. This endpoint is only available
|
|
||||||
for jobs whose parent bodyshop has an `external_shop_id` (i.e., is provisioned for parts
|
|
||||||
management).
|
|
||||||
|
|
||||||
## Endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
PATCH /integrations/parts-management/job/:id/status
|
|
||||||
```
|
|
||||||
|
|
||||||
- `:id` is the UUID of the job to update.
|
|
||||||
|
|
||||||
## Request Headers
|
|
||||||
|
|
||||||
- `Authorization`: (if required by your integration middleware)
|
|
||||||
- `Content-Type: application/json`
|
|
||||||
|
|
||||||
## Request Body
|
|
||||||
|
|
||||||
Send a JSON object with the following field:
|
|
||||||
|
|
||||||
- `status` (string, required): The new status for the job.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
PATCH /integrations/parts-management/job/123e4567-e89b-12d3-a456-426614174000/status
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "IN_PROGRESS"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Response
|
|
||||||
|
|
||||||
- **200 OK**
|
|
||||||
- Returns the updated job object with the new status.
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
||||||
"status": "IN_PROGRESS",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Responses
|
|
||||||
|
|
||||||
- **400 Bad Request**: Missing status field, or parent bodyshop does not have an `external_shop_id`.
|
|
||||||
- **404 Not Found**: No job found with the given ID.
|
|
||||||
- **500 Internal Server Error**: Unexpected error.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Only jobs whose parent bodyshop has an `external_shop_id` can be patched via this route.
|
|
||||||
- Fields other than `status` will be ignored if included in the request body.
|
|
||||||
- The route is protected by the same middleware as other parts management endpoints.
|
|
||||||
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
# PATCH /integrations/parts-management/provision/:id
|
|
||||||
|
|
||||||
Update (patch) select fields for a parts management bodyshop. Only available for shops that have an
|
|
||||||
`external_shop_id` (i.e., are provisioned for parts management).
|
|
||||||
|
|
||||||
## Endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
PATCH /integrations/parts-management/provision/:id
|
|
||||||
```
|
|
||||||
|
|
||||||
- `:id` is the UUID of the bodyshop to update.
|
|
||||||
|
|
||||||
## Request Headers
|
|
||||||
|
|
||||||
- `Authorization`: (if required by your integration middleware)
|
|
||||||
- `Content-Type: application/json`
|
|
||||||
|
|
||||||
## Request Body
|
|
||||||
|
|
||||||
Send a JSON object with one or more of the following fields to update:
|
|
||||||
|
|
||||||
- `shopname` (string)
|
|
||||||
- `address1` (string)
|
|
||||||
- `address2` (string, optional)
|
|
||||||
- `city` (string)
|
|
||||||
- `state` (string)
|
|
||||||
- `zip_post` (string)
|
|
||||||
- `country` (string)
|
|
||||||
- `email` (string, shop's email, not user email)
|
|
||||||
- `timezone` (string)
|
|
||||||
- `phone` (string)
|
|
||||||
- `logo_img_path` (object, e.g. `{ src, width, height, headerMargin }`)
|
|
||||||
|
|
||||||
Any fields not included in the request body will remain unchanged.
|
|
||||||
|
|
||||||
## Example Request
|
|
||||||
|
|
||||||
```
|
|
||||||
PATCH /integrations/parts-management/provision/123e4567-e89b-12d3-a456-426614174000
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"shopname": "New Shop Name",
|
|
||||||
"address1": "123 Main St",
|
|
||||||
"city": "Springfield",
|
|
||||||
"state": "IL",
|
|
||||||
"zip_post": "62704",
|
|
||||||
"country": "USA",
|
|
||||||
"email": "shop@example.com",
|
|
||||||
"timezone": "America/Chicago",
|
|
||||||
"phone": "555-123-4567",
|
|
||||||
"logo_img_path": {
|
|
||||||
"src": "https://example.com/logo.png",
|
|
||||||
"width": "200",
|
|
||||||
"height": "100",
|
|
||||||
"headerMargin": 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Response
|
|
||||||
|
|
||||||
- **200 OK**
|
|
||||||
- Returns the updated shop object with the patched fields.
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
||||||
"shopname": "New Shop Name",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Responses
|
|
||||||
|
|
||||||
- **400 Bad Request**: No valid fields provided, or shop does not have an `external_shop_id`.
|
|
||||||
- **404 Not Found**: No shop found with the given ID.
|
|
||||||
- **500 Internal Server Error**: Unexpected error.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Only shops with an `external_shop_id` can be patched via this route.
|
|
||||||
- Fields not listed above will be ignored if included in the request body.
|
|
||||||
- The route is protected by the same middleware as other parts management endpoints.
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
services:
|
|
||||||
ragmate:
|
|
||||||
image: ghcr.io/ragmate/ragmate:latest
|
|
||||||
ports:
|
|
||||||
- "11434:11434"
|
|
||||||
env_file:
|
|
||||||
- .ragmate.env
|
|
||||||
volumes:
|
|
||||||
- .:/project
|
|
||||||
- ./docker_data/ragmate:/apps/cache
|
|
||||||
@@ -5305,27 +5305,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>ro_posting</name>
|
|
||||||
<definition_loaded>false</definition_loaded>
|
|
||||||
<description></description>
|
|
||||||
<comment></comment>
|
|
||||||
<default_text></default_text>
|
|
||||||
<translations>
|
|
||||||
<translation>
|
|
||||||
<language>en-US</language>
|
|
||||||
<approved>false</approved>
|
|
||||||
</translation>
|
|
||||||
<translation>
|
|
||||||
<language>es-MX</language>
|
|
||||||
<approved>false</approved>
|
|
||||||
</translation>
|
|
||||||
<translation>
|
|
||||||
<language>fr-CA</language>
|
|
||||||
<approved>false</approved>
|
|
||||||
</translation>
|
|
||||||
</translations>
|
|
||||||
</concept_node>
|
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>sendmaterialscosting</name>
|
<name>sendmaterialscosting</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -14,7 +14,3 @@ VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
|||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
TEST_USERNAME="test@imex.dev"
|
TEST_USERNAME="test@imex.dev"
|
||||||
TEST_PASSWORD="test123"
|
TEST_PASSWORD="test123"
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
|
||||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
|
||||||
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
|
|
||||||
@@ -16,7 +16,3 @@ VITE_APP_COUNTRY=USA
|
|||||||
VITE_APP_INSTANCE=ROME
|
VITE_APP_INSTANCE=ROME
|
||||||
TEST_USERNAME="test@imex.dev"
|
TEST_USERNAME="test@imex.dev"
|
||||||
TEST_PASSWORD="test123"
|
TEST_PASSWORD="test123"
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
|
||||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
|
||||||
VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
|
|
||||||
@@ -13,7 +13,3 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.imex.online/
|
|||||||
VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
||||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
|
||||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
|
||||||
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
|
|
||||||
@@ -13,7 +13,3 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
|
|||||||
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
||||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||||
VITE_APP_INSTANCE=ROME
|
VITE_APP_INSTANCE=ROME
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
|
||||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
|
||||||
VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
|
|
||||||
@@ -13,7 +13,3 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online
|
|||||||
VITE_APP_IS_TEST=true
|
VITE_APP_IS_TEST=true
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
|
||||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
|
||||||
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
|
|
||||||
@@ -13,7 +13,3 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
|||||||
VITE_APP_IS_TEST=true
|
VITE_APP_IS_TEST=true
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=ROME
|
VITE_APP_INSTANCE=ROME
|
||||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
|
||||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
|
||||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
|
||||||
VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
|
|
||||||
8
client/.eslintrc
Normal file
8
client/.eslintrc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"react-app"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"no-useless-rename": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@ import globals from "globals";
|
|||||||
import pluginJs from "@eslint/js";
|
import pluginJs from "@eslint/js";
|
||||||
import pluginReact from "eslint-plugin-react";
|
import pluginReact from "eslint-plugin-react";
|
||||||
|
|
||||||
/** @type {import("eslint").Linter.Config[]} */
|
/** @type {import('eslint').Linter.Config[]} */
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{ ignores: ["node_modules/**", "dist/**", "build/**", "dev-dist/**"] },
|
|
||||||
{
|
{
|
||||||
files: ["**/*.{js,mjs,cjs,jsx}"]
|
files: ["**/*.{js,mjs,cjs,jsx}"]
|
||||||
},
|
},
|
||||||
@@ -12,13 +12,9 @@ export default [
|
|||||||
pluginJs.configs.recommended,
|
pluginJs.configs.recommended,
|
||||||
{
|
{
|
||||||
...pluginReact.configs.flat.recommended,
|
...pluginReact.configs.flat.recommended,
|
||||||
settings: {
|
|
||||||
react: { version: "detect" }
|
|
||||||
},
|
|
||||||
rules: {
|
rules: {
|
||||||
...pluginReact.configs.flat.recommended.rules,
|
...pluginReact.configs.flat.recommended.rules,
|
||||||
"react/prop-types": 0,
|
"react/prop-types": 0
|
||||||
"react/no-children-prop": 0 // Disable react/no-children-prop rule
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pluginReact.configs.flat["jsx-runtime"]
|
pluginReact.configs.flat["jsx-runtime"]
|
||||||
|
|||||||
6075
client/package-lock.json
generated
6075
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,61 +8,58 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-browser": "^2.25.2",
|
"@ant-design/pro-layout": "^7.22.4",
|
||||||
"@ant-design/pro-layout": "^7.22.6",
|
"@apollo/client": "^3.13.6",
|
||||||
"@apollo/client": "^3.13.9",
|
"@emotion/is-prop-valid": "^1.3.1",
|
||||||
"@emotion/is-prop-valid": "^1.4.0",
|
|
||||||
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
||||||
"@firebase/analytics": "^0.10.17",
|
"@firebase/analytics": "^0.10.16",
|
||||||
"@firebase/app": "^0.14.3",
|
"@firebase/app": "^0.13.1",
|
||||||
"@firebase/auth": "^1.10.8",
|
"@firebase/auth": "^1.10.6",
|
||||||
"@firebase/firestore": "^4.9.2",
|
"@firebase/firestore": "^4.7.17",
|
||||||
"@firebase/messaging": "^0.12.22",
|
"@firebase/messaging": "^0.12.21",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.9.0",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"@sentry/cli": "^2.56.0",
|
"@sentry/cli": "^2.47.1",
|
||||||
"@sentry/react": "^9.43.0",
|
"@sentry/react": "^9.38.0",
|
||||||
"@sentry/vite-plugin": "^4.3.0",
|
"@sentry/vite-plugin": "^3.5.0",
|
||||||
"@splitsoftware/splitio-react": "^2.5.0",
|
"@splitsoftware/splitio-react": "^2.3.1",
|
||||||
"@tanem/react-nprogress": "^5.0.53",
|
"@tanem/react-nprogress": "^5.0.53",
|
||||||
"antd": "^5.27.4",
|
"antd": "^5.25.4",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^4.4.0",
|
"apollo-link-sentry": "^4.3.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.8.4",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"css-box-model": "^1.2.1",
|
"css-box-model": "^1.2.1",
|
||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.13",
|
||||||
"dayjs-business-days2": "^1.3.0",
|
"dayjs-business-days2": "^1.3.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^16.4.7",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"graphql": "^16.11.0",
|
"graphql": "^16.11.0",
|
||||||
"i18next": "^25.5.3",
|
"i18next": "^24.2.3",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"libphonenumber-js": "^1.12.23",
|
"libphonenumber-js": "^1.12.10",
|
||||||
"lightningcss": "^1.30.2",
|
|
||||||
"logrocket": "^9.0.2",
|
"logrocket": "^9.0.2",
|
||||||
"markerjs2": "^2.32.7",
|
"markerjs2": "^2.32.4",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"normalize-url": "^8.1.0",
|
"normalize-url": "^8.0.2",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"phone": "^3.1.67",
|
"phone": "^3.1.59",
|
||||||
"posthog-js": "^1.271.0",
|
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.3.1",
|
"query-string": "^9.2.0",
|
||||||
"raf-schd": "^4.0.3",
|
"raf-schd": "^4.0.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-big-calendar": "^1.19.4",
|
"react-big-calendar": "^1.19.2",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^8.0.1",
|
"react-cookie": "^8.0.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.1",
|
"react-grid-gallery": "^1.0.1",
|
||||||
"react-grid-layout": "1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
"react-i18next": "^15.7.3",
|
"react-i18next": "^15.5.2",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
@@ -73,7 +70,7 @@
|
|||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
"react-router-dom": "^6.30.0",
|
"react-router-dom": "^6.30.0",
|
||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-virtuoso": "^4.14.1",
|
"react-virtuoso": "^4.12.8",
|
||||||
"recharts": "^2.15.2",
|
"recharts": "^2.15.2",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-actions": "^3.0.3",
|
"redux-actions": "^3.0.3",
|
||||||
@@ -81,9 +78,9 @@
|
|||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.1",
|
"reselect": "^5.1.1",
|
||||||
"sass": "^1.93.2",
|
"sass": "^1.89.1",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"styled-components": "^6.1.19",
|
"styled-components": "^6.1.18",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"use-memo-one": "^1.1.3",
|
"use-memo-one": "^1.1.3",
|
||||||
"vite-plugin-ejs": "^1.7.0",
|
"vite-plugin-ejs": "^1.7.0",
|
||||||
@@ -110,9 +107,7 @@
|
|||||||
"test:e2e:rome": "playwright test --config playwright.rome.config.js",
|
"test:e2e:rome": "playwright test --config playwright.rome.config.js",
|
||||||
"test:e2e:imex:headed": "playwright test --config playwright.config.js --headed",
|
"test:e2e:imex:headed": "playwright test --config playwright.config.js --headed",
|
||||||
"test:e2e:rome:headed": "playwright test --config playwright.rome.config.js --headed",
|
"test:e2e:rome:headed": "playwright test --config playwright.rome.config.js --headed",
|
||||||
"test:e2e:report": "playwright show-report",
|
"test:e2e:report": "playwright show-report"
|
||||||
"lint": "eslint .",
|
|
||||||
"lint:fix": "eslint . --fix"
|
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@@ -133,39 +128,40 @@
|
|||||||
"@rollup/rollup-linux-x64-gnu": "4.6.1"
|
"@rollup/rollup-linux-x64-gnu": "4.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^6.1.0",
|
"@ant-design/icons": "^6.0.0",
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@babel/preset-react": "^7.27.1",
|
"@babel/preset-react": "^7.27.1",
|
||||||
"@dotenvx/dotenvx": "^1.51.0",
|
"@dotenvx/dotenvx": "^1.47.5",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@eslint/js": "^9.37.0",
|
"@eslint/js": "^9.31.0",
|
||||||
"@playwright/test": "^1.56.0",
|
"@playwright/test": "^1.54.1",
|
||||||
"@sentry/webpack-plugin": "^4.3.0",
|
"@sentry/webpack-plugin": "^3.5.0",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.5.1",
|
||||||
"browserslist": "^4.26.3",
|
"browserslist": "^4.25.0",
|
||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.4.1",
|
||||||
"eslint": "^9.37.0",
|
"eslint": "^8.57.1",
|
||||||
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"memfs": "^4.48.1",
|
"memfs": "^4.17.2",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"playwright": "^1.56.0",
|
"playwright": "^1.54.1",
|
||||||
"react-error-overlay": "^6.1.0",
|
"react-error-overlay": "^6.1.0",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"vite": "^7.1.9",
|
"vite": "^6.3.5",
|
||||||
"vite-plugin-babel": "^1.3.2",
|
"vite-plugin-babel": "^1.3.1",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-node-polyfills": "^0.24.0",
|
"vite-plugin-node-polyfills": "^0.23.0",
|
||||||
"vite-plugin-pwa": "^1.0.3",
|
"vite-plugin-pwa": "^1.0.0",
|
||||||
"vite-plugin-style-import": "^2.0.0",
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
"vitest": "^3.2.4",
|
"vitest": "^3.2.3",
|
||||||
"workbox-window": "^7.3.0"
|
"workbox-window": "^7.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export default defineConfig({
|
|||||||
command: "npm run start:imex",
|
command: "npm run start:imex",
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export default defineConfig({
|
|||||||
command: "npm run start:rome",
|
command: "npm run start:rome",
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// Scripts for firebase and firebase messaging
|
// Scripts for firebase and firebase messaging
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js");
|
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js");
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js");
|
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js");
|
||||||
|
|
||||||
// Initialize the Firebase app in the service worker by passing the generated config
|
// Initialize the Firebase app in the service worker by passing the generated config
|
||||||
@@ -44,16 +42,13 @@ switch (this.location.hostname) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
firebase.initializeApp(firebaseConfig);
|
firebase.initializeApp(firebaseConfig);
|
||||||
|
|
||||||
// Retrieve firebase messaging
|
// Retrieve firebase messaging
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const messaging = firebase.messaging();
|
const messaging = firebase.messaging();
|
||||||
|
|
||||||
messaging.onBackgroundMessage(function (payload) {
|
messaging.onBackgroundMessage(function (payload) {
|
||||||
// Customize notification here
|
// Customize notification here
|
||||||
console.log("[firebase-messaging-sw.js] Received background message ", payload);
|
console.log("[firebase-messaging-sw.js] Received background message ", payload);
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
self.registration.showNotification(notificationTitle, notificationOptions);
|
self.registration.showNotification(notificationTitle, notificationOptions);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import { ApolloProvider } from "@apollo/client";
|
import { ApolloProvider } from "@apollo/client";
|
||||||
import * as Sentry from "@sentry/react";
|
|
||||||
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
||||||
import { ConfigProvider } from "antd";
|
import { ConfigProvider } from "antd";
|
||||||
import enLocale from "antd/es/locale/en_US";
|
import enLocale from "antd/es/locale/en_US";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect } from "react";
|
||||||
import { CookiesProvider } from "react-cookie";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect, useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||||
import { setDarkMode } from "../redux/application/application.actions";
|
|
||||||
import { selectDarkMode } from "../redux/application/application.selectors";
|
|
||||||
import { selectCurrentUser } from "../redux/user/user.selectors.js";
|
|
||||||
import client from "../utils/GraphQLClient";
|
import client from "../utils/GraphQLClient";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import getTheme from "./themeProvider";
|
import * as Sentry from "@sentry/react";
|
||||||
|
import themeProvider from "./themeProvider";
|
||||||
|
import { CookiesProvider } from "react-cookie";
|
||||||
|
|
||||||
// Base Split configuration
|
// Base Split configuration
|
||||||
const config = {
|
const config = {
|
||||||
@@ -28,53 +24,19 @@ const config = {
|
|||||||
function SplitClientProvider({ children }) {
|
function SplitClientProvider({ children }) {
|
||||||
const imexshopid = useSelector((state) => state.user.imexshopid); // Access imexshopid from Redux store
|
const imexshopid = useSelector((state) => state.user.imexshopid); // Access imexshopid from Redux store
|
||||||
const splitClient = useSplitClient({ key: imexshopid || "anon" }); // Use imexshopid or fallback to "anon"
|
const splitClient = useSplitClient({ key: imexshopid || "anon" }); // Use imexshopid or fallback to "anon"
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (splitClient && imexshopid) {
|
if (splitClient && imexshopid) {
|
||||||
|
// Log readiness for debugging; no need for ready() since isReady is available
|
||||||
console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`);
|
console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`);
|
||||||
}
|
}
|
||||||
}, [splitClient, imexshopid]);
|
}, [splitClient, imexshopid]);
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
function AppContainer() {
|
||||||
setDarkMode: (isDarkMode) => dispatch(setDarkMode(isDarkMode))
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
currentUser: selectCurrentUser
|
|
||||||
});
|
|
||||||
|
|
||||||
function AppContainer({ currentUser, setDarkMode }) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isDarkMode = useSelector(selectDarkMode);
|
|
||||||
const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]);
|
|
||||||
|
|
||||||
// Update data-theme attribute when dark mode changes
|
|
||||||
useEffect(() => {
|
|
||||||
document.documentElement.setAttribute("data-theme", isDarkMode ? "dark" : "light");
|
|
||||||
return () => document.documentElement.removeAttribute("data-theme");
|
|
||||||
}, [isDarkMode]);
|
|
||||||
|
|
||||||
// Sync Redux darkMode with localStorage on user change
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentUser?.uid) {
|
|
||||||
const savedMode = localStorage.getItem(`dark-mode-${currentUser.uid}`);
|
|
||||||
if (savedMode !== null) {
|
|
||||||
setDarkMode(JSON.parse(savedMode));
|
|
||||||
} else {
|
|
||||||
setDarkMode(false); // default to light mode
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setDarkMode(false);
|
|
||||||
}
|
|
||||||
}, [currentUser?.uid]);
|
|
||||||
|
|
||||||
// Persist darkMode to localStorage when it or user changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentUser?.uid) {
|
|
||||||
localStorage.setItem(`dark-mode-${currentUser.uid}`, JSON.stringify(isDarkMode));
|
|
||||||
}
|
|
||||||
}, [isDarkMode, currentUser?.uid]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CookiesProvider>
|
<CookiesProvider>
|
||||||
@@ -82,9 +44,10 @@ function AppContainer({ currentUser, setDarkMode }) {
|
|||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
input={{ autoComplete: "new-password" }}
|
input={{ autoComplete: "new-password" }}
|
||||||
locale={enLocale}
|
locale={enLocale}
|
||||||
theme={theme}
|
theme={themeProvider}
|
||||||
form={{
|
form={{
|
||||||
validateMessages: {
|
validateMessages: {
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
required: t("general.validation.required", { label: "${label}" })
|
required: t("general.validation.required", { label: "${label}" })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -101,4 +64,4 @@ function AppContainer({ currentUser, setDarkMode }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Sentry.withProfiler(connect(mapStateToProps, mapDispatchToProps)(AppContainer));
|
export default Sentry.withProfiler(AppContainer);
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import { connect } from "react-redux";
|
|||||||
import { Route, Routes, useNavigate } from "react-router-dom";
|
import { Route, Routes, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
import ErrorBoundary from "../components/error-boundary/error-boundary.component"; // Component Imports
|
||||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||||
import LandingPage from "../pages/landing/landing.page";
|
import LandingPage from "../pages/landing/landing.page";
|
||||||
import TechPageContainer from "../pages/tech/tech.page.container";
|
import TechPageContainer from "../pages/tech/tech.page.container";
|
||||||
import SimplifiedPartsPageContainer from "../pages/simplified-parts/simplified-parts.page.container.jsx";
|
import { setOnline } from "../redux/application/application.actions";
|
||||||
import { setIsPartsEntry, setOnline } from "../redux/application/application.actions";
|
import { selectOnline } from "../redux/application/application.selectors";
|
||||||
import { selectIsPartsEntry, selectOnline } from "../redux/application/application.selectors";
|
|
||||||
import { checkUserSession } from "../redux/user/user.actions";
|
import { checkUserSession } from "../redux/user/user.actions";
|
||||||
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
||||||
import PrivateRoute from "../components/PrivateRoute";
|
import PrivateRoute from "../components/PrivateRoute";
|
||||||
@@ -24,37 +23,26 @@ import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
|||||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||||
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
|
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
|
||||||
import SocketProvider from "../contexts/SocketIO/socketProvider.jsx";
|
import SocketProvider from "../contexts/SocketIO/socketProvider.jsx";
|
||||||
import SoundWrapper from "./SoundWrapper.jsx";
|
|
||||||
|
|
||||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||||
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
||||||
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
||||||
|
const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container"));
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
online: selectOnline,
|
online: selectOnline,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentEula: selectCurrentEula,
|
currentEula: selectCurrentEula
|
||||||
isPartsEntry: selectIsPartsEntry
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
checkUserSession: () => dispatch(checkUserSession()),
|
checkUserSession: () => dispatch(checkUserSession()),
|
||||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
setOnline: (isOnline) => dispatch(setOnline(isOnline))
|
||||||
setIsPartsEntry: (isParts) => dispatch(setIsPartsEntry(isParts))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export function App({
|
export function App({ bodyshop, checkUserSession, currentUser, online, setOnline, currentEula }) {
|
||||||
bodyshop,
|
|
||||||
checkUserSession,
|
|
||||||
currentUser,
|
|
||||||
online,
|
|
||||||
setOnline,
|
|
||||||
setIsPartsEntry,
|
|
||||||
currentEula,
|
|
||||||
isPartsEntry
|
|
||||||
}) {
|
|
||||||
const client = useSplitClient().client;
|
const client = useSplitClient().client;
|
||||||
const [listenersAdded, setListenersAdded] = useState(false);
|
const [listenersAdded, setListenersAdded] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -64,14 +52,12 @@ export function App({
|
|||||||
if (!navigator.onLine) {
|
if (!navigator.onLine) {
|
||||||
setOnline(false);
|
setOnline(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkUserSession();
|
checkUserSession();
|
||||||
}, [checkUserSession, setOnline]);
|
}, [checkUserSession, setOnline]);
|
||||||
|
|
||||||
useEffect(() => {
|
//const b = Grid.useBreakpoint();
|
||||||
const pathname = window.location.pathname;
|
// console.log("Breakpoints:", b);
|
||||||
const isParts = pathname === "/parts" || pathname.startsWith("/parts/");
|
|
||||||
setIsPartsEntry(isParts);
|
|
||||||
}, [setIsPartsEntry]);
|
|
||||||
|
|
||||||
// Associate event listeners, memoize to prevent multiple listeners being added
|
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -158,91 +144,86 @@ export function App({
|
|||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
bodyshop={bodyshop}
|
bodyshop={bodyshop}
|
||||||
workspaceCode={bodyshop?.tours_enabled ? "9BkbEseqNqxw8jUH" : ""}
|
workspaceCode={bodyshop?.tours_enabled ? "9BkbEseqNqxw8jUH" : ""}
|
||||||
isPartsEntry={isPartsEntry}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<SoundWrapper bodyshop={bodyshop}>
|
<Routes>
|
||||||
<Routes>
|
<Route
|
||||||
<Route
|
path="*"
|
||||||
path="*"
|
element={
|
||||||
element={
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<LandingPage />
|
||||||
<LandingPage />
|
</ErrorBoundary>
|
||||||
</ErrorBoundary>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/signin"
|
||||||
path="/signin"
|
element={
|
||||||
element={
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<SignInPage />
|
||||||
<SignInPage />
|
</ErrorBoundary>
|
||||||
</ErrorBoundary>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/resetpassword"
|
||||||
path="/resetpassword"
|
element={
|
||||||
element={
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<ResetPassword />
|
||||||
<ResetPassword />
|
</ErrorBoundary>
|
||||||
</ErrorBoundary>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/csi/:surveyId"
|
||||||
path="/csi/:surveyId"
|
element={
|
||||||
element={
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<CsiPage />
|
||||||
<CsiPage />
|
</ErrorBoundary>
|
||||||
</ErrorBoundary>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/disclaimer"
|
||||||
path="/disclaimer"
|
element={
|
||||||
element={
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<DisclaimerPage />
|
||||||
<DisclaimerPage />
|
</ErrorBoundary>
|
||||||
</ErrorBoundary>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/mp/:paymentIs"
|
||||||
path="/manage/*"
|
element={
|
||||||
element={
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<MobilePaymentContainer />
|
||||||
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
|
</ErrorBoundary>
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
}
|
||||||
</SocketProvider>
|
/>
|
||||||
</ErrorBoundary>
|
<Route
|
||||||
}
|
path="/manage/*"
|
||||||
>
|
element={
|
||||||
<Route path="*" element={<ManagePage />} />
|
<ErrorBoundary>
|
||||||
</Route>
|
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
|
||||||
<Route
|
|
||||||
path="/tech/*"
|
|
||||||
element={
|
|
||||||
<ErrorBoundary>
|
|
||||||
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
|
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
|
||||||
</SocketProvider>
|
|
||||||
</ErrorBoundary>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route path="*" element={<TechPageContainer />} />
|
|
||||||
</Route>
|
|
||||||
<Route
|
|
||||||
path="/parts/*"
|
|
||||||
element={
|
|
||||||
<ErrorBoundary>
|
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
</ErrorBoundary>
|
</SocketProvider>
|
||||||
}
|
</ErrorBoundary>
|
||||||
>
|
}
|
||||||
<Route path="*" element={<SimplifiedPartsPageContainer />} />
|
>
|
||||||
</Route>
|
<Route path="*" element={<ManagePage />} />
|
||||||
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized} />}>
|
</Route>
|
||||||
<Route path="*" element={<DocumentEditorContainer />} />
|
<Route
|
||||||
</Route>
|
path="/tech/*"
|
||||||
</Routes>
|
element={
|
||||||
</SoundWrapper>
|
<ErrorBoundary>
|
||||||
|
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
|
||||||
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
|
</SocketProvider>
|
||||||
|
</ErrorBoundary>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path="*" element={<TechPageContainer />} />
|
||||||
|
</Route>
|
||||||
|
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized} />}>
|
||||||
|
<Route path="*" element={<DocumentEditorContainer />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,226 +1,15 @@
|
|||||||
:root {
|
//Global Styles.
|
||||||
--table-stripe-bg: #f4f4f4; /* Light mode table stripe */
|
|
||||||
--menu-divider-color: #74695c; /* Light mode menu divider */
|
|
||||||
--menu-submenu-text: rgba(255, 255, 255, 0.65); /* Light mode submenu text */
|
|
||||||
--kanban-column-bg: #ddd; /* Light mode kanban column */
|
|
||||||
--alert-color: blue; /* Light mode alert */
|
|
||||||
--completion-soon-color: rgba(255, 140, 0, 0.8); /* Light mode completion soon */
|
|
||||||
--completion-past-color: rgba(255, 0, 0, 0.8); /* Light mode completion past */
|
|
||||||
--job-line-manual-color: tomato; /* Light mode job line manual */
|
|
||||||
--muted-button-color: lightgray; /* Light mode muted button */
|
|
||||||
--muted-button-hover-color: darkgrey; /* Light mode muted button hover */
|
|
||||||
--table-border-color: #ddd; /* Light mode table border */
|
|
||||||
--table-hover-bg: #f5f5f5; /* Light mode table hover */
|
|
||||||
--popover-bg: #fff; /* Light mode popover background */
|
|
||||||
--error-text: red; /* Light mode error message */
|
|
||||||
--no-jobs-text: #888; /* Light mode no jobs message */
|
|
||||||
--message-yours-bg: #eee; /* Light mode yours message background */
|
|
||||||
--message-mine-bg-start: #00d0ea; /* Light mode mine message gradient start */
|
|
||||||
--message-mine-bg-end: #0085d1; /* Light mode mine message gradient end */
|
|
||||||
--message-mine-text: white; /* Light mode mine message text */
|
|
||||||
--message-mine-tail-bg: white; /* Light mode mine/yours message tail */
|
|
||||||
--system-message-bg: #f5f5f5; /* Light mode system message background */
|
|
||||||
--system-message-text: #555; /* Light mode system message text */
|
|
||||||
--system-label-text: #888; /* Light mode system label/date text */
|
|
||||||
--message-icon-color: whitesmoke; /* Light mode message icon */
|
|
||||||
--eula-card-bg: lightgray; /* Light mode eula card background */
|
|
||||||
--notification-bg: #fff; /* Light mode notification background */
|
|
||||||
--notification-text: rgba(0, 0, 0, 0.85); /* Light mode notification text */
|
|
||||||
--notification-border: #d9d9d9; /* Light mode notification border */
|
|
||||||
--notification-header-bg: #fafafa; /* Light mode notification header background */
|
|
||||||
--notification-header-border: #f0f0f0; /* Light mode notification header border */
|
|
||||||
--notification-header-text: rgba(0, 0, 0, 0.85); /* Light mode notification header text */
|
|
||||||
--notification-toggle-icon: #1677ff; /* Light mode notification toggle icon */
|
|
||||||
--notification-switch-bg: #1677ff; /* Light mode notification switch background */
|
|
||||||
--notification-btn-link: #1677ff; /* Light mode notification link button */
|
|
||||||
--notification-btn-link-hover: #69b1ff; /* Light mode notification link button hover */
|
|
||||||
--notification-btn-link-disabled: rgba(0, 0, 0, 0.25); /* Light mode notification link button disabled */
|
|
||||||
--notification-btn-link-active: #0958d9; /* Light mode notification link button active */
|
|
||||||
--notification-read-bg: #fff; /* Light mode notification read background */
|
|
||||||
--notification-read-text: rgba(0, 0, 0, 0.65); /* Light mode notification read text */
|
|
||||||
--notification-unread-bg: #f5f5f5; /* Light mode notification unread background */
|
|
||||||
--notification-unread-text: rgba(0, 0, 0, 0.85); /* Light mode notification unread text */
|
|
||||||
--notification-item-hover-bg: #fafafa; /* Light mode notification item hover background */
|
|
||||||
--notification-ro-number: #1677ff; /* Light mode notification RO number */
|
|
||||||
--notification-relative-time: rgba(0, 0, 0, 0.45); /* Light mode notification relative time */
|
|
||||||
--alert-bg: #fff1f0; /* Light mode alert background */
|
|
||||||
--alert-text: rgba(0, 0, 0, 0.85); /* Light mode alert text */
|
|
||||||
--alert-border: #ffa39e; /* Light mode alert border */
|
|
||||||
--alert-message: #ff4d4f; /* Light mode alert message */
|
|
||||||
--share-badge-bg: #cccccc; /* Light mode share badge background */
|
|
||||||
--column-header-bg: #d0d0d0; /* Light mode column header background */
|
|
||||||
--footer-bg: #d0d0d0; /* Light mode footer background */
|
|
||||||
--tech-icon-color: orangered; /* Light mode tech icon color */
|
|
||||||
--clone-border-color: #1890ff; /* Light mode clone border color */
|
|
||||||
--event-arrived-bg: rgba(4, 141, 4, 0.4); /* Light mode arrived event background */
|
|
||||||
--event-block-bg: tomato; /* Light mode block event background */
|
|
||||||
--event-selected-bg: slategrey; /* Light mode selected event background */
|
|
||||||
--task-bg: #fff; /* Light mode task center background */
|
|
||||||
--task-text: rgba(0, 0, 0, 0.85); /* Light mode task text */
|
|
||||||
--task-border: #d9d9d9; /* Light mode task border */
|
|
||||||
--task-header-bg: #fafafa; /* Light mode task header background */
|
|
||||||
--task-header-border: #f0f0f0; /* Light mode task header border */
|
|
||||||
--task-section-bg: #f5f5f5; /* Light mode task section background */
|
|
||||||
--task-section-border: #e8e8e8; /* Light mode task section border */
|
|
||||||
--task-row-hover-bg: #f5f5f5; /* Light mode task row hover background */
|
|
||||||
--task-row-border: #f0f0f0; /* Light mode task row border */
|
|
||||||
--task-ro-number: #1677ff; /* Light mode task RO number */
|
|
||||||
--task-due-text: rgba(0, 0, 0, 0.45); /* Light mode task due text */
|
|
||||||
--task-button-bg: #1677ff; /* Light mode task button background */
|
|
||||||
--task-button-hover-bg: #4096ff; /* Light mode task button hover background */
|
|
||||||
--task-button-disabled-bg: #d9d9d9; /* Light mode task button disabled background */
|
|
||||||
--task-button-text: white; /* Light mode task button text */
|
|
||||||
--task-message-text: rgba(0, 0, 0, 0.45); /* Light mode task message text */
|
|
||||||
--mask-bg: rgba(0, 0, 0, 0.05); /* Light mode mask background */
|
|
||||||
--board-text-color: #393939; /* Light mode board text color */
|
|
||||||
--section-bg: #e3e3e3; /* Light mode section background */
|
|
||||||
--detail-text-color: #4d4d4d; /* Light mode detail text color */
|
|
||||||
--card-selected-bg: rgba(128, 128, 128, 0.2); /* Light mode selected card background */
|
|
||||||
--card-stripe-even-bg: #f0f2f5; /* Light mode even card background */
|
|
||||||
--card-stripe-odd-bg: #ffffff; /* Light mode odd card background */
|
|
||||||
--bar-border-color: #f0f2f5; /* Light mode bar border and background */
|
|
||||||
--tag-wrapper-bg: #f0f2f5; /* Light mode tag wrapper background */
|
|
||||||
--tag-wrapper-text: #000; /* Light mode tag wrapper text */
|
|
||||||
--preview-bg: lightgray; /* Light mode preview background */
|
|
||||||
--preview-border-color: #2196F3; /* Light mode preview border color */
|
|
||||||
--event-bg-fallback: #c4c4c4; /* Light mode event background fallback */
|
|
||||||
--card-bg-fallback: #ffffff; /* Light mode card background fallback */
|
|
||||||
--card-text-fallback: black; /* Light mode card text fallback */
|
|
||||||
--table-row-even-bg: rgb(236, 236, 236); /* Light mode table row even background */
|
|
||||||
--status-row-bg-fallback: #ffffff; /* Light mode status row fallback background */
|
|
||||||
--reset-link-color: #0000ff; /* Light mode reset link color */
|
|
||||||
--error-header-text: tomato; /* Light mode error header text */
|
|
||||||
--tooltip-bg: white; /* Light mode tooltip background */
|
|
||||||
--tooltip-border: gray; /* Light mode tooltip border */
|
|
||||||
--tooltip-text-fallback: black; /* Light mode tooltip text fallback */
|
|
||||||
--teams-button-bg: #6264A7; /* Light mode Teams button background */
|
|
||||||
--teams-button-border: #6264A7; /* Light mode Teams button border */
|
|
||||||
--teams-button-text: #FFFFFF; /* Light mode Teams button text and icon */
|
|
||||||
--content-bg: #fff; /* Light mode content background */
|
|
||||||
--legend-bg-fallback: #ffffff; /* Light mode legend background fallback */
|
|
||||||
--tech-content-bg: #fff; /* Light mode tech content background */
|
|
||||||
--today-bg: #ffffff; /* Light mode today background */
|
|
||||||
--today-text: #000000; /* Light mode today text */
|
|
||||||
--off-range-bg: #f8f8f8; /* Light mode off-range background */
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] {
|
|
||||||
--table-stripe-bg: #2a2a2a; /* Dark mode table stripe */
|
|
||||||
--menu-divider-color: #5c5c5c; /* Dark mode menu divider */
|
|
||||||
--menu-submenu-text: rgba(255, 255, 255, 0.85); /* Dark mode submenu text */
|
|
||||||
--kanban-column-bg: #333333; /* Dark mode kanban column */
|
|
||||||
--alert-color: #4da8ff; /* Dark mode alert */
|
|
||||||
--completion-soon-color: #ff8c1a; /* Dark mode completion soon */
|
|
||||||
--completion-past-color: #ff4d4f; /* Dark mode completion past */
|
|
||||||
--job-line-manual-color: #ff6347; /* Dark mode job line manual */
|
|
||||||
--muted-button-color: #666666; /* Dark mode muted button */
|
|
||||||
--muted-button-hover-color: #999999; /* Dark mode muted button hover */
|
|
||||||
--table-border-color: #5c5c5c; /* Dark mode table border */
|
|
||||||
--table-hover-bg: #2a2a2a; /* Dark mode table hover */
|
|
||||||
--popover-bg: #2a2a2a; /* Dark mode popover background */
|
|
||||||
--error-text: #ff4d4f; /* Dark mode error message */
|
|
||||||
--no-jobs-text: #999999; /* Dark mode no jobs message */
|
|
||||||
--message-yours-bg: #2a2a2a; /* Dark mode yours message background */
|
|
||||||
--message-mine-bg-start: #4da8ff; /* Dark mode mine message gradient start */
|
|
||||||
--message-mine-bg-end: #326ade; /* Dark mode mine message gradient end */
|
|
||||||
--message-mine-text: #ffffff; /* Dark mode mine message text */
|
|
||||||
--message-mine-tail-bg: #1f1f1f; /* Dark mode mine/yours message tail */
|
|
||||||
--system-message-bg: #333333; /* Dark mode system message background */
|
|
||||||
--system-message-text: #cccccc; /* Dark mode system message text */
|
|
||||||
--system-label-text: #999999; /* Dark mode system label/date text */
|
|
||||||
--message-icon-color: #cccccc; /* Dark mode message icon */
|
|
||||||
--eula-card-bg: #2a2a2a; /* Dark mode eula card background */
|
|
||||||
--notification-bg: #2a2a2a; /* Dark mode notification background */
|
|
||||||
--notification-text: rgba(255, 255, 255, 0.85); /* Dark mode notification text */
|
|
||||||
--notification-border: #5c5c5c; /* Dark mode notification border */
|
|
||||||
--notification-header-bg: #333333; /* Dark mode notification header background */
|
|
||||||
--notification-header-border: #444444; /* Dark mode notification header border */
|
|
||||||
--notification-header-text: rgba(255, 255, 255, 0.85); /* Dark mode notification header text */
|
|
||||||
--notification-toggle-icon: #4da8ff; /* Dark mode notification toggle icon */
|
|
||||||
--notification-switch-bg: #4da8ff; /* Dark mode notification switch background */
|
|
||||||
--notification-btn-link: #4da8ff; /* Dark mode notification link button */
|
|
||||||
--notification-btn-link-hover: #80c1ff; /* Dark mode notification link button hover */
|
|
||||||
--notification-btn-link-disabled: rgba(255, 255, 255, 0.25); /* Dark mode notification link button disabled */
|
|
||||||
--notification-btn-link-active: #2681ff; /* Dark mode notification link button active */
|
|
||||||
--notification-read-bg: #2a2a2a; /* Dark mode notification read background */
|
|
||||||
--notification-read-text: rgba(255, 255, 255, 0.65); /* Dark mode notification read text */
|
|
||||||
--notification-unread-bg: #333333; /* Dark mode notification unread background */
|
|
||||||
--notification-unread-text: rgba(255, 255, 255, 0.85); /* Dark mode notification unread text */
|
|
||||||
--notification-item-hover-bg: #3a3a3a; /* Dark mode notification item hover background */
|
|
||||||
--notification-ro-number: #4da8ff; /* Dark mode notification RO number */
|
|
||||||
--notification-relative-time: rgba(255, 255, 255, 0.45); /* Dark mode notification relative time */
|
|
||||||
--alert-bg: #3a1a1a; /* Dark mode alert background */
|
|
||||||
--alert-text: rgba(255, 255, 255, 0.85); /* Dark mode alert text */
|
|
||||||
--alert-border: #ff6666; /* Dark mode alert border */
|
|
||||||
--alert-message: #ff6666; /* Dark mode alert message */
|
|
||||||
--share-badge-bg: #666666; /* Dark mode share badge background */
|
|
||||||
--column-header-bg: #333333; /* Dark mode column header background */
|
|
||||||
--footer-bg: #333333; /* Dark mode footer background */
|
|
||||||
--tech-icon-color: #ff4500; /* Dark mode tech icon color */
|
|
||||||
--clone-border-color: #4da8ff; /* Dark mode clone border color */
|
|
||||||
--event-arrived-bg: rgba(4, 141, 4, 0.6); /* Dark mode arrived event background */
|
|
||||||
--event-block-bg: tomato; /* Dark mode block event background */
|
|
||||||
--event-selected-bg: #4a5e6e; /* Dark mode selected event background */
|
|
||||||
--task-bg: #2a2a2a; /* Dark mode task center background */
|
|
||||||
--task-text: rgba(255, 255, 255, 0.85); /* Dark mode task text */
|
|
||||||
--task-border: #5c5c5c; /* Dark mode task border */
|
|
||||||
--task-header-bg: #333333; /* Dark mode task header background */
|
|
||||||
--task-header-border: #444444; /* Dark mode task header border */
|
|
||||||
--task-section-bg: #333333; /* Dark mode task section background */
|
|
||||||
--task-section-border: #444444; /* Dark mode task section border */
|
|
||||||
--task-row-hover-bg: #3a3a3a; /* Dark mode task row hover background */
|
|
||||||
--task-row-border: #444444; /* Dark mode task row border */
|
|
||||||
--task-ro-number: #4da8ff; /* Dark mode task RO number */
|
|
||||||
--task-due-text: rgba(255, 255, 255, 0.45); /* Dark mode task due text */
|
|
||||||
--task-button-bg: #4da8ff; /* Dark mode task button background */
|
|
||||||
--task-button-hover-bg: #80c1ff; /* Dark mode task button hover background */
|
|
||||||
--task-button-disabled-bg: #666666; /* Dark mode task button disabled background */
|
|
||||||
--task-button-text: #ffffff; /* Dark mode task button text */
|
|
||||||
--task-message-text: rgba(255, 255, 255, 0.45); /* Dark mode task message text */
|
|
||||||
--mask-bg: rgba(255, 255, 255, 0.05); /* Dark mode mask background */
|
|
||||||
--board-text-color: #cccccc; /* Dark mode board text color */
|
|
||||||
--section-bg: #333333; /* Dark mode section background */
|
|
||||||
--detail-text-color: #bbbbbb; /* Dark mode detail text color */
|
|
||||||
--card-selected-bg: rgba(255, 255, 255, 0.1); /* Dark mode selected card background */
|
|
||||||
--card-stripe-even-bg: #2a2a2a; /* Dark mode even card background */
|
|
||||||
--card-stripe-odd-bg: #1f1f1f; /* Dark mode odd card background */
|
|
||||||
--bar-border-color: #2a2a2a; /* Dark mode bar border and background */
|
|
||||||
--tag-wrapper-bg: #2a2a2a; /* Dark mode tag wrapper background */
|
|
||||||
--tag-wrapper-text: #cccccc; /* Dark mode tag wrapper text */
|
|
||||||
--preview-bg: #2a2a2a; /* Dark mode preview background */
|
|
||||||
--preview-border-color: #4da8ff; /* Dark mode preview border color */
|
|
||||||
--event-bg-fallback: #262626; /* Dark mode event background fallback */
|
|
||||||
--card-bg-fallback: #2a2a2a; /* Dark mode card background fallback */
|
|
||||||
--card-text-fallback: #cccccc; /* Dark mode card text fallback */
|
|
||||||
--table-row-even-bg: #2a2a2a; /* Dark mode table row even background */
|
|
||||||
--status-row-bg-fallback: #1f1f1f; /* Dark mode status row fallback background */
|
|
||||||
--reset-link-color: #4da8ff; /* Dark mode reset link color */
|
|
||||||
--error-header-text: #ff6347; /* Dark mode error header text */
|
|
||||||
--tooltip-bg: #2a2a2a; /* Dark mode tooltip background */
|
|
||||||
--tooltip-border: #5c5c5c; /* Dark mode tooltip border */
|
|
||||||
--tooltip-text-fallback: #cccccc; /* Dark mode tooltip text fallback */
|
|
||||||
--teams-button-bg: #7b7dc4; /* Dark mode Teams button background */
|
|
||||||
--teams-button-border: #7b7dc4; /* Dark mode Teams button border */
|
|
||||||
--teams-button-text: #ffffff; /* Dark mode Teams button text and icon */
|
|
||||||
--content-bg: #2a2a2a; /* Dark mode content background */
|
|
||||||
--legend-bg-fallback: #2a2a2a; /* Dark mode legend background fallback */
|
|
||||||
--tech-content-bg: #2a2a2a; /* Dark mode tech content background */
|
|
||||||
--today-bg: #4a5e6e; /* Dark mode today background */
|
|
||||||
--today-text: #ffffff; /* Dark mode today text */
|
|
||||||
--off-range-bg: #333333; /* Dark mode off-range background */
|
|
||||||
--svg-background: #FFF; /* Dark mode SVG background */
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global Styles
|
|
||||||
@import "react-big-calendar/lib/sass/styles";
|
@import "react-big-calendar/lib/sass/styles";
|
||||||
|
|
||||||
.ant-menu-item-divider {
|
.ant-menu-item-divider {
|
||||||
border-bottom: 1px solid var(--menu-divider-color) !important;
|
border-bottom: 1px solid #74695c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Monitor this in dark mode to ensure text visibility
|
// TODO: This was added because the newest release of ant was making the text color and the background color the same on a selected header
|
||||||
|
// Tried all available tokens (https://ant.design/components/menu?locale=en-US) and even reverted all our custom styles, to no avail
|
||||||
|
// This should be kept an eye on, especially if implementing DARK MODE
|
||||||
.ant-menu-submenu-title {
|
.ant-menu-submenu-title {
|
||||||
color: var(--menu-submenu-text) !important;
|
color: rgba(255, 255, 255, 0.65) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.imex-table-header {
|
.imex-table-header {
|
||||||
@@ -257,7 +46,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ellipses {
|
.ellipses {
|
||||||
display: inline-block;
|
display: inline-block; /* for em, a, span, etc (inline by default) */
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: calc(95%);
|
width: calc(95%);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -271,23 +60,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scrollbar styles (uncomment if needed, updated for dark mode)
|
|
||||||
// ::-webkit-scrollbar-track {
|
// ::-webkit-scrollbar-track {
|
||||||
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||||
// border-radius: 0.2rem;
|
// border-radius: 0.2rem;
|
||||||
// background-color: var(--table-stripe-bg);
|
// background-color: #f5f5f5;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// ::-webkit-scrollbar {
|
// ::-webkit-scrollbar {
|
||||||
// width: 0.25rem;
|
// width: 0.25rem;
|
||||||
// max-height: 0.25rem;
|
// max-height: 0.25rem;
|
||||||
// background-color: var(--table-stripe-bg);
|
// background-color: #f5f5f5;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// ::-webkit-scrollbar-thumb {
|
// ::-webkit-scrollbar-thumb {
|
||||||
// border-radius: 0.2rem;
|
// border-radius: 0.2rem;
|
||||||
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||||
// background-color: var(--alert-color);
|
// background-color: #188fff;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
.ant-input-number-input,
|
.ant-input-number-input,
|
||||||
@@ -300,27 +88,28 @@
|
|||||||
|
|
||||||
.production-alert {
|
.production-alert {
|
||||||
animation: alertBlinker 1s linear infinite;
|
animation: alertBlinker 1s linear infinite;
|
||||||
color: var(--alert-color);
|
color: blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes alertBlinker {
|
@keyframes alertBlinker {
|
||||||
50% {
|
50% {
|
||||||
color: var(--completion-past-color);
|
color: red;
|
||||||
opacity: 100;
|
opacity: 100;
|
||||||
|
//opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.blue {
|
.blue {
|
||||||
color: var(--alert-color);
|
color: blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.production-completion-soon {
|
.production-completion-soon {
|
||||||
color: var(--completion-soon-color);
|
color: rgba(255, 140, 0, 0.8);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.production-completion-past {
|
.production-completion-past {
|
||||||
color: var(--completion-past-color);
|
color: rgba(255, 0, 0, 0.8);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +139,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-kanban-column {
|
.react-kanban-column {
|
||||||
background-color: var(--kanban-column-bg) !important;
|
background-color: #ddd !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.production-list-table {
|
.production-list-table {
|
||||||
@@ -362,18 +151,18 @@
|
|||||||
.ReactGridGallery_tile-icon-bar {
|
.ReactGridGallery_tile-icon-bar {
|
||||||
div {
|
div {
|
||||||
svg {
|
svg {
|
||||||
fill: var(--alert-color);
|
fill: #1890ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.job-line-manual {
|
.job-line-manual {
|
||||||
color: var(--job-line-manual-color);
|
color: tomato;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||||
background-color: var(--table-stripe-bg);
|
background-color: #f4f4f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rowWithColor > td {
|
.rowWithColor > td {
|
||||||
@@ -381,15 +170,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.muted-button {
|
.muted-button {
|
||||||
color: var(--muted-button-color);
|
color: lightgray;
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 16px;
|
font-size: 16px; /* Adjust as needed */
|
||||||
}
|
}
|
||||||
|
|
||||||
.muted-button:hover {
|
.muted-button:hover {
|
||||||
color: var(--muted-button-hover-color);
|
color: darkgrey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-alert-unordered-list {
|
.notification-alert-unordered-list {
|
||||||
@@ -401,31 +190,3 @@
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override react-big-calendar styles for dark mode only
|
|
||||||
[data-theme="dark"] {
|
|
||||||
.car-svg {
|
|
||||||
background-color: var(--svg-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rbc-today {
|
|
||||||
background-color: var(--today-bg);
|
|
||||||
color: var(--today-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rbc-off-range {
|
|
||||||
background-color: var(--off-range-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rbc-day-bg.rbc-today {
|
|
||||||
background-color: var(--today-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//.rbc-time-header-gutter {
|
|
||||||
// padding: 0;
|
|
||||||
//}
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { memo } from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { ProductFruits } from "react-product-fruits";
|
import { ProductFruits } from "react-product-fruits";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const ProductFruitsWrapper = memo(({ currentUser, bodyshop, workspaceCode, isPartsEntry }) => {
|
const ProductFruitsWrapper = React.memo(({ currentUser, bodyshop, workspaceCode }) => {
|
||||||
const featureProps = bodyshop?.features
|
const featureProps = bodyshop?.features
|
||||||
? Object.entries(bodyshop.features).reduce((acc, [key, value]) => {
|
? Object.entries(bodyshop.features).reduce((acc, [key, value]) => {
|
||||||
acc[key] = value === true || (typeof value === "string" && dayjs(value).isAfter(dayjs()));
|
acc[key] = value === true || (typeof value === "string" && dayjs(value).isAfter(dayjs()));
|
||||||
@@ -12,7 +12,6 @@ const ProductFruitsWrapper = memo(({ currentUser, bodyshop, workspaceCode, isPar
|
|||||||
: {};
|
: {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!isPartsEntry &&
|
|
||||||
workspaceCode &&
|
workspaceCode &&
|
||||||
currentUser?.authorized === true &&
|
currentUser?.authorized === true &&
|
||||||
currentUser?.email && (
|
currentUser?.email && (
|
||||||
@@ -31,8 +30,6 @@ const ProductFruitsWrapper = memo(({ currentUser, bodyshop, workspaceCode, isPar
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ProductFruitsWrapper.displayName = "ProductFruitsWrapper";
|
|
||||||
|
|
||||||
export default ProductFruitsWrapper;
|
export default ProductFruitsWrapper;
|
||||||
|
|
||||||
ProductFruitsWrapper.propTypes = {
|
ProductFruitsWrapper.propTypes = {
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useNotification } from "../contexts/Notifications/notificationContext.jsx";
|
|
||||||
import { initNewMessageSound, unlockAudio } from "./../utils/soundManager";
|
|
||||||
import { initSingleTabAudioLeader } from "../utils/singleTabAudioLeader";
|
|
||||||
|
|
||||||
export default function SoundWrapper({ children, bodyshop }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!bodyshop?.id) return;
|
|
||||||
|
|
||||||
// 1) Init single-tab leader election (only one tab should play sounds), scoped by bodyshopId
|
|
||||||
const cleanupLeader = initSingleTabAudioLeader(bodyshop.id);
|
|
||||||
|
|
||||||
// 2) Initialize base audio
|
|
||||||
initNewMessageSound("https://images.imex.online/app/messageTone.wav", 0.7);
|
|
||||||
|
|
||||||
// 3) Show a one-time prompt when autoplay blocks first play
|
|
||||||
const onNeedsUnlock = () => {
|
|
||||||
notification.info({
|
|
||||||
description: t("audio.manager.description"),
|
|
||||||
duration: 3
|
|
||||||
});
|
|
||||||
};
|
|
||||||
window.addEventListener("sound-needs-unlock", onNeedsUnlock);
|
|
||||||
|
|
||||||
// 4) Proactively unlock on first gesture (once per session)
|
|
||||||
const gesture = () => unlockAudio(bodyshop.id);
|
|
||||||
window.addEventListener("click", gesture, { once: true, passive: true });
|
|
||||||
window.addEventListener("touchstart", gesture, { once: true, passive: true });
|
|
||||||
window.addEventListener("keydown", gesture, { once: true });
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cleanupLeader();
|
|
||||||
window.removeEventListener("sound-needs-unlock", onNeedsUnlock);
|
|
||||||
// gesture listeners were added with {once:true}
|
|
||||||
};
|
|
||||||
}, [notification, t, bodyshop?.id]); // include bodyshop.id so this runs when org changes
|
|
||||||
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
||||||
@@ -4,42 +4,36 @@ import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
|||||||
|
|
||||||
const { defaultAlgorithm, darkAlgorithm } = theme;
|
const { defaultAlgorithm, darkAlgorithm } = theme;
|
||||||
|
|
||||||
|
let isDarkMode = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default theme
|
* Default theme
|
||||||
* @type {{components: {Menu: {itemDividerBorderColor: string}}}}
|
* @type {{components: {Menu: {itemDividerBorderColor: string}}}}
|
||||||
*/
|
*/
|
||||||
const defaultTheme = (isDarkMode) => ({
|
const defaultTheme = {
|
||||||
components: {
|
components: {
|
||||||
Table: {
|
Table: {
|
||||||
rowHoverBg: isDarkMode ? "#2a2a2a" : "#e7f3ff",
|
rowHoverBg: "#e7f3ff",
|
||||||
rowSelectedBg: isDarkMode ? "#333333" : "#e6f7ff",
|
rowSelectedBg: "#e6f7ff",
|
||||||
headerSortHoverBg: "transparent"
|
headerSortHoverBg: "transparent"
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
darkItemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
|
darkItemHoverBg: "#1890ff",
|
||||||
itemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
|
itemHoverBg: "#1890ff",
|
||||||
horizontalItemHoverBg: isDarkMode ? "#004a77" : "#1890ff"
|
horizontalItemHoverBg: "#1890ff"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: InstanceRenderMgr(
|
colorPrimary: InstanceRenderMgr({
|
||||||
{
|
imex: "#1890ff",
|
||||||
imex: isDarkMode ? "#4da8ff" : "#1890ff",
|
rome: "#326ade"
|
||||||
rome: isDarkMode ? "#5b8ce6" : "#326ade"
|
}),
|
||||||
},
|
colorInfo: InstanceRenderMgr({
|
||||||
isDarkMode
|
imex: "#1890ff",
|
||||||
),
|
rome: "#326ade"
|
||||||
colorInfo: InstanceRenderMgr(
|
})
|
||||||
{
|
|
||||||
imex: isDarkMode ? "#4da8ff" : "#1890ff",
|
|
||||||
rome: isDarkMode ? "#5b8ce6" : "#326ade"
|
|
||||||
},
|
|
||||||
isDarkMode
|
|
||||||
),
|
|
||||||
colorError: isDarkMode ? "#ff4d4f" : "#f5222d",
|
|
||||||
colorBgBase: isDarkMode ? "#1f1f1f" : "#ffffff" // Align with Ant Design dark mode
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Development theme
|
* Development theme
|
||||||
@@ -66,9 +60,8 @@ const prodTheme = {};
|
|||||||
|
|
||||||
const currentTheme = import.meta.env.DEV ? devTheme : prodTheme;
|
const currentTheme = import.meta.env.DEV ? devTheme : prodTheme;
|
||||||
|
|
||||||
const getTheme = (isDarkMode) => ({
|
const finaltheme = {
|
||||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||||
...defaultsDeep(currentTheme, defaultTheme)
|
...defaultsDeep(currentTheme, defaultTheme)
|
||||||
});
|
};
|
||||||
|
export default finaltheme;
|
||||||
export default getTheme;
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
function PrivateRoute({ isAuthorized }) {
|
function PrivateRoute({ component: Component, isAuthorized, ...rest }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Button } from "antd";
|
import { Button } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
|||||||
@@ -142,16 +142,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
<Link to={`/manage/jobs/${record.id}/close`}>
|
<Link to={`/manage/jobs/${record.id}/close`}>
|
||||||
<Button
|
<Button>{t("jobs.labels.viewallocations")}</Button>
|
||||||
style={{
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
verticalAlign: "middle"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("jobs.labels.viewallocations")}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Alert } from "antd";
|
import { Alert } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export default function AlertComponent(props) {
|
export default function AlertComponent(props) {
|
||||||
return <Alert {...props} />;
|
return <Alert {...props} />;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Button, InputNumber, Popover, Select } from "antd";
|
import { Button, InputNumber, Popover, Select } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import AllocationsAssignmentComponent from "./allocations-assignment.component";
|
import AllocationsAssignmentComponent from "./allocations-assignment.component";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
@@ -18,7 +18,7 @@ export default function AllocationsAssignmentContainer({ jobLineId, hours, refet
|
|||||||
|
|
||||||
const handleAssignment = () => {
|
const handleAssignment = () => {
|
||||||
insertAllocation({ variables: { alloc: { ...assignment } } })
|
insertAllocation({ variables: { alloc: { ...assignment } } })
|
||||||
.then(() => {
|
.then((r) => {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("allocations.successes.save")
|
message: t("allocations.successes.save")
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Button, Popover, Select } from "antd";
|
import { Button, Popover, Select } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import AllocationsBulkAssignment from "./allocations-bulk-assignment.component";
|
import AllocationsBulkAssignment from "./allocations-bulk-assignment.component";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
@@ -24,7 +24,7 @@ export default function AllocationsBulkAssignmentContainer({ jobLines, refetch }
|
|||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
insertAllocation({ variables: { alloc: allocs } }).then(() => {
|
insertAllocation({ variables: { alloc: allocs } }).then((r) => {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("employees.successes.save")
|
message: t("employees.successes.save")
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
|
import React from "react";
|
||||||
import { MdRemoveCircleOutline } from "react-icons/md";
|
import { MdRemoveCircleOutline } from "react-icons/md";
|
||||||
|
|
||||||
export default function AllocationsLabelComponent({ allocation, handleClick }) {
|
export default function AllocationsLabelComponent({ allocation, handleClick }) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
|
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
import AllocationsLabelComponent from "./allocations-employee-label.component";
|
import AllocationsLabelComponent from "./allocations-employee-label.component";
|
||||||
@@ -12,13 +13,13 @@ export default function AllocationsLabelContainer({ allocation, refetch }) {
|
|||||||
const handleClick = (e) => {
|
const handleClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
deleteAllocation({ variables: { id: allocation.id } })
|
deleteAllocation({ variables: { id: allocation.id } })
|
||||||
.then(() => {
|
.then((r) => {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("allocations.successes.deleted")
|
message: t("allocations.successes.deleted")
|
||||||
});
|
});
|
||||||
if (refetch) refetch();
|
if (refetch) refetch();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
notification["error"]({ message: t("allocations.errors.deleting") });
|
notification["error"]({ message: t("allocations.errors.deleting") });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Table } from "antd";
|
import { Table } from "antd";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import AuditTrailListComponent from "./audit-trail-list.component";
|
import AuditTrailListComponent from "./audit-trail-list.component";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Table } from "antd";
|
import { Table } from "antd";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import { List } from "antd";
|
import { List } from "antd";
|
||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
import { FaArrowRight } from "react-icons/fa";
|
import { FaArrowRight } from "react-icons/fa";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Popover, Tag } from "antd";
|
import { Popover, Tag } from "antd";
|
||||||
|
import React from "react";
|
||||||
import Barcode from "react-barcode";
|
import Barcode from "react-barcode";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
||||||
import { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||||
import "./bill-cm-returns-table.styles.scss";
|
import "./bill-cm-returns-table.styles.scss";
|
||||||
@@ -33,7 +33,7 @@ export default function BillCmdReturnsTableComponent({ form, returnLoading, retu
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.List name="outstanding_returns">
|
<Form.List name="outstanding_returns">
|
||||||
{(fields) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography.Title level={4}>{t("bills.labels.creditsnotreceived")}</Typography.Title>
|
<Typography.Title level={4}>{t("bills.labels.creditsnotreceived")}</Typography.Title>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
td {
|
td {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid var(--table-border-color);
|
border-bottom: 1px solid #ddd;
|
||||||
|
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 0px !important;
|
margin-bottom: 0px !important;
|
||||||
@@ -14,6 +14,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr:hover {
|
tr:hover {
|
||||||
background-color: var(--table-hover-bg);
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Popconfirm } from "antd";
|
import { Button, Popconfirm } from "antd";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DELETE_BILL } from "../../graphql/bills.queries";
|
import { DELETE_BILL } from "../../graphql/bills.queries";
|
||||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
@@ -43,7 +43,7 @@ export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!!!result.errors) {
|
||||||
notification["success"]({ message: t("bills.successes.deleted") });
|
notification["success"]({ message: t("bills.successes.deleted") });
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: jobid,
|
jobid: jobid,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { PageHeader } from "@ant-design/pro-layout";
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
@@ -10,6 +10,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { DELETE_BILL_LINE, INSERT_NEW_BILL_LINES, UPDATE_BILL_LINE } from "../../graphql/bill-lines.queries";
|
import { DELETE_BILL_LINE, INSERT_NEW_BILL_LINES, UPDATE_BILL_LINE } from "../../graphql/bill-lines.queries";
|
||||||
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
@@ -27,12 +28,13 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditcontainer);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditcontainer);
|
||||||
|
|
||||||
export function BillDetailEditcontainer({ insertAuditTrail, bodyshop }) {
|
export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail, bodyshop }) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -46,7 +48,7 @@ export function BillDetailEditcontainer({ insertAuditTrail, bodyshop }) {
|
|||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
|
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
|
||||||
variables: { billid: search.billid },
|
variables: { billid: search.billid },
|
||||||
skip: !search.billid,
|
skip: !!!search.billid,
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only"
|
nextFetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
@@ -69,7 +71,7 @@ export function BillDetailEditcontainer({ insertAuditTrail, bodyshop }) {
|
|||||||
setUpdateLoading(true);
|
setUpdateLoading(true);
|
||||||
//let adjustmentsToInsert = {};
|
//let adjustmentsToInsert = {};
|
||||||
|
|
||||||
const { billlines, ...bill } = values;
|
const { billlines, upload, ...bill } = values;
|
||||||
const updates = [];
|
const updates = [];
|
||||||
updates.push(
|
updates.push(
|
||||||
update_bill({
|
update_bill({
|
||||||
@@ -96,7 +98,6 @@ export function BillDetailEditcontainer({ insertAuditTrail, bodyshop }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
billlines.forEach((billline) => {
|
billlines.forEach((billline) => {
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
|
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
|
||||||
delete il.__typename;
|
delete il.__typename;
|
||||||
|
|
||||||
@@ -151,8 +152,8 @@ export function BillDetailEditcontainer({ insertAuditTrail, bodyshop }) {
|
|||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||||
|
|
||||||
const exported = data?.bills_by_pk && data.bills_by_pk.exported;
|
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||||
const isinhouse = data?.bills_by_pk && data.bills_by_pk.isinhouse;
|
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -182,8 +183,8 @@ export function BillDetailEditcontainer({ insertAuditTrail, bodyshop }) {
|
|||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
<BillReeportButtonComponent bill={data?.bills_by_pk} />
|
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
|
||||||
<BillMarkExportedButton bill={data?.bills_by_pk} />
|
<BillMarkExportedButton bill={data && data.bills_by_pk} />
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -219,11 +220,11 @@ const transformData = (data) => {
|
|||||||
billlines: data.bills_by_pk.billlines.map((i) => {
|
billlines: data.bills_by_pk.billlines.map((i) => {
|
||||||
return {
|
return {
|
||||||
...i,
|
...i,
|
||||||
joblineid: i.joblineid ? i.joblineid : "noline",
|
joblineid: !!i.joblineid ? i.joblineid : "noline",
|
||||||
applicable_taxes: {
|
applicable_taxes: {
|
||||||
federal: i.applicable_taxes?.federal || false,
|
federal: (i.applicable_taxes && i.applicable_taxes.federal) || false,
|
||||||
state: i.applicable_taxes?.state || false,
|
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
|
||||||
local: i.applicable_taxes?.local || false
|
local: (i.applicable_taxes && i.applicable_taxes.local) || false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { Button, Checkbox, Form, Modal } from "antd";
|
import { Button, Checkbox, Form, Modal } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) =>
|
setPartsOrderContext: (context) =>
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -17,12 +20,20 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
context: context,
|
context: context,
|
||||||
modal: "partsOrder"
|
modal: "partsOrder"
|
||||||
})
|
})
|
||||||
|
),
|
||||||
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
|
dispatch(
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid,
|
||||||
|
operation,
|
||||||
|
type
|
||||||
|
})
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditReturn);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditReturn);
|
||||||
|
|
||||||
export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, bodyshop, data, disabled }) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -75,9 +86,9 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
|||||||
title={t("bills.actions.return")}
|
title={t("bills.actions.return")}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
>
|
>
|
||||||
<Form initialValues={data?.bills_by_pk} onFinish={handleFinish} form={form}>
|
<Form initialValues={data && data.bills_by_pk} onFinish={handleFinish} form={form}>
|
||||||
<Form.List name={["billlines"]}>
|
<Form.List name={["billlines"]}>
|
||||||
{(fields) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<table style={{ tableLayout: "auto", width: "100%" }}>
|
<table style={{ tableLayout: "auto", width: "100%" }}>
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Drawer, Grid } from "antd";
|
import { Drawer, Grid } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
import React from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import BillDetailEditComponent from "./bill-detail-edit-component";
|
import BillDetailEditComponent from "./bill-detail-edit-component";
|
||||||
|
|
||||||
|
|||||||
@@ -85,8 +85,6 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const { upload, location, outstanding_returns, inventory, federal_tax_exempt, ...remainingValues } = values;
|
const { upload, location, outstanding_returns, inventory, federal_tax_exempt, ...remainingValues } = values;
|
||||||
|
|
||||||
let adjustmentsToInsert = {};
|
let adjustmentsToInsert = {};
|
||||||
@@ -104,13 +102,9 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
const {
|
const {
|
||||||
deductedfromlbr,
|
deductedfromlbr,
|
||||||
lbr_adjustment,
|
lbr_adjustment,
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
location: lineLocation,
|
location: lineLocation,
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
part_type,
|
part_type,
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
create_ppc,
|
create_ppc,
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
original_actual_price,
|
original_actual_price,
|
||||||
...restI
|
...restI
|
||||||
} = i;
|
} = i;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Form, Input, Table } from "antd";
|
import { Form, Input, Table } from "antd";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import BillFormItemsExtendedFormItem from "./bill-form-lines.extended.formitem.component";
|
import BillFormItemsExtendedFormItem from "./bill-form-lines.extended.formitem.component";
|
||||||
|
|
||||||
export default function BillFormLinesExtended({ lineData, discount, form, responsibilityCenters }) {
|
export default function BillFormLinesExtended({ lineData, discount, form, responsibilityCenters, disabled }) {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const columns = [
|
const columns = [
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import { MinusCircleFilled, PlusCircleFilled, WarningOutlined } from "@ant-design/icons";
|
import { MinusCircleFilled, PlusCircleFilled, WarningOutlined } from "@ant-design/icons";
|
||||||
import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -11,7 +12,7 @@ import CiecaSelect from "../../utils/Ciecaselect";
|
|||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillFormItemsExtendedFormItem);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillFormItemsExtendedFormItem);
|
||||||
@@ -21,6 +22,7 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
bodyshop,
|
bodyshop,
|
||||||
form,
|
form,
|
||||||
record,
|
record,
|
||||||
|
index,
|
||||||
disabled,
|
disabled,
|
||||||
responsibilityCenters,
|
responsibilityCenters,
|
||||||
discount
|
discount
|
||||||
@@ -76,7 +78,7 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
...billlineskeys,
|
...billlineskeys,
|
||||||
[record.id]: {
|
[record.id]: {
|
||||||
...billlineskeys[billlineskeys],
|
...billlineskeys[billlineskeys],
|
||||||
actual_cost: billlineskeys[billlineskeys].actual_cost
|
actual_cost: !!billlineskeys[billlineskeys].actual_cost
|
||||||
? billlineskeys[billlineskeys].actual_cost
|
? billlineskeys[billlineskeys].actual_cost
|
||||||
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
||||||
}
|
}
|
||||||
@@ -91,7 +93,7 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
<Form.Item shouldUpdate>
|
<Form.Item shouldUpdate>
|
||||||
{() => {
|
{() => {
|
||||||
const line = value;
|
const line = value;
|
||||||
if (!line) return null;
|
if (!!!line) return null;
|
||||||
const lineDiscount = (1 - Math.round((line.actual_cost / line.actual_price) * 100) / 100).toPrecision(2);
|
const lineDiscount = (1 - Math.round((line.actual_cost / line.actual_price) * 100) / 100).toPrecision(2);
|
||||||
|
|
||||||
if (lineDiscount - discount === 0) return <div />;
|
if (lineDiscount - discount === 0) return <div />;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Icon, { UploadOutlined } from "@ant-design/icons";
|
|||||||
import { useApolloClient } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload } from "antd";
|
import { Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload } from "antd";
|
||||||
import { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { MdOpenInNew } from "react-icons/md";
|
import { MdOpenInNew } from "react-icons/md";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -26,7 +26,7 @@ import DateTimePicker from "../form-date-time-picker/form-date-time-picker.compo
|
|||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({});
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
export function BillFormComponent({
|
export function BillFormComponent({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
@@ -254,7 +254,7 @@ export function BillFormComponent({
|
|||||||
required: true
|
required: true
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
() => ({
|
({ getFieldValue }) => ({
|
||||||
validator(rule, value) {
|
validator(rule, value) {
|
||||||
if (ClosingPeriod.treatment === "on" && bodyshop.accountingconfig.ClosingPeriod) {
|
if (ClosingPeriod.treatment === "on" && bodyshop.accountingconfig.ClosingPeriod) {
|
||||||
if (
|
if (
|
||||||
@@ -374,10 +374,8 @@ export function BillFormComponent({
|
|||||||
let totals;
|
let totals;
|
||||||
if (!!values.total && !!values.billlines && values.billlines.length > 0)
|
if (!!values.total && !!values.billlines && values.billlines.length > 0)
|
||||||
totals = CalculateBillTotal(values);
|
totals = CalculateBillTotal(values);
|
||||||
if (totals)
|
if (!!totals)
|
||||||
return (
|
return (
|
||||||
// TODO: Align is not correct
|
|
||||||
// eslint-disable-next-line react/no-unknown-property
|
|
||||||
<div align="right">
|
<div align="right">
|
||||||
<Space size="large" wrap>
|
<Space size="large" wrap>
|
||||||
<Statistic title={t("bills.labels.subtotal")} value={totals.subtotal.toFormat()} precision={2} />
|
<Statistic title={t("bills.labels.subtotal")} value={totals.subtotal.toFormat()} precision={2} />
|
||||||
@@ -460,7 +458,7 @@ export function BillFormComponent({
|
|||||||
if (Array.isArray(e)) {
|
if (Array.isArray(e)) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
return e?.fileList;
|
return e && e.fileList;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Upload.Dragger multiple={true} name="logo" beforeUpload={() => false} listType="picture">
|
<Upload.Dragger multiple={true} name="logo" beforeUpload={() => false} listType="picture">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip } from "antd";
|
import { Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -15,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -26,7 +27,8 @@ export function BillEnterModalLinesComponent({
|
|||||||
discount,
|
discount,
|
||||||
form,
|
form,
|
||||||
responsibilityCenters,
|
responsibilityCenters,
|
||||||
billEdit
|
billEdit,
|
||||||
|
billid
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||||
@@ -124,7 +126,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: () => <Input.TextArea disabled={disabled} autoSize />
|
formInput: (record, index) => <Input.TextArea disabled={disabled} autoSize />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.quantity"),
|
title: t("billlines.fields.quantity"),
|
||||||
@@ -156,7 +158,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: () => <InputNumber precision={0} min={1} disabled={disabled} />
|
formInput: (record, index) => <InputNumber precision={0} min={1} disabled={disabled} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.actual_price"),
|
title: t("billlines.fields.actual_price"),
|
||||||
@@ -186,7 +188,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
if (idx === index) {
|
if (idx === index) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
actual_cost: item.actual_cost
|
actual_cost: !!item.actual_cost
|
||||||
? item.actual_cost
|
? item.actual_cost
|
||||||
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
||||||
};
|
};
|
||||||
@@ -256,7 +258,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
<Form.Item shouldUpdate noStyle>
|
<Form.Item shouldUpdate noStyle>
|
||||||
{() => {
|
{() => {
|
||||||
const line = getFieldsValue(["billlines"]).billlines[index];
|
const line = getFieldsValue(["billlines"]).billlines[index];
|
||||||
if (!line) return null;
|
if (!!!line) return null;
|
||||||
let lineDiscount = 1 - line.actual_cost / line.actual_price;
|
let lineDiscount = 1 - line.actual_cost / line.actual_price;
|
||||||
if (isNaN(lineDiscount)) lineDiscount = 0;
|
if (isNaN(lineDiscount)) lineDiscount = 0;
|
||||||
return (
|
return (
|
||||||
@@ -320,7 +322,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: () => (
|
formInput: (record, index) => (
|
||||||
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
||||||
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||||
? CiecaSelect(true, false)
|
? CiecaSelect(true, false)
|
||||||
@@ -342,7 +344,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "location"]
|
name: [field.name, "location"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: () => (
|
formInput: (record, index) => (
|
||||||
<Select disabled={disabled}>
|
<Select disabled={disabled}>
|
||||||
{bodyshop.md_parts_locations.map((loc, idx) => (
|
{bodyshop.md_parts_locations.map((loc, idx) => (
|
||||||
<Select.Option key={idx} value={loc}>
|
<Select.Option key={idx} value={loc}>
|
||||||
@@ -364,7 +366,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "deductedfromlbr"]
|
name: [field.name, "deductedfromlbr"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: () => <Switch disabled={disabled} />,
|
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||||
additional: (record, index) => (
|
additional: (record, index) => (
|
||||||
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
||||||
{() => {
|
{() => {
|
||||||
@@ -476,7 +478,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "applicable_taxes", "federal"]
|
name: [field.name, "applicable_taxes", "federal"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: () => <Switch disabled={disabled} />
|
formInput: (record, index) => <Switch disabled={disabled} />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
@@ -493,7 +495,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "applicable_taxes", "state"]
|
name: [field.name, "applicable_taxes", "state"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: () => <Switch disabled={disabled} />
|
formInput: (record, index) => <Switch disabled={disabled} />
|
||||||
},
|
},
|
||||||
|
|
||||||
...InstanceRenderManager({
|
...InstanceRenderManager({
|
||||||
@@ -511,7 +513,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "applicable_taxes", "local"]
|
name: [field.name, "applicable_taxes", "local"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: () => <Switch disabled={disabled} />
|
formInput: (record, index) => <Switch disabled={disabled} />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
@@ -573,7 +575,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Table
|
<Table
|
||||||
@@ -610,7 +612,19 @@ export function BillEnterModalLinesComponent({
|
|||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillEnterModalLinesComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillEnterModalLinesComponent);
|
||||||
|
|
||||||
const EditableCell = ({ dataIndex, record, children, formInput, formItemProps, additional, wrapper, ...restProps }) => {
|
const EditableCell = ({
|
||||||
|
dataIndex,
|
||||||
|
title,
|
||||||
|
inputType,
|
||||||
|
record,
|
||||||
|
index,
|
||||||
|
children,
|
||||||
|
formInput,
|
||||||
|
formItemProps,
|
||||||
|
additional,
|
||||||
|
wrapper,
|
||||||
|
...restProps
|
||||||
|
}) => {
|
||||||
const propsFinal = formItemProps && formItemProps(record);
|
const propsFinal = formItemProps && formItemProps(record);
|
||||||
if (propsFinal && "key" in propsFinal) {
|
if (propsFinal && "key" in propsFinal) {
|
||||||
delete propsFinal.key;
|
delete propsFinal.key;
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ export const CalculateBillTotal = (invoice) => {
|
|||||||
let stateTax = Dinero({ amount: 0 });
|
let stateTax = Dinero({ amount: 0 });
|
||||||
let localTax = Dinero({ amount: 0 });
|
let localTax = Dinero({ amount: 0 });
|
||||||
|
|
||||||
if (!billlines) return null;
|
if (!!!billlines) return null;
|
||||||
|
|
||||||
billlines.forEach((i) => {
|
billlines.forEach((i) => {
|
||||||
if (i) {
|
if (!!i) {
|
||||||
const itemTotal = Dinero({
|
const itemTotal = Dinero({
|
||||||
amount: Math.round((i.actual_cost || 0) * 100)
|
amount: Math.round((i.actual_cost || 0) * 100)
|
||||||
}).multiply(i.quantity || 1);
|
}).multiply(i.quantity || 1);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
||||||
import { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||||
import "./bill-inventory-table.styles.scss";
|
import "./bill-inventory-table.styles.scss";
|
||||||
@@ -13,7 +13,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
billEnterModal: selectBillEnterModal
|
billEnterModal: selectBillEnterModal
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillInventoryTable);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillInventoryTable);
|
||||||
@@ -22,7 +22,7 @@ export function BillInventoryTable({ billEnterModal, bodyshop, form, billEdit, i
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inventoryData?.inventory) {
|
if (inventoryData && inventoryData.inventory) {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
inventory: billEnterModal.context.consumeinventoryid
|
inventory: billEnterModal.context.consumeinventoryid
|
||||||
? inventoryData.inventory.map((i) => {
|
? inventoryData.inventory.map((i) => {
|
||||||
@@ -47,7 +47,7 @@ export function BillInventoryTable({ billEnterModal, bodyshop, form, billEdit, i
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.List name="inventory">
|
<Form.List name="inventory">
|
||||||
{(fields) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography.Title level={4}>{t("inventory.labels.inventory")}</Typography.Title>
|
<Typography.Title level={4}>{t("inventory.labels.inventory")}</Typography.Title>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
td {
|
td {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid var(--table-border-color);
|
border-bottom: 1px solid #ddd;
|
||||||
|
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 0px !important;
|
margin-bottom: 0px !important;
|
||||||
@@ -14,6 +14,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr:hover {
|
tr:hover {
|
||||||
background-color: var(--table-hover-bg);
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { gql, useMutation } from "@apollo/client";
|
||||||
import { Button } from "antd";
|
import { Button } from "antd";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -15,7 +15,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
authLevel: selectAuthLevel,
|
authLevel: selectAuthLevel,
|
||||||
currentUser: selectCurrentUser
|
currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Space } from "antd";
|
import { Button, Space } from "antd";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
@@ -26,7 +26,7 @@ export default function BillPrintButton({ billid }) {
|
|||||||
null,
|
null,
|
||||||
notification
|
notification
|
||||||
);
|
);
|
||||||
} catch {
|
} catch (e) {
|
||||||
console.warn("Warning: Error generating a document.");
|
console.warn("Warning: Error generating a document.");
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { gql, useMutation } from "@apollo/client";
|
||||||
import { Button } from "antd";
|
import { Button } from "antd";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -13,7 +13,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
authLevel: selectAuthLevel
|
authLevel: selectAuthLevel
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useMutation } from "@apollo/client";
|
|||||||
import { Button, Tooltip } from "antd";
|
import { Button, Tooltip } from "antd";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import dayjs from "./../../utils/day";
|
import dayjs from "./../../utils/day";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries";
|
import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries";
|
||||||
@@ -17,7 +17,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser
|
currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BilllineAddInventory);
|
export default connect(mapStateToProps, mapDispatchToProps)(BilllineAddInventory);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { FaTasks } from "react-icons/fa";
|
import { FaTasks } from "react-icons/fa";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
@@ -76,7 +75,6 @@ export function BillsListTableComponent({
|
|||||||
<Button
|
<Button
|
||||||
title={t("tasks.buttons.create")}
|
title={t("tasks.buttons.create")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
logImEXEvent("bills_create_task", {});
|
|
||||||
setTaskUpsertContext({
|
setTaskUpsertContext({
|
||||||
context: {
|
context: {
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
@@ -111,13 +109,6 @@ export function BillsListTableComponent({
|
|||||||
key: "vendorname",
|
key: "vendorname",
|
||||||
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
|
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
|
||||||
sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
|
||||||
filters: bills
|
|
||||||
? [...new Set(bills.map((bill) => bill.vendor.name))].map((name) => ({
|
|
||||||
text: name,
|
|
||||||
value: name
|
|
||||||
}))
|
|
||||||
: [],
|
|
||||||
onFilter: (value, record) => record.vendor.name === value,
|
|
||||||
render: (text, record) => <span>{record.vendor.name}</span>
|
render: (text, record) => <span>{record.vendor.name}</span>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -169,7 +160,6 @@ export function BillsListTableComponent({
|
|||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
logImEXEvent("bills_list_sort_filter", { pagination, filters, sorter });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredBills = bills
|
const filteredBills = bills
|
||||||
@@ -211,7 +201,6 @@ export function BillsListTableComponent({
|
|||||||
<Button
|
<Button
|
||||||
disabled={!hasBillsAccess}
|
disabled={!hasBillsAccess}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
logImEXEvent("bills_reconcile", {});
|
|
||||||
setReconciliationContext({
|
setReconciliationContext({
|
||||||
actions: { refetch: billsQuery.refetch },
|
actions: { refetch: billsQuery.refetch },
|
||||||
context: {
|
context: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
|
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
@@ -100,9 +100,9 @@ export default function BillsVendorsList() {
|
|||||||
selectedRowKeys: [search.vendorid],
|
selectedRowKeys: [search.vendorid],
|
||||||
type: "radio"
|
type: "radio"
|
||||||
}}
|
}}
|
||||||
onRow={(record) => {
|
onRow={(record, rowIndex) => {
|
||||||
return {
|
return {
|
||||||
onClick: () => {
|
onClick: (event) => {
|
||||||
handleOnRowClick(record);
|
handleOnRowClick(record);
|
||||||
} // click row
|
} // click row
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HomeFilled } from "@ant-design/icons";
|
import { HomeFilled } from "@ant-design/icons";
|
||||||
import { Breadcrumb, Col, Row } from "antd";
|
import { Breadcrumb, Col, Row } from "antd";
|
||||||
import { selectBreadcrumbs, selectIsPartsEntry } from "../../redux/application/application.selectors";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import GlobalSearch from "../global-search/global-search.component";
|
import GlobalSearch from "../global-search/global-search.component";
|
||||||
import GlobalSearchOs from "../global-search/global-search-os.component";
|
import GlobalSearchOs from "../global-search/global-search-os.component";
|
||||||
@@ -12,19 +13,18 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
breadcrumbs: selectBreadcrumbs,
|
breadcrumbs: selectBreadcrumbs,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop
|
||||||
isPartsEntry: selectIsPartsEntry
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export function BreadCrumbs({ breadcrumbs, bodyshop, isPartsEntry }) {
|
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||||
const {
|
const {
|
||||||
treatments: { OpenSearch }
|
treatments: { OpenSearch }
|
||||||
} = useSplitTreatments({
|
} = useSplitTreatments({
|
||||||
attributes: {},
|
attributes: {},
|
||||||
names: ["OpenSearch"],
|
names: ["OpenSearch"],
|
||||||
splitKey: bodyshop?.imexshopid
|
splitKey: bodyshop && bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
// TODO - Client Update - Technically key is not doing anything here
|
||||||
return (
|
return (
|
||||||
<Row className="breadcrumb-container">
|
<Row className="breadcrumb-container">
|
||||||
<Col xs={24} sm={24} md={16}>
|
<Col xs={24} sm={24} md={16}>
|
||||||
@@ -34,8 +34,8 @@ export function BreadCrumbs({ breadcrumbs, bodyshop, isPartsEntry }) {
|
|||||||
{
|
{
|
||||||
key: "home",
|
key: "home",
|
||||||
title: (
|
title: (
|
||||||
<Link to={isPartsEntry ? `/parts/` : `/manage/`}>
|
<Link to={`/manage/`}>
|
||||||
<HomeFilled /> {(bodyshop?.shopname && `(${bodyshop.shopname})`) || ""}
|
<HomeFilled /> {(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) || ""}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Form, Modal } from "antd";
|
import { Button, Form, Modal } from "antd";
|
||||||
import { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -32,13 +32,12 @@ export function ContractsFindModalContainer({ caBcEtfTableModal, toggleModalVisi
|
|||||||
logImEXEvent("ca_bc_etf_table_parse");
|
logImEXEvent("ca_bc_etf_table_parse");
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const claimNumbers = [];
|
const claimNumbers = [];
|
||||||
values.table.split("\n").forEach((row) => {
|
values.table.split("\n").forEach((row, idx, arr) => {
|
||||||
const { 1: claim, 2: shortclaim, 4: amount } = row.split("\t");
|
const { 1: claim, 2: shortclaim, 4: amount } = row.split("\t");
|
||||||
if (!claim || !shortclaim) return;
|
if (!claim || !shortclaim) return;
|
||||||
const trimmedShortClaim = shortclaim.trim();
|
const trimmedShortClaim = shortclaim.trim();
|
||||||
// const trimmedClaim = claim.trim();
|
// const trimmedClaim = claim.trim();
|
||||||
if (amount.slice(-1) === "-") {
|
if (amount.slice(-1) === "-") {
|
||||||
// NO OP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
claimNumbers.push({
|
claimNumbers.push({
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import { Form, Input, Radio } from "antd";
|
import { Form, Input, Radio } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(PartsReceiveModalComponent);
|
export default connect(mapStateToProps, null)(PartsReceiveModalComponent);
|
||||||
|
|
||||||
export function PartsReceiveModalComponent() {
|
export function PartsReceiveModalComponent({ bodyshop, form }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CalculatorFilled } from "@ant-design/icons";
|
import { CalculatorFilled } from "@ant-design/icons";
|
||||||
import { Button, Form, InputNumber, Popover, Space } from "antd";
|
import { Button, Form, InputNumber, Popover, Space } from "antd";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CopyFilled, DeleteFilled } from "@ant-design/icons";
|
|||||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||||
import { Button, Card, Col, Form, Input, message, Row, Space, Spin, Statistic } from "antd";
|
import { Button, Card, Col, Form, Input, message, Row, Space, Spin, Statistic } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -14,7 +14,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||||
import { getCurrentUser, logImEXEvent } from "../../firebase/firebase.utils";
|
import { getCurrentUser } from "../../firebase/firebase.utils";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -124,7 +124,6 @@ const CardPaymentModalComponent = ({
|
|||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logImEXEvent("payment_cc_lightbox");
|
|
||||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||||
bodyshop,
|
bodyshop,
|
||||||
refresh: !!window.intellipay,
|
refresh: !!window.intellipay,
|
||||||
@@ -134,6 +133,7 @@ const CardPaymentModalComponent = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (window.intellipay) {
|
if (window.intellipay) {
|
||||||
|
// eslint-disable-next-line no-eval
|
||||||
eval(response.data);
|
eval(response.data);
|
||||||
pollForIntelliPay(() => {
|
pollForIntelliPay(() => {
|
||||||
SetIntellipayCallbackFunctions();
|
SetIntellipayCallbackFunctions();
|
||||||
@@ -149,7 +149,7 @@ const CardPaymentModalComponent = ({
|
|||||||
window.intellipay.initialize();
|
window.intellipay.initialize();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
notification.open({
|
notification.open({
|
||||||
type: "error",
|
type: "error",
|
||||||
message: t("job_payments.notifications.error.openingip")
|
message: t("job_payments.notifications.error.openingip")
|
||||||
@@ -172,7 +172,6 @@ const CardPaymentModalComponent = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
logImEXEvent("payment_cc_shortlink");
|
|
||||||
const response = await axios.post("/intellipay/generate_payment_url", {
|
const response = await axios.post("/intellipay/generate_payment_url", {
|
||||||
bodyshop,
|
bodyshop,
|
||||||
amount: payments.reduce((acc, val) => acc + (val?.amount || 0), 0),
|
amount: payments.reduce((acc, val) => acc + (val?.amount || 0), 0),
|
||||||
@@ -188,7 +187,7 @@ const CardPaymentModalComponent = ({
|
|||||||
message.success(t("general.actions.copied"));
|
message.success(t("general.actions.copied"));
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch {
|
} catch (error) {
|
||||||
notification.open({
|
notification.open({
|
||||||
type: "error",
|
type: "error",
|
||||||
message: t("job_payments.notifications.error.openingip")
|
message: t("job_payments.notifications.error.openingip")
|
||||||
@@ -360,7 +359,7 @@ function pollForIntelliPay(callbackFunction) {
|
|||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
function checkFixAmount() {
|
function checkFixAmount() {
|
||||||
if (window.intellipay?.fixAmount) {
|
if (window.intellipay && window.intellipay.fixAmount !== undefined) {
|
||||||
callbackFunction();
|
callbackFunction();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import { Button, Modal } from "antd";
|
import { Button, Modal } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CardPaymentModalComponent from "./card-payment-modal.component";
|
import CardPaymentModalComponent from "./card-payment-modal.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
cardPaymentModal: selectCardPayment
|
cardPaymentModal: selectCardPayment,
|
||||||
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
||||||
});
|
});
|
||||||
|
|
||||||
function CardPaymentModalContainer({ cardPaymentModal, toggleModalVisible }) {
|
function CardPaymentModalContainer({ cardPaymentModal, toggleModalVisible, bodyshop }) {
|
||||||
const { open } = cardPaymentModal;
|
const { open } = cardPaymentModal;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import "./chat-affix.styles.scss";
|
|||||||
import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers";
|
import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
export function ChatAffixContainer({ bodyshop, chatVisible, currentUser }) {
|
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!bodyshop?.messagingservicesid) return;
|
if (!bodyshop || !bodyshop.messagingservicesid) return;
|
||||||
|
|
||||||
async function SubscribeToTopicForFCMNotification() {
|
async function SubscribeToTopicForFCMNotification() {
|
||||||
try {
|
try {
|
||||||
@@ -35,8 +35,8 @@ export function ChatAffixContainer({ bodyshop, chatVisible, currentUser }) {
|
|||||||
SubscribeToTopicForFCMNotification();
|
SubscribeToTopicForFCMNotification();
|
||||||
|
|
||||||
// Register WebSocket handlers
|
// Register WebSocket handlers
|
||||||
if (socket?.connected) {
|
if (socket && socket.connected) {
|
||||||
registerMessagingHandlers({ socket, client, currentUser, bodyshop, t });
|
registerMessagingHandlers({ socket, client });
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unregisterMessagingHandlers({ socket });
|
unregisterMessagingHandlers({ socket });
|
||||||
@@ -44,11 +44,11 @@ export function ChatAffixContainer({ bodyshop, chatVisible, currentUser }) {
|
|||||||
}
|
}
|
||||||
}, [bodyshop, socket, t, client]);
|
}, [bodyshop, socket, t, client]);
|
||||||
|
|
||||||
if (!bodyshop?.messagingservicesid) return <></>;
|
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
||||||
{bodyshop?.messagingservicesid ? <ChatPopupComponent /> : null}
|
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { gql } from "@apollo/client";
|
|
||||||
|
|
||||||
import { playNewMessageSound } from "../../utils/soundManager.js";
|
|
||||||
import { isLeaderTab } from "../../utils/singleTabAudioLeader";
|
|
||||||
|
|
||||||
import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
||||||
import { QUERY_ACTIVE_ASSOCIATION_SOUND } from "../../graphql/user.queries";
|
import { gql } from "@apollo/client";
|
||||||
|
|
||||||
const logLocal = (message, ...args) => {
|
const logLocal = (message, ...args) => {
|
||||||
if (import.meta.env.VITE_APP_IS_TEST || !import.meta.env.PROD) {
|
if (import.meta.env.VITE_APP_IS_TEST || !import.meta.env.PROD) {
|
||||||
@@ -31,48 +26,16 @@ const enrichConversation = (conversation, isOutbound) => ({
|
|||||||
__typename: "conversations"
|
__typename: "conversations"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Can be uncommonted to test the playback of the notification sound
|
export const registerMessagingHandlers = ({ socket, client }) => {
|
||||||
// window.testTone = () => {
|
|
||||||
// const notificationSound = new Audio(newMessageSound);
|
|
||||||
// notificationSound.play().catch((error) => {
|
|
||||||
// console.error("Error playing notification sound:", error);
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const registerMessagingHandlers = ({ socket, client, currentUser, bodyshop }) => {
|
|
||||||
if (!(socket && client)) return;
|
if (!(socket && client)) return;
|
||||||
|
|
||||||
const handleNewMessageSummary = async (message) => {
|
const handleNewMessageSummary = async (message) => {
|
||||||
const { conversationId, newConversation, existingConversation, isoutbound } = message;
|
const { conversationId, newConversation, existingConversation, isoutbound } = message;
|
||||||
|
|
||||||
// True only when DB value is strictly true; falls back to true on cache miss
|
|
||||||
const isNewMessageSoundEnabled = (client) => {
|
|
||||||
try {
|
|
||||||
const email = currentUser?.email;
|
|
||||||
if (!email) return true; // default allow if we can't resolve user
|
|
||||||
const res = client.readQuery({
|
|
||||||
query: QUERY_ACTIVE_ASSOCIATION_SOUND,
|
|
||||||
variables: { email }
|
|
||||||
});
|
|
||||||
const flag = res?.associations?.[0]?.new_message_sound;
|
|
||||||
return flag === true; // strictly true => enabled
|
|
||||||
} catch {
|
|
||||||
// If the query hasn't been seeded in cache yet, default ON
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation });
|
logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation });
|
||||||
|
|
||||||
const queryVariables = { offset: 0 };
|
const queryVariables = { offset: 0 };
|
||||||
|
|
||||||
if (!isoutbound) {
|
|
||||||
// Play notification sound for new inbound message (scoped to bodyshop)
|
|
||||||
if (isLeaderTab(bodyshop.id) && isNewMessageSoundEnabled(client)) {
|
|
||||||
playNewMessageSound(bodyshop.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existingConversation && conversationId) {
|
if (!existingConversation && conversationId) {
|
||||||
// Attempt to read from the cache to determine if this is actually a new conversation
|
// Attempt to read from the cache to determine if this is actually a new conversation
|
||||||
try {
|
try {
|
||||||
@@ -94,7 +57,7 @@ export const registerMessagingHandlers = ({ socket, client, currentUser, bodysho
|
|||||||
existingConversation: true
|
existingConversation: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
logLocal("handleNewMessageSummary - Cache miss", { conversationId });
|
logLocal("handleNewMessageSummary - Cache miss", { conversationId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,6 +291,8 @@ export const registerMessagingHandlers = ({ socket, client, currentUser, bodysho
|
|||||||
|
|
||||||
case "conversation-unarchived":
|
case "conversation-unarchived":
|
||||||
case "conversation-archived":
|
case "conversation-archived":
|
||||||
|
// Would like to someday figure out how to get this working without refetch queries,
|
||||||
|
// But I have but a solid 4 hours into it, and there are just too many weird occurrences
|
||||||
try {
|
try {
|
||||||
const listQueryVariables = { offset: 0 };
|
const listQueryVariables = { offset: 0 };
|
||||||
const detailsQueryVariables = { conversationId };
|
const detailsQueryVariables = { conversationId };
|
||||||
@@ -363,7 +328,7 @@ export const registerMessagingHandlers = ({ socket, client, currentUser, bodysho
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "tag-added": {
|
case "tag-added":
|
||||||
// Ensure `job_conversations` is properly formatted
|
// Ensure `job_conversations` is properly formatted
|
||||||
const formattedJobConversations = job_conversations.map((jc) => ({
|
const formattedJobConversations = job_conversations.map((jc) => ({
|
||||||
__typename: "job_conversations",
|
__typename: "job_conversations",
|
||||||
@@ -410,7 +375,6 @@ export const registerMessagingHandlers = ({ socket, client, currentUser, bodysho
|
|||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case "tag-removed":
|
case "tag-removed":
|
||||||
try {
|
try {
|
||||||
@@ -498,7 +462,7 @@ export const registerMessagingHandlers = ({ socket, client, currentUser, bodysho
|
|||||||
logLocal("handlePhoneNumberOptedOut - Error", { error: error.message });
|
logLocal("handlePhoneNumberOptedOut - Error", { error: error.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// New handler for phone number opt-in
|
// New handler for phone number opt-in
|
||||||
const handlePhoneNumberOptedIn = async (data) => {
|
const handlePhoneNumberOptedIn = async (data) => {
|
||||||
const { bodyshopid, phone_number } = data;
|
const { bodyshopid, phone_number } = data;
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) {
|
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [, forceUpdate] = useState(false);
|
const [, forceUpdate] = useState(false);
|
||||||
|
|
||||||
const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""));
|
const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""));
|
||||||
|
|
||||||
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
|
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
|
||||||
variables: {
|
variables: {
|
||||||
bodyshopid: bodyshop.id,
|
bodyshopid: bodyshop.id,
|
||||||
@@ -62,12 +64,15 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
|||||||
const item = sortedConversationList[index];
|
const item = sortedConversationList[index];
|
||||||
const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
|
const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
|
||||||
const hasOptOutEntry = optOutMap.has(normalizedPhone);
|
const hasOptOutEntry = optOutMap.has(normalizedPhone);
|
||||||
|
|
||||||
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
||||||
const cardContentLeft =
|
const cardContentLeft =
|
||||||
item.job_conversations.length > 0
|
item.job_conversations.length > 0
|
||||||
? item.job_conversations.map((j, idx) => <Tag key={idx}>{j.job.ro_number}</Tag>)
|
? item.job_conversations.map((j, idx) => <Tag key={idx}>{j.job.ro_number}</Tag>)
|
||||||
: null;
|
: null;
|
||||||
const names = <>{_.uniq(item.job_conversations.map((j) => OwnerNameDisplayFunction(j.job)))}</>;
|
|
||||||
|
const names = <>{_.uniq(item.job_conversations.map((j, idx) => OwnerNameDisplayFunction(j.job)))}</>;
|
||||||
|
|
||||||
const cardTitle = (
|
const cardTitle = (
|
||||||
<>
|
<>
|
||||||
{item.label && <Tag color="blue">{item.label}</Tag>}
|
{item.label && <Tag color="blue">{item.label}</Tag>}
|
||||||
@@ -80,6 +85,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const cardExtra = (
|
const cardExtra = (
|
||||||
<>
|
<>
|
||||||
<Badge count={item.messages_aggregate.aggregate.count} />
|
<Badge count={item.messages_aggregate.aggregate.count} />
|
||||||
@@ -92,10 +98,11 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const getCardStyle = () =>
|
const getCardStyle = () =>
|
||||||
item.id === selectedConversation
|
item.id === selectedConversation
|
||||||
? { backgroundColor: "var(--card-selected-bg)" }
|
? { backgroundColor: "rgba(128, 128, 128, 0.2)" }
|
||||||
: { backgroundColor: index % 2 === 0 ? "var(--card-stripe-even-bg)" : "var(--card-stripe-odd-bg)" };
|
: { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function ChatConversationTitleTags({ jobConversations, bodyshop }) {
|
|||||||
|
|
||||||
const handleRemoveTag = async (jobId) => {
|
const handleRemoveTag = async (jobId) => {
|
||||||
const convId = jobConversations[0].conversationid;
|
const convId = jobConversations[0].conversationid;
|
||||||
if (convId) {
|
if (!!convId) {
|
||||||
await removeJobConversation({
|
await removeJobConversation({
|
||||||
variables: {
|
variables: {
|
||||||
conversationId: convId,
|
conversationId: convId,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Space } from "antd";
|
import { Space } from "antd";
|
||||||
|
import React from "react";
|
||||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||||
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
|
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
|
||||||
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
|
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
|
||||||
@@ -15,10 +16,10 @@ const mapDispatchToProps = () => ({});
|
|||||||
export function ChatConversationTitle({ conversation }) {
|
export function ChatConversationTitle({ conversation }) {
|
||||||
return (
|
return (
|
||||||
<Space className="chat-title" wrap>
|
<Space className="chat-title" wrap>
|
||||||
<PhoneNumberFormatter>{conversation?.phone_num}</PhoneNumberFormatter>
|
<PhoneNumberFormatter>{conversation && conversation.phone_num}</PhoneNumberFormatter>
|
||||||
<ChatLabelComponent conversation={conversation} />
|
<ChatLabelComponent conversation={conversation} />
|
||||||
<ChatPrintButton conversation={conversation} />
|
<ChatPrintButton conversation={conversation} />
|
||||||
<ChatConversationTitleTags jobConversations={conversation?.job_conversations || []} />
|
<ChatConversationTitleTags jobConversations={(conversation && conversation.job_conversations) || []} />
|
||||||
<ChatTagRoContainer conversation={conversation || []} />
|
<ChatTagRoContainer conversation={conversation || []} />
|
||||||
<ChatArchiveButton conversation={conversation} />
|
<ChatArchiveButton conversation={conversation} />
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ChatConversationTitle from "../chat-conversation-title/chat-conversation-title.component";
|
import ChatConversationTitle from "../chat-conversation-title/chat-conversation-title.component";
|
||||||
import ChatMessageListComponent from "../chat-messages-list/chat-message-list.component";
|
import ChatMessageListComponent from "../chat-messages-list/chat-message-list.component";
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = () => ({});
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
export function ChatLabel({ conversation, bodyshop }) {
|
export function ChatLabel({ conversation, bodyshop }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = () => ({});
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector);
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
max-height: 480px;
|
max-height: 480px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
background-color: var(--popover-bg);
|
background-color: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
color: var(--error-text);
|
color: red;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -25,13 +25,14 @@
|
|||||||
|
|
||||||
.no-jobs-message {
|
.no-jobs-message {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--no-jobs-text);
|
color: #888;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style images within gallery components */
|
/* Style images within gallery components */
|
||||||
.media-selector-content img {
|
.media-selector-content img {
|
||||||
|
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
@@ -39,8 +40,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Grid layout for gallery components */
|
/* Grid layout for gallery components */
|
||||||
.media-selector-content .ant-image,
|
.media-selector-content .ant-image, /* Assuming gallery components use Ant Design's Image */
|
||||||
.media-selector-content .gallery-container {
|
.media-selector-content .gallery-container { /* Fallback for custom gallery classes */
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
import { renderMessage } from "./renderMessage";
|
import { renderMessage } from "./renderMessage";
|
||||||
import "./chat-message-list.styles.scss";
|
import "./chat-message-list.styles.scss";
|
||||||
@@ -76,7 +76,7 @@ export default function ChatMessageListComponent({ messages }) {
|
|||||||
<Virtuoso
|
<Virtuoso
|
||||||
ref={virtuosoRef}
|
ref={virtuosoRef}
|
||||||
data={messages}
|
data={messages}
|
||||||
overscan={messages.reduce((acc, message) => acc + (message.image_path?.length || 0), 0) ? messages.length : 0}
|
overscan={!!messages.reduce((acc, message) => acc + (message.image_path?.length || 0), 0) ? messages.length : 0}
|
||||||
itemContent={(index) => renderMessage(messages, index)}
|
itemContent={(index) => renderMessage(messages, index)}
|
||||||
followOutput={(isAtBottom) => handleScrollStateChange(isAtBottom)}
|
followOutput={(isAtBottom) => handleScrollStateChange(isAtBottom)}
|
||||||
initialTopMostItemIndex={messages.length - 1}
|
initialTopMostItemIndex={messages.length - 1}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
.chat-send-message-button {
|
.chat-send-message-button {
|
||||||
margin: 0.3rem;
|
margin: 0.3rem;
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-icon {
|
.message-icon {
|
||||||
@@ -51,7 +52,7 @@
|
|||||||
bottom: 0.1rem;
|
bottom: 0.1rem;
|
||||||
right: 0.3rem;
|
right: 0.3rem;
|
||||||
margin: 0 0.1rem;
|
margin: 0 0.1rem;
|
||||||
color: var(--message-icon-color);
|
color: whitesmoke;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@
|
|||||||
|
|
||||||
&:last-child:after {
|
&:last-child:after {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
background: var(--message-mine-tail-bg);
|
background: white;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,11 +92,11 @@
|
|||||||
|
|
||||||
.message {
|
.message {
|
||||||
margin-right: 20%;
|
margin-right: 20%;
|
||||||
background-color: var(--message-yours-bg);
|
background-color: #eee;
|
||||||
|
|
||||||
&:last-child:before {
|
&:last-child:before {
|
||||||
left: -7px;
|
left: -7px;
|
||||||
background: var(--message-yours-bg);
|
background: #eee;
|
||||||
border-bottom-right-radius: 15px;
|
border-bottom-right-radius: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,14 +112,14 @@
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
color: var(--message-mine-text);
|
color: white;
|
||||||
margin-left: 25%;
|
margin-left: 25%;
|
||||||
background: linear-gradient(to bottom, var(--message-mine-bg-start) 0%, var(--message-mine-bg-end) 100%);
|
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
|
||||||
padding-bottom: 0.6rem;
|
padding-bottom: 0.6rem;
|
||||||
|
|
||||||
&:last-child:before {
|
&:last-child:before {
|
||||||
right: -8px;
|
right: -8px;
|
||||||
background: linear-gradient(to bottom, var(--message-mine-bg-start) 0%, var(--message-mine-bg-end) 100%);
|
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
|
||||||
border-bottom-left-radius: 15px;
|
border-bottom-left-radius: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,31 +135,32 @@
|
|||||||
margin: 0.5rem 10%;
|
margin: 0.5rem 10%;
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
background-color: var(--system-message-bg);
|
background-color: #f5f5f5;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: var(--system-message-text);
|
color: #555;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.system-label {
|
.system-label {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--system-label-text);
|
color: #888;
|
||||||
margin-bottom: 0.2rem;
|
margin-bottom: 0.2rem;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.system-date {
|
.system-date {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--system-label-text);
|
color: #888;
|
||||||
margin-top: 0.2rem;
|
margin-top: 0.2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.virtuoso-container {
|
.virtuoso-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { PlusCircleOutlined } from "@ant-design/icons";
|
import { PlusCircleOutlined } from "@ant-design/icons";
|
||||||
import { Dropdown } from "antd";
|
import { Dropdown } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { setMessage } from "../../redux/messaging/messaging.actions";
|
import { setMessage } from "../../redux/messaging/messaging.actions";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
|
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
|
||||||
import { Space, Spin } from "antd";
|
import { Space, Spin } from "antd";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||||
@@ -31,7 +31,7 @@ export function ChatPrintButton({ conversation }) {
|
|||||||
type,
|
type,
|
||||||
conversation.id,
|
conversation.id,
|
||||||
notification
|
notification
|
||||||
).catch(() => {
|
).catch((e) => {
|
||||||
console.warn("Something went wrong generating a document.");
|
console.warn("Something went wrong generating a document.");
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons";
|
import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons";
|
||||||
import { Empty, Select, Space } from "antd";
|
import { Empty, Select, Space } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Checkbox, Form } from "antd";
|
import { Checkbox, Form } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||||
const { name, label, required } = formItem;
|
const { name, label, required } = formItem;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import FormTypes from "./config-form-types";
|
import FormTypes from "./config-form-types";
|
||||||
|
|
||||||
export default function ConfirmFormComponents({ componentList, readOnly }) {
|
export default function ConfirmFormComponents({ componentList, readOnly }) {
|
||||||
@@ -6,7 +7,7 @@ export default function ConfirmFormComponents({ componentList, readOnly }) {
|
|||||||
{componentList.map((f, idx) => {
|
{componentList.map((f, idx) => {
|
||||||
const Comp = FormTypes[f.type];
|
const Comp = FormTypes[f.type];
|
||||||
|
|
||||||
if (Comp) {
|
if (!!Comp) {
|
||||||
return <Comp key={idx} formItem={f} readOnly={readOnly} />;
|
return <Comp key={idx} formItem={f} readOnly={readOnly} />;
|
||||||
} else {
|
} else {
|
||||||
return <div key={idx}>Error</div>;
|
return <div key={idx}>Error</div>;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Form, Rate } from "antd";
|
import { Form, Rate } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||||
const { name, label, required } = formItem;
|
const { name, label, required } = formItem;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Form, Slider } from "antd";
|
import { Form, Slider } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||||
const { name, label, required, min, max } = formItem;
|
const { name, label, required, min, max } = formItem;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Form, Input } from "antd";
|
import { Form, Input } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||||
const { name, label, required } = formItem;
|
const { name, label, required } = formItem;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Form, Input } from "antd";
|
import { Form, Input } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||||
const { name, label, required, rows } = formItem;
|
const { name, label, required, rows } = formItem;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import { Button, Result } from "antd";
|
import { Button, Result } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Card, Input, Table } from "antd";
|
import { Card, Input, Table } from "antd";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
|
||||||
@@ -114,9 +114,9 @@ export default function ContractsCarsComponent({ loading, data, selectedCarId, h
|
|||||||
type: "radio",
|
type: "radio",
|
||||||
selectedRowKeys: [selectedCarId]
|
selectedRowKeys: [selectedCarId]
|
||||||
}}
|
}}
|
||||||
onRow={(record) => {
|
onRow={(record, rowIndex) => {
|
||||||
return {
|
return {
|
||||||
onClick: () => {
|
onClick: (event) => {
|
||||||
handleSelect(record);
|
handleSelect(record);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
|
import React from "react";
|
||||||
import { QUERY_AVAILABLE_CC } from "../../graphql/courtesy-car.queries";
|
import { QUERY_AVAILABLE_CC } from "../../graphql/courtesy-car.queries";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ContractCarsComponent from "./contract-cars.component";
|
import ContractCarsComponent from "./contract-cars.component";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMutation } from "@apollo/client";
|
|||||||
import { Button, Form, InputNumber, Popover, Radio, Select, Space } from "antd";
|
import { Button, Form, InputNumber, Popover, Radio, Select, Space } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@@ -16,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser
|
currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -270,7 +270,7 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
|
|||||||
// awaitRefetchQueries: true,
|
// awaitRefetchQueries: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.errors) {
|
if (!!result.errors) {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.inserting", {
|
message: t("jobs.errors.inserting", {
|
||||||
message: JSON.stringify(result.errors)
|
message: JSON.stringify(result.errors)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Card } from "antd";
|
import { Card } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import DataLabel from "../data-label/data-label.component";
|
import DataLabel from "../data-label/data-label.component";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useLazyQuery } from "@apollo/client";
|
import { useLazyQuery } from "@apollo/client";
|
||||||
import { Button } from "antd";
|
import { Button } from "antd";
|
||||||
import { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { GET_JOB_FOR_CC_CONTRACT } from "../../graphql/jobs.queries";
|
import { GET_JOB_FOR_CC_CONTRACT } from "../../graphql/jobs.queries";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { WarningFilled } from "@ant-design/icons";
|
import { WarningFilled } from "@ant-design/icons";
|
||||||
import { Form, Input, InputNumber, Space } from "antd";
|
import { Form, Input, InputNumber, Space } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Card } from "antd";
|
import { Card } from "antd";
|
||||||
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import DataLabel from "../data-label/data-label.component";
|
import DataLabel from "../data-label/data-label.component";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Card, Input, Table } from "antd";
|
import { Card, Input, Table } from "antd";
|
||||||
import { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
@@ -142,9 +142,9 @@ export default function ContractsJobsComponent({ loading, data, selectedJob, han
|
|||||||
type: "radio",
|
type: "radio",
|
||||||
selectedRowKeys: [selectedJob]
|
selectedRowKeys: [selectedJob]
|
||||||
}}
|
}}
|
||||||
onRow={(record) => {
|
onRow={(record, rowIndex) => {
|
||||||
return {
|
return {
|
||||||
onClick: () => {
|
onClick: (event) => {
|
||||||
handleSelect(record);
|
handleSelect(record);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user