Compare commits

...

14 Commits

Author SHA1 Message Date
Allan Carr
ac6856b136 IO-3373 Dashboard Component Infinite Recursion
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-09-17 13:59:20 -07:00
Patrick Fic
ddd3b3d056 Merged in feature/IO-3325-amplitude-reverse-proxy (pull request #2566)
IO-3325 Add reverse proxy URL for amplitude.
2025-09-15 21:37:33 +00:00
Patrick Fic
2660466db1 IO-3325 Add reverse proxy URL for amplitude. 2025-09-15 14:13:48 -07:00
Dave Richer
fe67efe47c Merged in release/2025-09-26 (pull request #2562)
Release/2025 09 26  into master-AIO IO-3365, IO-3366, IO-3369
2025-09-11 20:43:43 +00:00
Dave
69a35772e5 release/2025-09-26 - Remove sockets from Parts Management 2025-09-11 16:41:21 -04:00
Dave Richer
38932f4bf9 Merged in feature/IO-3369-Fix-Parts-Status-List-Component (pull request #2559)
feature/IO-3369-Fix-Parts-Status-List-Component - Add Fixes
2025-09-11 19:58:28 +00:00
Dave
3fcb36a28e feature/IO-3369-Fix-Parts-Status-List-Component - Add Fixes 2025-09-11 15:56:47 -04:00
Allan Carr
fe78f5c7ff Merged in feature/IO-3365-Bills-Filters-and-Sorters (pull request #2556)
IO-3365 Bills Filters and Sorters

Approved-by: Dave Richer
2025-09-11 18:49:55 +00:00
Allan Carr
683846c3b0 Merged in feature/IO-3366-Shop-General-Field-Validators (pull request #2557)
IO-3366 Shop General Field Validators

Approved-by: Dave Richer
2025-09-11 18:49:20 +00:00
Dave Richer
2cc0b247b6 Merged in master-AIO (pull request #2555)
Master AIO
2025-09-11 18:18:26 +00:00
Dave Richer
31579354d4 Merged in release/2025-09-12 (pull request #2554)
DO NOT MERGE - Release/2025-09-12 into master-AIO -IO-3255, IO-3310, IO-3352, IO-3355
2025-09-11 15:31:22 +00:00
Dave Richer
0e7531dc54 Merged in feature/IO-3255-simplified-part-management (pull request #2552)
feature/IO-3255-simplified-parts-management -Extra checks
2025-09-11 15:24:38 +00:00
Dave
268b57c38a feature/IO-3255-simplified-parts-management -Extra checks 2025-09-11 11:22:59 -04:00
Allan Carr
808eeb91e9 IO-3365 Bills Filters and Sorters
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-09-10 21:33:10 -07:00
17 changed files with 65 additions and 57 deletions

View File

@@ -15,4 +15,5 @@ VITE_APP_INSTANCE=IMEX
TEST_USERNAME="test@imex.dev"
TEST_PASSWORD="test123"
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -17,4 +17,5 @@ VITE_APP_INSTANCE=ROME
TEST_USERNAME="test@imex.dev"
TEST_PASSWORD="test123"
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -14,4 +14,5 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
VITE_APP_INSTANCE=IMEX
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -14,4 +14,5 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
VITE_APP_INSTANCE=ROME
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -14,4 +14,5 @@ VITE_APP_IS_TEST=true
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=IMEX
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -14,4 +14,5 @@ VITE_APP_IS_TEST=true
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=ROME
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com

View File

@@ -233,9 +233,7 @@ export function App({
path="/parts/*"
element={
<ErrorBoundary>
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
<PrivateRoute isAuthorized={currentUser.authorized} />
</SocketProvider>
<PrivateRoute isAuthorized={currentUser.authorized} />
</ErrorBoundary>
}
>

View File

@@ -109,6 +109,13 @@ export function BillsListTableComponent({
key: "vendorname",
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
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>
},
{

View File

@@ -80,7 +80,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
};
const handleAddComponent = (e) => {
logImEXEvent("dashboard_add_component", { name: e });
logImEXEvent("dashboard_add_component", { name: e.key });
setState({
...state,
items: [

View File

@@ -16,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
export function GlobalFooter({ isPartsEntry }) {
const { t } = useTranslation();
if (isPartsEntry) {
return (
<Footer>
@@ -35,7 +35,6 @@ export function GlobalFooter({ isPartsEntry }) {
rome: t("titles.romeonline")
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
</div>
<WssStatusDisplayComponent />
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
Disclaimer & Notices
</Link>

View File

@@ -10,9 +10,12 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export const DEFAULT_COL_LAYOUT = { xs: 24, sm: 24, md: 8, lg: 4, xl: 4, xxl: 4 };
export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount);
export function JobPartsQueueCount({ bodyshop, parts, style }) {
export function JobPartsQueueCount({ bodyshop, parts, defaultColLayout = DEFAULT_COL_LAYOUT }) {
const partsStatus = useMemo(() => {
if (!parts) return null;
return parts.reduce(
@@ -35,35 +38,34 @@ export function JobPartsQueueCount({ bodyshop, parts, style }) {
}, [bodyshop, parts]);
if (!parts) return null;
return (
<Row style={style}>
<Col span={4}>
<Row>
<Col {...defaultColLayout}>
<Tooltip title="Total">
<Tag>{partsStatus.total}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title="No Status">
<Tag color="gold">{partsStatus["null"]}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title={bodyshop.md_order_statuses.default_ordered}>
<Tag color="blue">{partsStatus[bodyshop.md_order_statuses.default_ordered]}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title={bodyshop.md_order_statuses.default_received}>
<Tag color="green">{partsStatus[bodyshop.md_order_statuses.default_received]}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title={bodyshop.md_order_statuses.default_returned}>
<Tag color="orange">{partsStatus[bodyshop.md_order_statuses.default_returned]}</Tag>
</Tooltip>
</Col>
<Col span={4}>
<Col {...defaultColLayout}>
<Tooltip title={bodyshop.md_order_statuses.default_bo}>
<Tag color="red">{partsStatus[bodyshop.md_order_statuses.default_bo]}</Tag>
</Tooltip>

View File

@@ -233,7 +233,7 @@ export function PartsQueueListComponent({ bodyshop }) {
title: t("jobs.fields.partsstatus"),
dataIndex: "partsstatus",
key: "partsstatus",
render: (text, record) => <JobPartsQueueCount style={{ minWidth: "10rem" }} parts={record.joblines_status} />
render: (text, record) => <JobPartsQueueCount parts={record.joblines_status} />
},
{
title: t("jobs.fields.comment"),

View File

@@ -147,7 +147,7 @@ export function SimplifiedPartsJobsListComponent({
title: t("jobs.fields.partsstatus"),
dataIndex: "partsstatus",
key: "partsstatus",
render: (text, record) => <JobPartsQueueCount style={{ minWidth: "10rem" }} parts={record.joblines_status} />
render: (text, record) => <JobPartsQueueCount parts={record.joblines_status} />
},
{
title: t("jobs.fields.comment"),

View File

@@ -27,7 +27,8 @@ registerSW({ immediate: true });
Dinero.globalRoundingMode = "HALF_EVEN";
amplitude.init("6228a598e57cd66875cfd41604f1f891", {
defaultTracking: true
defaultTracking: true,
serverUrl: import.meta.env.VITE_APP_AMP_URL
// {
// attribution: {
// excludeReferrers: true,

View File

@@ -1,5 +1,6 @@
import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import { useQuery } from "@apollo/client";
import axios from "axios";
import queryString from "query-string";
import { useEffect, useState } from "react";
@@ -16,6 +17,7 @@ import { TemplateList } from "../../utils/TemplateConstants";
import { pageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" }))
@@ -33,25 +35,22 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte
});
const Templates = TemplateList("bill");
const { t } = useTranslation();
const { data: vendorsData } = useQuery(QUERY_ALL_VENDORS);
const columns = [
{
title: t("bills.fields.vendorname"),
dataIndex: "vendorname",
key: "vendorname",
// sortObject: (direction) => {
// return {
// vendor: {
// name: direction
// ? direction === "descend"
// ? "desc"
// : "asc"
// : "desc",
// },
// };
// },
// sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
// sortOrder:
// state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
sortObject: (order) => ({
vendor: { name: order === "descend" ? "desc" : "asc" }
}),
filters: (vendorsData?.vendors || []).map((v) => ({ text: v.name, value: v.id })),
filteredValue: state.filteredInfo.vendorname || null,
onFilter: (value, record) => record.vendorid === value,
sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
render: (text, record) => <span>{record.vendor.name}</span>
},
{
@@ -65,20 +64,11 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
// sortObject: (direction) => {
// return {
// job: {
// ro_number: direction
// ? direction === "descend"
// ? "desc"
// : "asc"
// : "desc",
// },
// };
// },
// sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
// sortOrder:
// state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
sortObject: (order) => ({
job: { ro_number: order === "descend" ? "desc" : "asc" }
}),
sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => record.job && <Link to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number}</Link>
},
{
@@ -175,7 +165,8 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
// Persist filters (including vendorname) and sorting
setState({ ...state, filteredInfo: { ...state.filteredInfo, ...filters }, sortedInfo: sorter });
search.page = pagination.current;
if (sorter && sorter.column && sorter.column.sortObject) {
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));

View File

@@ -88,7 +88,7 @@ const partsManagementDeprovisioning = async (req, res) => {
const { logger } = req;
const { shopId } = req.body;
if (process.env.NODE_ENV === "production") {
if (process.env.NODE_ENV === "production" || process.env.HOSTNAME?.endsWith("compute.internal")) {
return res.status(403).json({ error: "Deprovisioning not allowed in production environment." });
}

View File

@@ -1,5 +1,6 @@
const express = require("express");
const router = express.Router();
const logger = require("../../server/utils/logger");
// Pull secrets from env
const { VSSTA_INTEGRATION_SECRET, PARTS_MANAGEMENT_INTEGRATION_SECRET } = process.env;
@@ -11,7 +12,7 @@ if (typeof VSSTA_INTEGRATION_SECRET === "string" && VSSTA_INTEGRATION_SECRET.len
router.post("/vssta", vsstaMiddleware, vsstaIntegration);
} else {
console.warn("VSSTA_INTEGRATION_SECRET is not set — skipping /vssta integration route");
logger.logger.warn("VSSTA_INTEGRATION_SECRET is not set — skipping /vssta integration route");
}
// Only load Parts Management routes if that secret is set
@@ -45,14 +46,17 @@ if (typeof PARTS_MANAGEMENT_INTEGRATION_SECRET === "string" && PARTS_MANAGEMENT_
);
// Deprovisioning route
router.post("/parts-management/deprovision", partsManagementIntegrationMiddleware, partsManagementDeprovisioning);
if (process.env.NODE_ENV !== "production" && !process.env.HOSTNAME?.endsWith("compute.internal")) {
logger.logger.warn("Parts Management Deprovisioning route has been loaded.");
router.post("/parts-management/deprovision", partsManagementIntegrationMiddleware, partsManagementDeprovisioning);
}
/**
* Route to handle Parts Management Provisioning
*/
router.post("/parts-management/provision", partsManagementIntegrationMiddleware, partsManagementProvisioning);
} else {
console.warn("PARTS_MANAGEMENT_INTEGRATION_SECRET is not set — skipping /parts-management/provision route");
logger.logger.warn("PARTS_MANAGEMENT_INTEGRATION_SECRET is not set — skipping /parts-management/provision route");
}
module.exports = router;