Merged in release/2026-05-08 (pull request #3210)

Release/2026 05 08
This commit is contained in:
Dave Richer
2026-04-29 16:46:47 +00:00
13 changed files with 142 additions and 64 deletions

View File

@@ -11,12 +11,13 @@ import ResponsiveTable from "../responsive-table/responsive-table.component";
export default function OwnersListComponent({ loading, owners, total, refetch }) {
const search = queryString.parse(useLocation().search);
const {
page
// sortcolumn, sortorder
} = search;
const { page, pageSize } = search;
const history = useNavigate();
const currentPage = Number.parseInt(page || "1", 10);
const parsedPageSize = Number.parseInt(pageSize || String(pageLimit), 10);
const currentPageSize = Number.isNaN(parsedPageSize) ? pageLimit : parsedPageSize;
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" }
@@ -71,10 +72,14 @@ export default function OwnersListComponent({ loading, owners, total, refetch })
];
const handleTableChange = (pagination, filters, sorter) => {
const nextPageSize = pagination?.pageSize || currentPageSize;
const pageSizeChanged = nextPageSize !== currentPageSize;
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
const updatedSearch = {
...search,
page: pagination.current,
pageSize: nextPageSize,
page: pageSizeChanged ? 1 : pagination.current,
sortcolumn: sorter.columnKey,
sortorder: sorter.order
};
@@ -119,7 +124,7 @@ export default function OwnersListComponent({ loading, owners, total, refetch })
>
<ResponsiveTable
loading={loading}
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(page || 1, 10), total: total }}
pagination={{ placement: "top", pageSize: currentPageSize, current: currentPage, showSizeChanger: true, total: total }}
columns={columns}
mobileColumnKeys={["name", "ownr_ph1", "ownr_ph2"]}
rowKey="id"

View File

@@ -8,14 +8,19 @@ import { pageLimit } from "../../utils/config";
export default function OwnersListContainer() {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search } = searchParams;
const { page, sortcolumn, sortorder, search, pageSize } = searchParams;
const currentPage = Number.parseInt(page || "1", 10);
const parsedPageSize = Number.parseInt(pageSize || String(pageLimit), 10);
const currentPageSize = Number.isNaN(parsedPageSize) ? pageLimit : parsedPageSize;
const { loading, error, data, refetch } = useQuery(QUERY_ALL_OWNERS_PAGINATED, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
offset: (currentPage - 1) * currentPageSize,
limit: currentPageSize,
order: [
{
[sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc"

View File

@@ -29,7 +29,10 @@ const mapStateToProps = createStructuredSelector({
export function PartsQueueListComponent({ bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
const { selected, sortcolumn, sortorder, statusFilters } = searchParams;
const { selected, sortcolumn, sortorder, statusFilters, page, pageSize } = searchParams;
const currentPage = Number.parseInt(page || "1", 10);
const parsedPageSize = Number.parseInt(pageSize || String(pageLimit), 10);
const currentPageSize = Number.isNaN(parsedPageSize) ? pageLimit : parsedPageSize;
const history = useNavigate();
const [filter, setFilter] = useLocalStorage("filter_parts_queue", null);
const [viewTimeStamp, setViewTimeStamp] = useLocalStorage("parts_queue_timestamps", false);
@@ -66,7 +69,11 @@ export function PartsQueueListComponent({ bodyshop }) {
: [];
const handleTableChange = (pagination, filters, sorter) => {
// searchParams.page = pagination.current;
const nextPageSize = pagination?.pageSize || currentPageSize;
const pageSizeChanged = nextPageSize !== currentPageSize;
searchParams.pageSize = nextPageSize;
searchParams.page = pageSizeChanged ? 1 : pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
@@ -315,9 +322,10 @@ export function PartsQueueListComponent({ bodyshop }) {
loading={loading}
pagination={{
placement: "top",
pageSize: pageLimit
// current: parseInt(page || 1),
// total: data && data.jobs_aggregate.aggregate.count,
pageSize: currentPageSize,
current: currentPage,
showSizeChanger: true,
total: jobs.length
}}
columns={columns}
mobileColumnKeys={["ro_number", "ownr_ln", "status", "vehicle", "partsstatus"]}

View File

@@ -431,6 +431,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
<Card
className={`react-trello-card ${cardSettings.kiosk ? "kiosk-mode" : ""}`}
size="small"
styles={{ header: { backgroundColor: "var(--card-bg-fallback)" } }}
style={{
backgroundColor: cardSettings?.cardcolor
? bgColor.fallback || `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a || 1})`

View File

@@ -28,11 +28,14 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
const { t } = useTranslation();
const calculateTotal = (items, key, subKey) => {
return items.reduce((acc, item) => acc + (item[key]?.aggregate?.sum?.[subKey] || 0), 0);
return items.reduce((acc, item) => acc + (item?.[key]?.aggregate?.sum?.[subKey] ?? 0), 0);
};
const calculateTotalAmount = (items, key) => {
return items.reduce((acc, item) => acc.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 }));
return items.reduce(
(acc, item) => acc.add(Dinero(item?.[key]?.totals?.subtotal ?? Dinero())),
Dinero({ amount: 0 })
);
};
const calculateReducerTotalAmount = (lanes, key) => {
@@ -67,58 +70,83 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
return value;
};
const filteredData = cardSettings.excludeSuspended === true ? data.filter((item) => item.suspended !== true) : data;
const filteredReducerData =
cardSettings.excludeSuspended === true
? {
...reducerData,
lanes: reducerData.lanes.map((lane) => ({
...lane,
cards: lane.cards.filter((card) => card.metadata.suspended !== true)
}))
}
: reducerData;
const totalHrs = cardSettings.totalHrs
? parseFloat((calculateTotal(data, "labhrs", "mod_lb_hrs") + calculateTotal(data, "larhrs", "mod_lb_hrs")).toFixed(2))
? parseFloat(
(
calculateTotal(filteredData, "labhrs", "mod_lb_hrs") + calculateTotal(filteredData, "larhrs", "mod_lb_hrs")
).toFixed(2)
)
: null;
const totalLAB = cardSettings.totalLAB
? parseFloat(calculateTotal(data, "labhrs", "mod_lb_hrs").toFixed(2))
? parseFloat(calculateTotal(filteredData, "labhrs", "mod_lb_hrs").toFixed(2))
: null;
const totalLAR = cardSettings.totalLAR
? parseFloat(calculateTotal(data, "larhrs", "mod_lb_hrs").toFixed(2))
? parseFloat(calculateTotal(filteredData, "larhrs", "mod_lb_hrs").toFixed(2))
: null;
const jobsInProduction = cardSettings.jobsInProduction ? data.length : null;
const jobsInProduction = cardSettings.jobsInProduction ? filteredData.length : null;
const totalAmountInProduction = cardSettings.totalAmountInProduction
? calculateTotalAmount(data, "job_totals").toFormat("$0,0.00")
? calculateTotalAmount(filteredData, "job_totals").toFormat("$0,0.00")
: null;
const totalAmountOnBoard = reducerData && cardSettings.totalAmountOnBoard
? calculateReducerTotalAmount(reducerData.lanes, "job_totals").toFormat("$0,0.00")
: null;
const totalAmountOnBoard =
filteredReducerData && cardSettings.totalAmountOnBoard
? calculateReducerTotalAmount(filteredReducerData.lanes, "job_totals").toFormat("$0,0.00")
: null;
const totalHrsOnBoard = reducerData && cardSettings.totalHrsOnBoard
? parseFloat((
calculateReducerTotal(reducerData.lanes, "labhrs", "mod_lb_hrs") +
calculateReducerTotal(reducerData.lanes, "larhrs", "mod_lb_hrs")
).toFixed(2))
: null;
const totalHrsOnBoard =
filteredReducerData && cardSettings.totalHrsOnBoard
? parseFloat(
(
calculateReducerTotal(filteredReducerData.lanes, "labhrs", "mod_lb_hrs") +
calculateReducerTotal(filteredReducerData.lanes, "larhrs", "mod_lb_hrs")
).toFixed(2)
)
: null;
const totalLABOnBoard = reducerData && cardSettings.totalLABOnBoard
? parseFloat(calculateReducerTotal(reducerData.lanes, "labhrs", "mod_lb_hrs").toFixed(2))
: null;
const totalLABOnBoard =
filteredReducerData && cardSettings.totalLABOnBoard
? parseFloat(calculateReducerTotal(filteredReducerData.lanes, "labhrs", "mod_lb_hrs").toFixed(2))
: null;
const totalLAROnBoard = reducerData && cardSettings.totalLAROnBoard
? parseFloat(calculateReducerTotal(reducerData.lanes, "larhrs", "mod_lb_hrs").toFixed(2))
: null;
const totalLAROnBoard =
filteredReducerData && cardSettings.totalLAROnBoard
? parseFloat(calculateReducerTotal(filteredReducerData.lanes, "larhrs", "mod_lb_hrs").toFixed(2))
: null;
const jobsOnBoard = reducerData && cardSettings.jobsOnBoard
? reducerData.lanes.reduce((acc, lane) => acc + lane.cards.length, 0)
: null;
const jobsOnBoard =
filteredReducerData && cardSettings.jobsOnBoard
? filteredReducerData.lanes.reduce((acc, lane) => acc + lane.cards.length, 0)
: null;
const tasksInProduction = cardSettings.tasksInProduction
? data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0)
? filteredData.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0)
: null;
const tasksOnBoard = reducerData && cardSettings.tasksOnBoard
? reducerData.lanes.reduce((acc, lane) => {
return (
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata.tasks_aggregate?.aggregate?.count || 0), 0)
);
}, 0)
: null;
const tasksOnBoard =
filteredReducerData && cardSettings.tasksOnBoard
? filteredReducerData.lanes.reduce((acc, lane) => {
return (
acc +
lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata.tasks_aggregate?.aggregate?.count || 0), 0)
);
}, 0)
: null;
const statistics = mergeStatistics(statisticsItems, [
{ id: 0, value: totalHrs, type: StatisticType.HOURS },

View File

@@ -14,7 +14,16 @@ const StatisticsSettings = ({ t, statisticsOrder, setStatisticsOrder, setHasChan
};
return (
<Card title={t("production.settings.statistics_title")}>
<Card
title={t("production.settings.statistics_title")}
extra={
<div style={{ display: "flex", alignItems: "center" }}>
<Form.Item name="excludeSuspended" valuePropName="checked" style={{ marginBottom: 0 }}>
<Checkbox>{t("production.settings.statistics.exclude_suspended")}</Checkbox>
</Form.Item>
</div>
}
>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable direction="grid" droppableId="statistics">
{(provided) => (

View File

@@ -91,7 +91,8 @@ const defaultKanbanSettings = {
subtotal: false,
statisticsOrder: statisticsItems.map((item) => item.id),
selectedMdInsCos: [],
selectedEstimators: []
selectedEstimators: [],
excludeSuspended: false
};
const defaultFilters = { search: "", employeeId: null, alert: false };

View File

@@ -11,12 +11,13 @@ import ResponsiveTable from "../responsive-table/responsive-table.component";
export default function VehiclesListComponent({ loading, vehicles, total, refetch, basePath = "/manage" }) {
const search = queryString.parse(useLocation().search);
const {
page
//sortcolumn, sortorder,
} = search;
const { page, pageSize } = search;
const history = useNavigate();
const currentPage = Number.parseInt(page || "1", 10);
const parsedPageSize = Number.parseInt(pageSize || String(pageLimit), 10);
const currentPageSize = Number.isNaN(parsedPageSize) ? pageLimit : parsedPageSize;
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" }
@@ -62,10 +63,14 @@ export default function VehiclesListComponent({ loading, vehicles, total, refetc
];
const handleTableChange = (pagination, filters, sorter) => {
const nextPageSize = pagination?.pageSize || currentPageSize;
const pageSizeChanged = nextPageSize !== currentPageSize;
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
const updatedSearch = {
...search,
page: pagination.current,
pageSize: nextPageSize,
page: pageSizeChanged ? 1 : pagination.current,
sortcolumn: sorter.columnKey,
sortorder: sorter.order
};
@@ -106,7 +111,7 @@ export default function VehiclesListComponent({ loading, vehicles, total, refetc
>
<ResponsiveTable
loading={loading}
pagination={{ placement: "top", pageSize: pageLimit, current: parseInt(page || 1), total: total }}
pagination={{ placement: "top", pageSize: currentPageSize, current: currentPage, showSizeChanger: true, total: total }}
columns={columns}
mobileColumnKeys={["v_vin", "description", "plate_no"]}
rowKey="id"

View File

@@ -16,14 +16,18 @@ const mapStateToProps = createStructuredSelector({
export function VehiclesListContainer({ isPartsEntry }) {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search } = searchParams;
const { page, sortcolumn, sortorder, search, pageSize } = searchParams;
const basePath = getPartsBasePath(isPartsEntry);
const currentPage = Number.parseInt(page || "1", 10);
const parsedPageSize = Number.parseInt(pageSize || String(pageLimit), 10);
const currentPageSize = Number.isNaN(parsedPageSize) ? pageLimit : parsedPageSize;
const { loading, error, data, refetch } = useQuery(QUERY_ALL_VEHICLES_PAGINATED, {
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
offset: (currentPage - 1) * currentPageSize,
limit: currentPageSize,
order: [
{
[sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc"

View File

@@ -18,16 +18,20 @@ const mapStateToProps = createStructuredSelector({});
export function ExportLogsPageComponent() {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search } = searchParams;
const { page, sortcolumn, sortorder, search, pageSize } = searchParams;
const history = useNavigate();
const currentPage = Number.parseInt(page || "1", 10);
const parsedPageSize = Number.parseInt(pageSize || String(pageLimit), 10);
const currentPageSize = Number.isNaN(parsedPageSize) ? pageLimit : parsedPageSize;
const { loading, error, data, refetch } = useQuery(QUERY_EXPORT_LOG_PAGINATED, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
offset: (currentPage - 1) * currentPageSize,
limit: currentPageSize,
order: [
{
...(sortcolumn === "ro_number"
@@ -61,7 +65,11 @@ export function ExportLogsPageComponent() {
if (error) return <AlertComponent title={error.message} type="error" />;
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
const nextPageSize = pagination?.pageSize || currentPageSize;
const pageSizeChanged = nextPageSize !== currentPageSize;
searchParams.pageSize = nextPageSize;
searchParams.page = pageSizeChanged ? 1 : pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
if (filters.status) {
@@ -191,8 +199,9 @@ export function ExportLogsPageComponent() {
loading={loading}
pagination={{
placement: "top",
pageSize: pageLimit,
current: parseInt(page || 1, 10),
pageSize: currentPageSize,
current: currentPage,
showSizeChanger: true,
total: data && data.search_exportlog_aggregate.aggregate.count
}}
columns={columns}

View File

@@ -3266,6 +3266,7 @@
"information": "Information",
"layout": "Layout",
"statistics": {
"exclude_suspended": "Exclude Suspended Jobs",
"jobs_in_production": "Jobs in Production",
"tasks_in_production": "Tasks in Production",
"tasks_in_view": "Tasks in View",

View File

@@ -3260,6 +3260,7 @@
"information": "",
"layout": "",
"statistics": {
"exclude_suspended": "",
"jobs_in_production": "",
"tasks_in_production": "",
"tasks_in_view": "",

View File

@@ -3260,6 +3260,7 @@
"information": "",
"layout": "",
"statistics": {
"exclude_suspended": "",
"jobs_in_production": "",
"tasks_in_production": "",
"tasks_in_view": "",