From fd6f46e39d774c996474797e476a41680b192497 Mon Sep 17 00:00:00 2001 From: Dave Date: Thu, 26 Feb 2026 15:56:57 -0500 Subject: [PATCH] feature/feature/IO-3554-Form-Row-Layout - Responsive overhaul --- client/.env.development.imex | 1 + client/.env.development.rome | 1 + client/package-lock.json | 24 ++-- client/package.json | 4 +- client/src/App/App.container.jsx | 52 ++++++++- client/src/App/themeProvider.js | 2 +- .../accounting-payables-table.component.jsx | 6 +- .../accounting-payments-table.component.jsx | 6 +- ...accounting-receivables-table.component.jsx | 6 +- .../audit-trail-list.component.jsx | 5 +- .../email-audit-trail-list.component.jsx | 5 +- .../bill-form-lines-extended.component.jsx | 12 +- .../bills-list-table.component.jsx | 6 +- .../bills-vendors-list.component.jsx | 6 +- .../contract-cars/contract-cars.component.jsx | 6 +- .../contract-jobs/contract-jobs.component.jsx | 6 +- .../contracts-find-modal.container.jsx | 6 +- .../contracts-list.component.jsx | 6 +- .../courtesy-car-contract-list.component.jsx | 6 +- .../courtesy-cars-list.component.jsx | 6 +- .../csi-response-list-paginated.component.jsx | 6 +- .../job-lifecycle-dashboard.component.jsx | 6 +- .../monthly-job-costing.component.jsx | 28 ++--- .../scheduled-delivery-today.component.jsx | 6 +- .../scheduled-in-today.component.jsx | 6 +- .../scheduled-out-today.component.jsx | 6 +- .../dms-allocations-summary-ap.component.jsx | 10 +- .../dms-allocations-summary.component.jsx | 24 ++-- .../rr-dms-allocations-summary.component.jsx | 37 +++--- .../dms-cdk-makes/dms-cdk-makes.component.jsx | 6 +- .../cdk-customer-selector.jsx | 6 +- .../fortellis-customer-selector.jsx | 6 +- .../pbs-customer-selector.jsx | 6 +- .../rr-customer-selector.jsx | 22 ++-- .../dms-post-form/rr-early-ro-form.jsx | 12 +- .../inventory-list.component.jsx | 6 +- .../job-audit-trail.component.jsx | 19 ++- .../job-close-ro-guard.ppd.jsx | 13 ++- .../job-close-ro-guard.sublet.jsx | 13 ++- .../job-costing-parts-table.component.jsx | 22 ++-- .../job-detail-cards.parts.component.jsx | 9 +- .../job-detail-lines/job-lines.component.jsx | 6 +- .../job-lifecycle/job-lifecycle.component.jsx | 6 +- .../job-payments/job-payments.component.jsx | 26 +++-- ...b-reconciliation-bills-table.component.jsx | 6 +- ...b-reconciliation-parts-table.component.jsx | 6 +- .../job-totals.table.labor.component.jsx | 108 +++++++++--------- .../job-totals.table.other.component.jsx | 29 ++--- .../job-totals.table.parts.component.jsx | 43 +++---- .../job-totals.table.totals.component.jsx | 5 +- .../jobs-available-scan.component.jsx | 6 +- .../jobs-available-table.component.jsx | 12 +- ...obs-create-owner-info.search.component.jsx | 6 +- ...eate-vehicle-info.predefined.component.jsx | 8 +- ...s-create-vehicle-info.search.component.jsx | 6 +- .../jobs-find-modal.component.jsx | 6 +- .../jobs-list-paginated.component.jsx | 6 +- .../jobs-list/jobs-list.component.jsx | 29 +---- .../jobs-notes/jobs.notes.component.jsx | 12 +- .../jobs-ready-list.component.jsx | 29 +---- .../labor-allocations-table.component.jsx | 33 ++++-- ...or-allocations-table.payroll.component.jsx | 25 ++-- .../layout-form-row.component.jsx | 58 +++++++--- .../layout-form-row.styles.scss | 21 +++- .../notification-settings-form.component.jsx | 12 +- .../owner-detail-jobs.component.jsx | 6 +- .../owner-find-modal.component.jsx | 6 +- .../owners-list/owners-list.component.jsx | 6 +- .../parts-dispatch-expander.component.jsx | 10 +- .../parts-dispatch-table.component.jsx | 6 +- ...arts-order-list-table-drawer.component.jsx | 10 +- .../parts-order-list-table.component.jsx | 6 +- .../parts-queue-job-lines.component.jsx | 6 +- .../parts-queue.list.component.jsx | 6 +- .../payment-list-paginated.component.jsx | 6 +- .../phone-number-consent.component.jsx | 6 +- .../profile-shops/profile-shops.component.jsx | 12 +- .../responsive-table.component.jsx | 52 +++++++++ .../scoreboard-jobs-list.component.jsx | 6 +- ...scoreboard-timetickets.stats.component.jsx | 6 +- .../shop-employees-form.component.jsx | 6 +- .../shop-employees-list.component.jsx | 6 +- .../shop-teams/shop-employee-teams.list.jsx | 6 +- .../shop-users/shop-users.component.jsx | 6 +- .../simplified-parts-jobs-list.component.jsx | 6 +- .../task-list/task-list.component.jsx | 7 +- .../tech-lookup-jobs-list.component.jsx | 6 +- .../time-ticket-list-team-pay.component.jsx | 19 ++- .../time-ticket-list.component.jsx | 30 ++--- ...me-tickets-summary-employees.component.jsx | 9 +- .../tt-approvals-list.component.jsx | 6 +- .../vehicle-detail-jobs.component.jsx | 6 +- .../vehicles-list/vehicles-list.component.jsx | 6 +- .../vendors-list/vendors-list.component.jsx | 6 +- .../src/pages/bills/bills.page.component.jsx | 6 +- .../export-logs.page.component.jsx | 6 +- .../phonebook/phonebook.page.component.jsx | 6 +- .../tech-assigned-prod-jobs.component.jsx | 6 +- .../tech-dispatched-parts.page.jsx | 6 +- 99 files changed, 807 insertions(+), 443 deletions(-) create mode 100644 client/src/components/responsive-table/responsive-table.component.jsx diff --git a/client/.env.development.imex b/client/.env.development.imex index 1a43b6c7f..add365e15 100644 --- a/client/.env.development.imex +++ b/client/.env.development.imex @@ -18,3 +18,4 @@ 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 +VITE_APP_ENABLE_RESPONSIVE_TABLE_FILTERING=false diff --git a/client/.env.development.rome b/client/.env.development.rome index ac5c8e6fe..56764ec6a 100644 --- a/client/.env.development.rome +++ b/client/.env.development.rome @@ -20,3 +20,4 @@ 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 +VITE_APP_ENABLE_RESPONSIVE_TABLE_FILTERING=false diff --git a/client/package-lock.json b/client/package-lock.json index 3f126d895..02a8f6229 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -47,7 +47,7 @@ "i18next": "^25.8.13", "i18next-browser-languagedetector": "^8.2.1", "immutability-helper": "^3.1.1", - "libphonenumber-js": "^1.12.37", + "libphonenumber-js": "^1.12.38", "lightningcss": "^1.31.1", "logrocket": "^12.0.0", "markerjs2": "^2.32.7", @@ -55,7 +55,7 @@ "normalize-url": "^8.1.1", "object-hash": "^3.0.0", "phone": "^3.1.71", - "posthog-js": "^1.354.1", + "posthog-js": "^1.355.0", "prop-types": "^15.8.1", "query-string": "^9.3.1", "raf-schd": "^4.0.3", @@ -4861,9 +4861,9 @@ } }, "node_modules/@posthog/types": { - "version": "1.354.1", - "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.354.1.tgz", - "integrity": "sha512-/d2ubZOcRbKJU5PWSkt8AwR3l/CCv5vPf4RDHIpGptI6ezDf5sTFWdqF088ziS7J3CB03Pax/ubAqsgJUfIy9A==", + "version": "1.355.0", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.355.0.tgz", + "integrity": "sha512-g9YNcIzSe+BJFKjuea7Hr0DiN83XlmqBXRpIyEJ7t9jVAK2Srf67Y2rI0ZjeNXe2LasOyOvoy7RgnSPBXbNZGw==", "license": "MIT" }, "node_modules/@protobufjs/aspromise": { @@ -12906,9 +12906,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.12.37", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.37.tgz", - "integrity": "sha512-rDU6bkpuMs8YRt/UpkuYEAsYSoNuDEbrE41I3KNvmXREGH6DGBJ8Wbak4by29wNOQ27zk4g4HL82zf0OGhwRuw==", + "version": "1.12.38", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.38.tgz", + "integrity": "sha512-vwzxmasAy9hZigxtqTbFEwp8ZdZ975TiqVDwj5bKx5sR+zi5ucUQy9mbVTkKM9GzqdLdxux/hTw2nmN5J7POMA==", "license": "MIT" }, "node_modules/lightningcss": { @@ -15049,9 +15049,9 @@ "license": "MIT" }, "node_modules/posthog-js": { - "version": "1.354.1", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.354.1.tgz", - "integrity": "sha512-ncHYOOglHAsy5H9xogwmfBeqOF8bK58+b2VRv5SdnqiCfUBndo5pgAU9z2zS5MJuXuYbbpbBN0fGafHwmdr+5Q==", + "version": "1.355.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.355.0.tgz", + "integrity": "sha512-RpxHyodlr9wuqoZOct8DDg50SGq3SQmUWxYu+G1eOO+PvWsr5N2ZZdhE/rpv2zZccPdbXdgIy3M1pa574Nps7w==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@opentelemetry/api": "^1.9.0", @@ -15060,7 +15060,7 @@ "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", "@posthog/core": "1.23.1", - "@posthog/types": "1.354.1", + "@posthog/types": "1.355.0", "core-js": "^3.38.1", "dompurify": "^3.3.1", "fflate": "^0.4.8", diff --git a/client/package.json b/client/package.json index 0e2f87670..1f14bec96 100644 --- a/client/package.json +++ b/client/package.json @@ -46,7 +46,7 @@ "i18next": "^25.8.13", "i18next-browser-languagedetector": "^8.2.1", "immutability-helper": "^3.1.1", - "libphonenumber-js": "^1.12.37", + "libphonenumber-js": "^1.12.38", "lightningcss": "^1.31.1", "logrocket": "^12.0.0", "markerjs2": "^2.32.7", @@ -54,7 +54,7 @@ "normalize-url": "^8.1.1", "object-hash": "^3.0.0", "phone": "^3.1.71", - "posthog-js": "^1.354.1", + "posthog-js": "^1.355.0", "prop-types": "^15.8.1", "query-string": "^9.3.1", "raf-schd": "^4.0.3", diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index 8abf59242..bcfc39fa1 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -1,7 +1,7 @@ import { ApolloProvider } from "@apollo/client/react"; import * as Sentry from "@sentry/react"; import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react"; -import { ConfigProvider } from "antd"; +import { ConfigProvider, Grid } from "antd"; import enLocale from "antd/es/locale/en_US"; import { useEffect, useMemo } from "react"; import { CookiesProvider } from "react-cookie"; @@ -43,10 +43,47 @@ function AppContainer() { const currentUser = useSelector(selectCurrentUser); const isDarkMode = useSelector(selectDarkMode); + const screens = Grid.useBreakpoint(); + const isPhone = !screens.md; + const isUltraWide = Boolean(screens.xxxl); - const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]); + const theme = useMemo(() => { + const baseTheme = getTheme(isDarkMode); + + return { + ...baseTheme, + token: { + ...(baseTheme.token || {}), + screenXXXL: 2160 + }, + components: { + ...(baseTheme.components || {}), + Table: { + ...(baseTheme.components?.Table || {}), + cellFontSizeSM: isPhone ? 12 : 13, + cellFontSizeMD: isPhone ? 13 : isUltraWide ? 15 : 14, + cellFontSize: isUltraWide ? 15 : 14, + cellPaddingInlineSM: isPhone ? 8 : 10, + cellPaddingInlineMD: isPhone ? 10 : 14, + cellPaddingInline: isUltraWide ? 20 : 16, + cellPaddingBlockSM: isPhone ? 8 : 10, + cellPaddingBlockMD: isPhone ? 10 : 12, + cellPaddingBlock: isUltraWide ? 14 : 12, + selectionColumnWidth: isPhone ? 44 : 52 + } + } + }; + }, [isDarkMode, isPhone, isUltraWide]); const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []); + const antdTable = useMemo(() => ({ scroll: { x: "max-content" } }), []); + const antdPagination = useMemo( + () => ({ + showSizeChanger: !isPhone, + totalBoundaryShowSizeChanger: 100 + }), + [isPhone] + ); const antdForm = useMemo( () => ({ @@ -122,7 +159,16 @@ function AppContainer() { return ( - + diff --git a/client/src/App/themeProvider.js b/client/src/App/themeProvider.js index 06ea72e18..b05671c1f 100644 --- a/client/src/App/themeProvider.js +++ b/client/src/App/themeProvider.js @@ -68,7 +68,7 @@ const currentTheme = import.meta.env.DEV ? devTheme : prodTheme; const getTheme = (isDarkMode) => ({ algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm, - ...defaultsDeep(currentTheme, defaultTheme) + ...defaultsDeep({}, currentTheme, defaultTheme(isDarkMode)) }); export default getTheme; diff --git a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx index e6d7d9b1e..aaf81c2a6 100644 --- a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx +++ b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx @@ -1,4 +1,4 @@ -import { Card, Checkbox, Input, Space, Table } from "antd"; +import { Card, Checkbox, Input, Space } from "antd"; import queryString from "query-string"; import { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -16,6 +16,7 @@ import PayableExportAll from "../payable-export-all-button/payable-export-all-bu import PayableExportButton from "../payable-export-button/payable-export-button.component"; import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component"; import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import useLocalStorage from "./../../utils/useLocalStorage"; const mapStateToProps = createStructuredSelector({ @@ -179,11 +180,12 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref } > - } > -
} > -
setSearch(e.target.value)} allowClear /> -
+ ); } diff --git a/client/src/components/bills-list-table/bills-list-table.component.jsx b/client/src/components/bills-list-table/bills-list-table.component.jsx index 283949d91..7f4ab5149 100644 --- a/client/src/components/bills-list-table/bills-list-table.component.jsx +++ b/client/src/components/bills-list-table/bills-list-table.component.jsx @@ -1,5 +1,5 @@ import { EditFilled, SyncOutlined } from "@ant-design/icons"; -import { Button, Card, Checkbox, Input, Space, Table } from "antd"; +import { Button, Card, Checkbox, Input, Space } from "antd"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { FaTasks } from "react-icons/fa"; @@ -18,6 +18,7 @@ import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit- import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import LockerWrapperComponent from "../lock-wrapper/lock-wrapper.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import UpsellComponent, { upsellEnum } from "../upsell/upsell.component"; const mapStateToProps = createStructuredSelector({ @@ -237,12 +238,13 @@ export function BillsListTableComponent({ } > -
{ return ( @@ -91,6 +92,7 @@ export default function BillsVendorsList() { dataSource={dataSource} pagination={{ placement: "top" }} columns={columns} + mobileColumnKeys={["name", "cost_center", "city"]} rowKey="id" onChange={handleTableChange} rowSelection={{ diff --git a/client/src/components/contract-cars/contract-cars.component.jsx b/client/src/components/contract-cars/contract-cars.component.jsx index f43ac2945..814017d9f 100644 --- a/client/src/components/contract-cars/contract-cars.component.jsx +++ b/client/src/components/contract-cars/contract-cars.component.jsx @@ -1,4 +1,5 @@ -import { Card, Input, Table } from "antd"; +import { Card, Input } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { alphaSort } from "../../utils/sorters"; @@ -103,10 +104,11 @@ export default function ContractsCarsComponent({ loading, data, selectedCarId, h /> } > -
} > -
{error && } -
{record.actualreturn} } ]} + mobileColumnKeys={["agreementnumber", "job.ro_number", "driver_ln", "status"]} rowKey="id" dataSource={data?.cccontracts} /> diff --git a/client/src/components/contracts-list/contracts-list.component.jsx b/client/src/components/contracts-list/contracts-list.component.jsx index 313393245..28195bf13 100644 --- a/client/src/components/contracts-list/contracts-list.component.jsx +++ b/client/src/components/contracts-list/contracts-list.component.jsx @@ -1,5 +1,5 @@ import { SyncOutlined } from "@ant-design/icons"; -import { Button, Card, Input, Space, Table, Typography } from "antd"; +import { Button, Card, Input, Space, Typography } from "antd"; import queryString from "query-string"; import { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -14,6 +14,7 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { pageLimit } from "../../utils/config"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -170,13 +171,14 @@ export function ContractsList({ bodyshop, loading, contracts, refetch, total, se } > -
-
} > -
refetch()} icon={} />}> -
-
record.status} dataSource={lifecycleData.summations.sort((a, b) => b.value - a.value).slice(0, 3)} /> diff --git a/client/src/components/dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx b/client/src/components/dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx index 7088bc684..d00940657 100644 --- a/client/src/components/dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx +++ b/client/src/components/dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx @@ -1,4 +1,5 @@ -import { Card, Input, Space, Table, Typography } from "antd"; +import { Card, Input, Space, Typography } from "antd"; +import ResponsiveTable from "../../responsive-table/responsive-table.component"; import axios from "axios"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -104,30 +105,31 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) { >
-
( - - + + {t("general.labels.totals")} - - + + {Dinero(costingData?.allSummaryData && costingData.allSummaryData.totalSales).toFormat()} - - + + {Dinero(costingData?.allSummaryData && costingData.allSummaryData.totalCost).toFormat()} - - + + {Dinero(costingData?.allSummaryData && costingData.allSummaryData.gpdollars).toFormat()} - - - + + + )} /> diff --git a/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx b/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx index 80c10a884..9ae59833a 100644 --- a/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx +++ b/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx @@ -1,5 +1,6 @@ import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons"; -import { Card, Space, Switch, Table, Tooltip, Typography } from "antd"; +import { Card, Space, Switch, Tooltip, Typography } from "antd"; +import ResponsiveTable from "../../responsive-table/responsive-table.component"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; @@ -359,10 +360,11 @@ export default function DashboardScheduledDeliveryToday({ data, ...cardProps }) {...cardProps} >
-
-
-
( -
+ @@ -91,7 +92,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) { ))} -
{t("bills.fields.invoice_number")} {t("bodyshop.fields.dms.dms_acctnumber")}{l.Amount}
+ ) } ]; @@ -115,9 +116,10 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) { /> } > - `${record.InvoiceNumber}${record.Account}`} dataSource={allocationsSummary} locale={{ emptyText: t("dms.labels.refreshallocations") }} diff --git a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx index 75d15aa02..cfbd1e536 100644 --- a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx +++ b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx @@ -1,4 +1,5 @@ -import { Alert, Button, Card, Table, Typography } from "antd"; +import { Alert, Button, Card, Typography } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { SyncOutlined } from "@ant-design/icons"; import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -116,9 +117,10 @@ export function DmsAllocationsSummary({ mode, socket, bodyshop, jobId, title, on )} -
- + + {t("general.labels.totals")} - - {hasNonZeroSaleTotal ? totals.totalSale.toFormat() : null} - - - - + + + {hasNonZeroSaleTotal ? totals.totalSale.toFormat() : null} + + + + + ); }} /> diff --git a/client/src/components/dms-allocations-summary/rr-dms-allocations-summary.component.jsx b/client/src/components/dms-allocations-summary/rr-dms-allocations-summary.component.jsx index abd2137af..4cffcfd85 100644 --- a/client/src/components/dms-allocations-summary/rr-dms-allocations-summary.component.jsx +++ b/client/src/components/dms-allocations-summary/rr-dms-allocations-summary.component.jsx @@ -1,4 +1,5 @@ -import { Alert, Button, Card, Table, Tabs, Typography } from "antd"; +import { Alert, Button, Card, Tabs, Typography } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { SyncOutlined } from "@ant-design/icons"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -261,9 +262,10 @@ export function RrAllocationsSummary({ socket, bodyshop, jobId, title, onAllocat into taxable / non-taxable segments. -
- + + {t("general.labels.totals")} - - - - - - - - {hasCustTotal ? roggTotals.totalCustPrice : null} - {hasCostTotal ? roggTotals.totalDlrCost : null} - + + + + + + + + + {hasCustTotal ? roggTotals.totalCustPrice : null} + + + {hasCostTotal ? roggTotals.totalDlrCost : null} + + ); }} /> @@ -313,9 +319,10 @@ export function RrAllocationsSummary({ socket, bodyshop, jobId, title, onAllocat This mirrors the shell that would be sent for ROLABOR when all financials are carried in GOG. -
{error && } -
( callSearch({ variables: { search: val } })} @@ -69,6 +70,7 @@ export function DmsCdkVehicles({ form, job }) { /> )} columns={columns} + mobileColumnKeys={["make", "model", "makecode", "modelcode"]} loading={loading} rowKey="id" dataSource={data ? data.search_dms_vehicles : []} diff --git a/client/src/components/dms-customer-selector/cdk-customer-selector.jsx b/client/src/components/dms-customer-selector/cdk-customer-selector.jsx index 479b23f0c..755ca84db 100644 --- a/client/src/components/dms-customer-selector/cdk-customer-selector.jsx +++ b/client/src/components/dms-customer-selector/cdk-customer-selector.jsx @@ -1,4 +1,5 @@ -import { Button, Checkbox, Col, Table } from "antd"; +import { Button, Checkbox, Col } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { alphaSort } from "../../utils/sorters"; @@ -72,7 +73,7 @@ export default function CDKCustomerSelector({ bodyshop, socket }) { return ( -
(
-
(
-
(
+ (
{/* Open RO limit banner */} @@ -304,6 +299,7 @@ export default function RRCustomerSelector({ )} pagination={{ placement: "top" }} columns={columns} + mobileColumnKeys={["custNo", "vinOwner", "name", "address"]} rowKey={(r) => r.custNo} dataSource={customerList} rowSelection={{ diff --git a/client/src/components/dms-post-form/rr-early-ro-form.jsx b/client/src/components/dms-post-form/rr-early-ro-form.jsx index cda20e543..2761debfd 100644 --- a/client/src/components/dms-post-form/rr-early-ro-form.jsx +++ b/client/src/components/dms-post-form/rr-early-ro-form.jsx @@ -1,5 +1,6 @@ import { ReloadOutlined } from "@ant-design/icons"; -import { Alert, Button, Form, Input, InputNumber, Modal, Radio, Select, Space, Table, Typography } from "antd"; +import { Alert, Button, Form, Input, InputNumber, Modal, Radio, Select, Space, Typography } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useEffect, useMemo, useState } from "react"; // Simple customer selector table @@ -26,7 +27,14 @@ function CustomerSelectorTable({ customers, onSelect, isSubmitting }) { return (
-
+
} > -
+ -
+ diff --git a/client/src/components/job-close-ro-guard/job-close-ro-guard.ppd.jsx b/client/src/components/job-close-ro-guard/job-close-ro-guard.ppd.jsx index b4ad9b6a8..555a70276 100644 --- a/client/src/components/job-close-ro-guard/job-close-ro-guard.ppd.jsx +++ b/client/src/components/job-close-ro-guard/job-close-ro-guard.ppd.jsx @@ -1,5 +1,6 @@ import { useEffect } from "react"; -import { Alert, Card, Table } from "antd"; +import { Alert, Card } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { t } from "i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -68,7 +69,15 @@ export function JobCloseRGuardPpd({ job, warningCallback }) { return ( -
+ {linesWithPPD.length > 0 && ( )} diff --git a/client/src/components/job-close-ro-guard/job-close-ro-guard.sublet.jsx b/client/src/components/job-close-ro-guard/job-close-ro-guard.sublet.jsx index 2cfed2a88..e42739fd6 100644 --- a/client/src/components/job-close-ro-guard/job-close-ro-guard.sublet.jsx +++ b/client/src/components/job-close-ro-guard/job-close-ro-guard.sublet.jsx @@ -1,6 +1,7 @@ import { useEffect } from "react"; -import { Alert, Card, Table } from "antd"; +import { Alert, Card } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { t } from "i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -62,7 +63,15 @@ export function JobCloseRGuardSublet({ job, warningCallback }) { return ( -
+ {subletsNotDone.length > 0 && ( )} diff --git a/client/src/components/job-costing-parts-table/job-costing-parts-table.component.jsx b/client/src/components/job-costing-parts-table/job-costing-parts-table.component.jsx index 04289f81e..d3568fb71 100644 --- a/client/src/components/job-costing-parts-table/job-costing-parts-table.component.jsx +++ b/client/src/components/job-costing-parts-table/job-costing-parts-table.component.jsx @@ -1,4 +1,5 @@ -import { Input, Space, Table, Typography } from "antd"; +import { Input, Space, Typography } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { alphaSort } from "../../utils/sorters"; @@ -65,7 +66,7 @@ export default function JobCostingPartsTable({ data, summaryData }) { return (
-
{ return ( @@ -87,18 +88,19 @@ export default function JobCostingPartsTable({ data, summaryData }) { onChange={handleTableChange} pagination={{ placement: "top", defaultPageSize: pageLimit }} columns={columns} + mobileColumnKeys={["cost_center", "sales", "costs", "gpdollars", "gppercent"]} rowKey="id" dataSource={filteredData} summary={() => ( - - + + {t("general.labels.totals")} - - {Dinero(summaryData.totalSales).toFormat()} - {Dinero(summaryData.totalCost).toFormat()} - {Dinero(summaryData.gpdollars).toFormat()} - - + + {Dinero(summaryData.totalSales).toFormat()} + {Dinero(summaryData.totalCost).toFormat()} + {Dinero(summaryData.gpdollars).toFormat()} + + )} /> diff --git a/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx index a8e749c9a..127d5f94d 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.parts.component.jsx @@ -1,4 +1,4 @@ -import { Table } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useTranslation } from "react-i18next"; import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component"; import PartsStatusPie from "../parts-status-pie/parts-status-pie.component"; @@ -101,7 +101,12 @@ function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
-
+ ); diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx index 735374b11..1376ec903 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -11,7 +11,8 @@ import { import { PageHeader } from "@ant-design/pro-layout"; import { useMutation } from "@apollo/client/react"; import { gql } from "@apollo/client"; -import { Button, Dropdown, Input, Modal, Select, Space, Table, Tag, Typography } from "antd"; +import { Button, Dropdown, Input, Modal, Select, Space, Tag, Typography } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import axios from "axios"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -688,8 +689,9 @@ export function JobLinesComponent({ } /> -
} > -
diff --git a/client/src/components/job-payments/job-payments.component.jsx b/client/src/components/job-payments/job-payments.component.jsx index 37342aa83..c0cbb9846 100644 --- a/client/src/components/job-payments/job-payments.component.jsx +++ b/client/src/components/job-payments/job-payments.component.jsx @@ -1,5 +1,6 @@ import { EditFilled } from "@ant-design/icons"; -import { Button, Card, Space, Table } from "antd"; +import { Button, Card, Space } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import Dinero from "dinero.js"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -186,8 +187,9 @@ export function JobPayments({ job, bodyshop, setPaymentContext, setCardPaymentCo } > -
}} summary={() => ( - - + + {t("payments.labels.totalpayments")} - - - + + + {total.toFormat()} - - - - - + + + + + )} /> diff --git a/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx b/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx index b779c6c69..41ba39e17 100644 --- a/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx +++ b/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx @@ -1,4 +1,5 @@ -import { Checkbox, Table, Typography } from "antd"; +import { Checkbox, Typography } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; @@ -79,11 +80,12 @@ export default function JobReconciliationBillsTable({ billLineState, invoiceLine return (
{t("bills.labels.bills")} -
{t("jobs.labels.lines")} -
( <> - - + + {t("jobs.labels.labor_rates_subtotal")} - - - + + + {(job.job_totals.rates.mapa.hours + job.job_totals.rates.mash.hours).toFixed(1)} - + {InstanceRenderManager({ imex: null, rome: ( <> - - + + ) })} - + {Dinero(job.job_totals.rates.rates_subtotal).toFormat()} - - - - + + + + {t("jobs.labels.mapa")} {InstanceRenderManager({ @@ -156,34 +158,34 @@ export default function JobTotalsTableLabor({ job }) { }) })} - - + + {job.job_totals.rates.mapa.rate} - - {job.job_totals.rates.mapa.hours.toFixed(1)} + + {job.job_totals.rates.mapa.hours.toFixed(1)} {InstanceRenderManager({ imex: ( - + {Dinero(job.job_totals.rates.mapa.total).toFormat()} - + ), rome: ( <> - + {Dinero(job.job_totals.rates.mapa.base).toFormat()} - - + + {Dinero(job.job_totals.rates.mapa.adjustment).toFormat()} - - + + {Dinero(job.job_totals.rates.mapa.total).toFormat()} - + ) })} - - - + + + {t("jobs.labels.mash")} {InstanceRenderManager({ @@ -202,51 +204,51 @@ export default function JobTotalsTableLabor({ job }) { }) })} - - + + {job.job_totals.rates.mash.rate} - - {job.job_totals.rates.mash.hours.toFixed(1)} + + {job.job_totals.rates.mash.hours.toFixed(1)} {InstanceRenderManager({ imex: ( - + {Dinero(job.job_totals.rates.mash.total).toFormat()} - + ), rome: ( <> - + {Dinero(job.job_totals.rates.mash.base).toFormat()} - - + + {Dinero(job.job_totals.rates.mash.adjustment).toFormat()} - - + + {Dinero(job.job_totals.rates.mash.total).toFormat()} - + ) })} - - - + + + {t("jobs.labels.rates_subtotal")} - - - + + + {InstanceRenderManager({ imex: null, rome: ( <> - - + + ) })} - + {Dinero(job.job_totals.rates.subtotal).toFormat()} - - + + )} /> diff --git a/client/src/components/job-totals-table/job-totals.table.other.component.jsx b/client/src/components/job-totals-table/job-totals.table.other.component.jsx index 51f96d81b..272138132 100644 --- a/client/src/components/job-totals-table/job-totals.table.other.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.other.component.jsx @@ -1,4 +1,4 @@ -import { Table } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import Dinero from "dinero.js"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -65,8 +65,9 @@ export default function JobTotalsTableOther({ job }) { setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); }; return ( -
( <> - - + + {t("jobs.labels.additionaltotal")} - + - + {Dinero(job.job_totals.additional.total).toFormat()} - - - - + + + + {t("jobs.labels.subletstotal")} - + - + {Dinero(job.job_totals.parts.sublets.total).toFormat()} - - + + )} /> diff --git a/client/src/components/job-totals-table/job-totals.table.parts.component.jsx b/client/src/components/job-totals-table/job-totals.table.parts.component.jsx index 9141ce848..df62dd003 100644 --- a/client/src/components/job-totals-table/job-totals.table.parts.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.parts.component.jsx @@ -1,4 +1,4 @@ -import { Table } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import Dinero from "dinero.js"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -64,8 +64,9 @@ export default function JobTotalsTableParts({ job }) { setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); }; return ( -
( <> - - {t("jobs.labels.prt_dsmk_total")} - + + {t("jobs.labels.prt_dsmk_total")} + {Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()} - - + + - - + + {t("jobs.labels.partstotal")} - + - + {Dinero(job.job_totals.parts.parts.total).toFormat()} - - + + { //TODO:AIO This shoudl only be in the US version. need to verify whether this causes problems for the CA version. insuranceAdjustments.length > 0 && ( - - {t("jobs.labels.profileadjustments")} - + + + {t("jobs.labels.profileadjustments")} + + ) } {insuranceAdjustments.map((adj, idx) => ( - - {t(`jobs.fields.${adj.id.toLowerCase()}`)} + + {t(`jobs.fields.${adj.id.toLowerCase()}`)} - {adj.amount.toFormat()} - + {adj.amount.toFormat()} + ))} )} diff --git a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx index c1bc373a8..14d5d58da 100644 --- a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx @@ -1,4 +1,4 @@ -import { Table } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import Dinero from "dinero.js"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; @@ -245,8 +245,9 @@ export function JobTotalsTableTotals({ bodyshop, job }) { ]; return ( -
} > -
} > -
+ ); } diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx index 5b438ae1e..3897c8846 100644 --- a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx @@ -1,4 +1,5 @@ -import { Card, Input, Table } from "antd"; +import { Card, Input } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; @@ -95,11 +96,12 @@ export default function JobsCreateOwnerInfoSearchComponent({ loading, owners }) /> } > -
(
-
setSearch(value)} enterButton/>} + title={() => setSearch(value)} enterButton />} dataSource={filteredPredefinedVehicles} columns={[ { @@ -61,6 +62,7 @@ export default function JobsCreateVehicleInfoPredefined({ disabled, form }) { ) } ]} + mobileColumnKeys={["make", "model", "select"]} /> ); diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.search.component.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.search.component.jsx index 41ecac4a0..00de6846e 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.search.component.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.search.component.jsx @@ -1,4 +1,5 @@ -import { Card, Input, Space, Table } from "antd"; +import { Card, Input, Space } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; @@ -68,11 +69,12 @@ export default function JobsCreateVehicleInfoSearchComponent({ loading, vehicles } > -
-
(
{t("jobs.labels.existing_jobs")} @@ -191,6 +192,7 @@ export default function JobsFindModalComponent({ )} pagination={{ placement: "bottom" }} columns={columns} + mobileColumnKeys={["ro_number", "owner", "status", "vehicle"]} rowKey="id" loading={jobsListLoading} dataSource={jobsList} diff --git a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx index ec9b77b59..ad029eff7 100644 --- a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx +++ b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx @@ -1,5 +1,5 @@ import { SyncOutlined } from "@ant-design/icons"; -import { Button, Card, Input, Space, Table, Typography } from "antd"; +import { Button, Card, Input, Space, Typography } from "antd"; import axios from "axios"; import _ from "lodash"; import queryString from "query-string"; @@ -15,6 +15,7 @@ import { alphaSort, statusSort } from "../../utils/sorters"; import useLocalStorage from "../../utils/useLocalStorage"; import StartChatButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { logImEXEvent } from "../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ @@ -238,7 +239,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { } > -
({}); export function JobsList({ bodyshop }) { const searchParams = queryString.parse(useLocation().search); const { selected } = searchParams; - const screens = Grid.useBreakpoint(); const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { variables: { statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"] @@ -296,15 +296,6 @@ export function JobsList({ bodyshop }) { // }, ]; - const scrollMapper = { - xs: true, - sm: true, - md: true, - lg: "100%", - xl: "100%", - xxl: "100%" - }; - return ( } > -
{ handleOnRowClick(record); diff --git a/client/src/components/jobs-notes/jobs.notes.component.jsx b/client/src/components/jobs-notes/jobs.notes.component.jsx index d325176e3..6d23f75ce 100644 --- a/client/src/components/jobs-notes/jobs.notes.component.jsx +++ b/client/src/components/jobs-notes/jobs.notes.component.jsx @@ -1,5 +1,6 @@ import { AuditOutlined, DeleteFilled, EditFilled, EyeInvisibleFilled, WarningFilled } from "@ant-design/icons"; -import { Button, Card, Form, Input, Space, Table } from "antd"; +import { Button, Card, Form, Input, Space } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -185,7 +186,14 @@ export function JobNotesComponent({ } > -
+ ); diff --git a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx index de4d46a1f..0b991f259 100644 --- a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx +++ b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx @@ -1,6 +1,6 @@ import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined } from "@ant-design/icons"; import { useQuery } from "@apollo/client/react"; -import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; +import { Button, Card, Input, Space, Tooltip } from "antd"; import queryString from "query-string"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -17,6 +17,7 @@ import useLocalStorage from "../../utils/useLocalStorage"; import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -25,7 +26,6 @@ const mapStateToProps = createStructuredSelector({ export function JobsReadyList({ bodyshop }) { const searchParams = queryString.parse(useLocation().search); const { selected } = searchParams; - const screens = Grid.useBreakpoint(); const readyStatuses = useMemo(() => { if (bodyshop.md_ro_statuses.ready_statuses) return bodyshop.md_ro_statuses.ready_statuses; @@ -280,15 +280,6 @@ export function JobsReadyList({ bodyshop }) { // }, ]; - const scrollMapper = { - xs: true, - sm: true, - md: true, - lg: "100%", - xl: "100%", - xxl: "100%" - }; - return ( } > -
{ handleOnRowClick(record); diff --git a/client/src/components/labor-allocations-table/labor-allocations-table.component.jsx b/client/src/components/labor-allocations-table/labor-allocations-table.component.jsx index ca796f81d..bea88ac17 100644 --- a/client/src/components/labor-allocations-table/labor-allocations-table.component.jsx +++ b/client/src/components/labor-allocations-table/labor-allocations-table.component.jsx @@ -1,5 +1,6 @@ import { EditFilled } from "@ant-design/icons"; -import { Alert, Card, Col, Row, Space, Table, Typography } from "antd"; +import { Alert, Card, Col, Row, Space, Typography } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -196,8 +197,9 @@ export function LaborAllocationsTable({ -
`${record.cost_center} ${record.mod_lbr_ty}`} pagination={false} onChange={handleTableChange} @@ -215,16 +217,22 @@ export function LaborAllocationsTable({ x: true }} summary={() => ( - - + + {t("general.labels.totals")} - - {summary.hrs_total.toFixed(1)} - {summary.hrs_claimed.toFixed(1)} - {summary.adjustments.toFixed(1)} - + + + {summary.hrs_total.toFixed(1)} + + + {summary.hrs_claimed.toFixed(1)} + + + {summary.adjustments.toFixed(1)} + + {(Math.abs(summary.difference) < 0.05 ? 0 : summary.difference).toFixed(1)} - - + + )} /> @@ -242,8 +250,9 @@ export function LaborAllocationsTable({ {convertedLines && convertedLines.length > 0 && ( -
} > -
`${record.employeeid} ${record.mod_lbr_ty}`} pagination={false} onChange={handleTableChange} @@ -270,16 +272,16 @@ export function PayrollLaborAllocationsTable({ }) }} summary={() => ( - - + + {t("general.labels.totals")} - - - {summary.hrs_total.toFixed(5)} - {summary.hrs_claimed.toFixed(5)} + + + {summary.hrs_total.toFixed(5)} + {summary.hrs_claimed.toFixed(5)} - {summary.difference.toFixed(5)} - + {summary.difference.toFixed(5)} + )} /> @@ -287,8 +289,9 @@ export function PayrollLaborAllocationsTable({ {convertedLines && convertedLines.length > 0 && ( -
Math.max(min, Math.floor(24 / count)); - // Mobile-first: stack on xs - const baseCol = { - xs: { span: 24 }, - sm: { span: grow ? spanFor(12) : 12 }, - md: { span: grow ? spanFor(8) : 8 }, - lg: { span: grow ? spanFor(6) : 6 }, - xl: { span: grow ? spanFor(4) : 4 }, - xxl: { span: grow ? spanFor(4) : 4 } - }; + // Modern responsive strategy leveraging Ant Design 6: + // - xs (phone <576px): Always stack vertically + // - sm (tablet 576-768px): 2 columns for better readability + // - md (tablet 768-992px): 3 columns for tablets + // - lg+ (desktop >992px): Dynamic flex-based columns that adapt to screen width + // Target: 1366px → 4 cols, 1920px → 6 cols, 2560px → 8 cols, 4K → capped at 8-9 cols + // Note: xxl uses higher min-width to naturally cap maximum columns + const baseCol = (() => { + if (grow) { + // Grow mode: use flex with reasonable min-widths + return { + xs: 24, + sm: 12, // Fixed 2 cols on small tablets + md: 8, // Fixed 3 cols on large tablets + lg: { flex: `1 1 320px` }, // Dynamic: ~3 cols at 1200px + xl: { flex: `1 1 280px` }, // Dynamic: ~4 cols at 1366px, ~6 cols at 1920px + xxl: { flex: `1 1 380px` } // Dynamic: ~6 cols at 2560px, ~9 cols at 4K (capped) + }; + } else { + // Fixed mode: Use flex without grow to maintain uniform widths across rows + // xxl uses larger min-width to cap maximum columns on 4K/ultrawide + const minWidthLg = count <= 2 ? 500 : count === 3 ? 380 : 320; // 3 cols at 1200px + const minWidthXl = count <= 2 ? 480 : count === 3 ? 360 : 280; // 4 cols at 1366px, ~6 at 1920px + const minWidthXxl = count <= 2 ? 500 : count === 3 ? 400 : 380; // ~6 cols at 2560px, ~9 at 4K (natural cap) + + return { + xs: 24, + sm: 12, // Fixed 2 columns on tablet + md: count === 1 ? 24 : count === 2 ? 12 : 8, // Fixed 1-3 cols on large tablet + lg: count === 1 ? 24 : { flex: `0 0 ${minWidthLg}px` }, // Fixed width on desktop + xl: count === 1 ? 24 : { flex: `0 0 ${minWidthXl}px` }, // Fixed width on large desktop + xxl: count === 1 ? 24 : { flex: `0 0 ${minWidthXxl}px` } // Fixed width on ultrawide + }; + } + })(); const getColPropsForChild = (child) => { if (!isValidElement(child)) return baseCol; - // Back-compat with old pattern: child.props.span can override Col sizing + // Back-compat: child.props.span can override Col sizing const spanOverride = child.props?.span; if (typeof spanOverride === "number") return { span: spanOverride }; if (spanOverride && typeof spanOverride === "object") return spanOverride; - // Optional explicit override: + // Explicit override: const colOverride = child.props?.col; if (colOverride && typeof colOverride === "object") { - return { ...baseCol, ...colOverride }; + // Deep merge: allow partial overrides while keeping baseCol defaults + const merged = { ...baseCol }; + Object.keys(colOverride).forEach((bp) => { + merged[bp] = typeof colOverride[bp] === "object" ? { ...baseCol[bp], ...colOverride[bp] } : colOverride[bp]; + }); + return merged; } return baseCol; diff --git a/client/src/components/layout-form-row/layout-form-row.styles.scss b/client/src/components/layout-form-row/layout-form-row.styles.scss index 4cedb9b79..264ae9760 100644 --- a/client/src/components/layout-form-row/layout-form-row.styles.scss +++ b/client/src/components/layout-form-row/layout-form-row.styles.scss @@ -24,7 +24,7 @@ html[data-theme="dark"] { .imex-form-row { width: 100%; - + /* Match old Divider title typography */ .ant-card-head-title { font-weight: 500; @@ -47,18 +47,33 @@ html[data-theme="dark"] { background: var(--imex-form-surface); } - /* Optional: slightly tighter on phones */ + /* Optional: tighter spacing on phones for better space usage */ @media (max-width: 575px) { .ant-card-head { padding-inline: 12px; + padding-block: 12px; } .ant-card-body { padding: 12px; } } - /* Common form nicety */ + /* Tablet optimization: slightly reduce padding */ + @media (min-width: 576px) and (max-width: 991px) { + .ant-card-body { + padding: 14px; + } + } + + /* Ensure form items use full column width */ .ant-col > * { width: 100%; } + + /* Better form item spacing on mobile */ + @media (max-width: 575px) { + .ant-form-item { + margin-bottom: 12px; + } + } } diff --git a/client/src/components/notification-settings/notification-settings-form.component.jsx b/client/src/components/notification-settings/notification-settings-form.component.jsx index ffa6e007b..3bfb571c3 100644 --- a/client/src/components/notification-settings/notification-settings-form.component.jsx +++ b/client/src/components/notification-settings/notification-settings-form.component.jsx @@ -1,6 +1,7 @@ import { useMutation, useQuery } from "@apollo/client/react"; import { useEffect, useState } from "react"; -import { Alert, Button, Card, Checkbox, Divider, Form, Space, Switch, Table, Typography } from "antd"; +import { Alert, Button, Card, Checkbox, Divider, Form, Space, Switch, Typography } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -204,7 +205,14 @@ const NotificationSettingsForm = ({ currentUser, bodyshop }) => { )} -
+ diff --git a/client/src/components/owner-detail-jobs/owner-detail-jobs.component.jsx b/client/src/components/owner-detail-jobs/owner-detail-jobs.component.jsx index d6fe24aaa..e7cfb5468 100644 --- a/client/src/components/owner-detail-jobs/owner-detail-jobs.component.jsx +++ b/client/src/components/owner-detail-jobs/owner-detail-jobs.component.jsx @@ -1,4 +1,4 @@ -import { Card, Table } from "antd"; +import { Card } from "antd"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -11,6 +11,7 @@ import { alphaSort, dateSort, statusSort } from "../../utils/sorters"; import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component"; import { selectIsPartsEntry } from "../../redux/application/application.selectors"; import getPartsBasePath from "../../utils/getPartsBasePath.js"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -109,9 +110,10 @@ function OwnerDetailJobsComponent({ bodyshop, owner, isPartsEntry }) { /> } > -
-
} > -
-
+ diff --git a/client/src/components/parts-dispatch-table/parts-dispatch-table.component.jsx b/client/src/components/parts-dispatch-table/parts-dispatch-table.component.jsx index c1fbf51fe..bd6d66e43 100644 --- a/client/src/components/parts-dispatch-table/parts-dispatch-table.component.jsx +++ b/client/src/components/parts-dispatch-table/parts-dispatch-table.component.jsx @@ -1,5 +1,6 @@ import { MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined } from "@ant-design/icons"; -import { Button, Card, Input, Space, Table } from "antd"; +import { Button, Card, Input, Space } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -106,7 +107,7 @@ export function PartDispatchTableComponent({ bodyshop, job, billsQuery }) { } > -
-
-
-
} > -
} > -
-
} > -
+ ); diff --git a/client/src/components/responsive-table/responsive-table.component.jsx b/client/src/components/responsive-table/responsive-table.component.jsx new file mode 100644 index 000000000..4228d9efe --- /dev/null +++ b/client/src/components/responsive-table/responsive-table.component.jsx @@ -0,0 +1,52 @@ +import { Grid, Table } from "antd"; +import { useMemo } from "react"; + +function ResponsiveTable({ columns, mobileColumnKeys, scroll, ...rest }) { + const screens = Grid.useBreakpoint(); + const isPhone = !screens.md; + const isResponsiveFilteringEnabled = ["1", "true", "yes", "on"].includes( + String(import.meta.env.VITE_APP_ENABLE_RESPONSIVE_TABLE_FILTERING || "") + .trim() + .toLowerCase() + ); + + const resolvedColumns = useMemo(() => { + if ( + !isResponsiveFilteringEnabled || + !Array.isArray(columns) || + !isPhone || + !Array.isArray(mobileColumnKeys) || + mobileColumnKeys.length === 0 + ) { + return columns; + } + + const visibleColumnKeys = new Set(mobileColumnKeys); + const filteredColumns = columns.filter((column) => { + const key = column?.key ?? column?.dataIndex; + + // Keep columns with no stable key to avoid accidental loss. + if (key == null) return true; + + if (Array.isArray(key)) { + return key.some((part) => visibleColumnKeys.has(part)); + } + + return visibleColumnKeys.has(key); + }); + + return filteredColumns.length > 0 ? filteredColumns : columns; + }, [columns, isPhone, isResponsiveFilteringEnabled, mobileColumnKeys]); + + const resolvedScroll = scroll ?? { x: "max-content" }; + + return
; +} + +ResponsiveTable.Summary = Table.Summary; +ResponsiveTable.Column = Table.Column; +ResponsiveTable.ColumnGroup = Table.ColumnGroup; +ResponsiveTable.SELECTION_COLUMN = Table.SELECTION_COLUMN; +ResponsiveTable.EXPAND_COLUMN = Table.EXPAND_COLUMN; + +export default ResponsiveTable; diff --git a/client/src/components/scoreboard-jobs-list/scoreboard-jobs-list.component.jsx b/client/src/components/scoreboard-jobs-list/scoreboard-jobs-list.component.jsx index 3f06584d2..2373cb004 100644 --- a/client/src/components/scoreboard-jobs-list/scoreboard-jobs-list.component.jsx +++ b/client/src/components/scoreboard-jobs-list/scoreboard-jobs-list.component.jsx @@ -1,6 +1,7 @@ import { SyncOutlined } from "@ant-design/icons"; import { useQuery } from "@apollo/client/react"; -import { Button, Card, Input, Modal, Space, Table, Typography } from "antd"; +import { Button, Card, Input, Modal, Space, Typography } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; @@ -140,8 +141,9 @@ export default function ScoreboardJobsList() { } > -
{t("scoreboard.labels.calendarperiod")}-
-
} columns={columns} + mobileColumnKeys={["start", "length", "actions"]} rowKey={"id"} dataSource={data?.employees_by_pk?.employee_vacations ?? []} /> diff --git a/client/src/components/shop-employees/shop-employees-list.component.jsx b/client/src/components/shop-employees/shop-employees-list.component.jsx index acbea5fd0..e9082ac05 100644 --- a/client/src/components/shop-employees/shop-employees-list.component.jsx +++ b/client/src/components/shop-employees/shop-employees-list.component.jsx @@ -1,9 +1,10 @@ -import { Button, Table } from "antd"; +import { Button } from "antd"; import queryString from "query-string"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router-dom"; import { alphaSort } from "../../utils/sorters"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; export default function ShopEmployeesListComponent({ loading, employees }) { const { t } = useTranslation(); @@ -89,7 +90,7 @@ export default function ShopEmployeesListComponent({ loading, employees }) { ]; return (
-
{ return (
{ return (
diff --git a/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx b/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx index f37edddf8..b57b15d40 100644 --- a/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx +++ b/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx @@ -1,5 +1,5 @@ import { CarOutlined, SettingOutlined, SyncOutlined } from "@ant-design/icons"; -import { Button, Card, Input, Space, Table, Typography } from "antd"; +import { Button, Card, Input, Space, Typography } from "antd"; import axios from "axios"; import _ from "lodash"; import queryString from "query-string"; @@ -20,6 +20,7 @@ import * as Sentry from "@sentry/react"; import getPartsBasePath from "../../utils/getPartsBasePath.js"; import { toggleDarkMode } from "../../redux/application/application.actions.js"; import { FaMoon, FaSun } from "react-icons/fa"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -243,7 +244,7 @@ export function SimplifiedPartsJobsListComponent({ } > -
-
} > -
- % + + % + @@ -134,7 +146,7 @@ export function TimeTicketListTeamPay({ bodyshop, context }) { } return ( -
( @@ -182,6 +194,7 @@ export function TimeTicketListTeamPay({ bodyshop, context }) { key: "pay" } ]} + mobileColumnKeys={["employee", "cost_center", "productivehrs", "pay"]} /> ); }} diff --git a/client/src/components/time-ticket-list/time-ticket-list.component.jsx b/client/src/components/time-ticket-list/time-ticket-list.component.jsx index 43c222eee..5c4fda4eb 100644 --- a/client/src/components/time-ticket-list/time-ticket-list.component.jsx +++ b/client/src/components/time-ticket-list/time-ticket-list.component.jsx @@ -1,6 +1,7 @@ import { EditFilled, SyncOutlined } from "@ant-design/icons"; import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react"; -import { Button, Card, Checkbox, Space, Table } from "antd"; +import { Button, Card, Checkbox, Space } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -327,9 +328,10 @@ export function TimeTicketList({ } > -
- {t("general.labels.totals")} - - - {totals.productivehrs.toFixed(1)} - {totals.actualhrs.toFixed(1)} - + + {t("general.labels.totals")} + + + {totals.productivehrs.toFixed(1)} + {totals.actualhrs.toFixed(1)} + {totals.actualhrs === 0 || !totals.actualhrs ? "∞" : `${((totals.productivehrs / totals.actualhrs) * 100).toFixed( 2 )}% ${t("timetickets.labels.efficiency")}`} - - - - - + + + + + ); }} locale={{ diff --git a/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx b/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx index 0cef49897..b876cc958 100644 --- a/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx +++ b/client/src/components/time-tickets-summary-employees/time-tickets-summary-employees.component.jsx @@ -1,4 +1,5 @@ -import { Card, Col, Row, Table } from "antd"; +import { Card, Col, Row } from "antd"; +import ResponsiveTable from "../responsive-table/responsive-table.component"; import _ from "lodash"; import dayjs from "../../utils/day"; import { useMemo, useState } from "react"; @@ -213,12 +214,13 @@ const JobRelatedTicketsTable = ({ loading, jobTickets, startDate, endDate }) => return ( -
-
} > -
} > -
} > -
} > -
({ setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })) @@ -229,7 +230,7 @@ export function BillsListPage({ loading, data, refetch, total, setBillEnterConte > -
} > -
} > -
} > -
} > -