Merged in release/2025-09-26 (pull request #2562)

Release/2025 09 26  into master-AIO IO-3365, IO-3366, IO-3369
This commit is contained in:
Dave Richer
2025-09-11 20:43:43 +00:00
8 changed files with 82 additions and 49 deletions

View File

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

View File

@@ -109,6 +109,13 @@ 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>
}, },
{ {

View File

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

View File

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

View File

@@ -233,7 +233,7 @@ export function PartsQueueListComponent({ bodyshop }) {
title: t("jobs.fields.partsstatus"), title: t("jobs.fields.partsstatus"),
dataIndex: "partsstatus", dataIndex: "partsstatus",
key: "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"), title: t("jobs.fields.comment"),

View File

@@ -1183,7 +1183,17 @@ export function ShopInfoGeneral({ form, bodyshop }) {
{fields.map((field, index) => ( {fields.map((field, index) => (
<Form.Item key={field.key}> <Form.Item key={field.key}>
<LayoutFormRow noDivider> <LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}> <Form.Item
label={t("general.labels.label")}
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -1294,7 +1304,17 @@ export function ShopInfoGeneral({ form, bodyshop }) {
{fields.map((field, index) => ( {fields.map((field, index) => (
<Form.Item key={field.key}> <Form.Item key={field.key}>
<LayoutFormRow noDivider> <LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}> <Form.Item
label={t("general.labels.label")}
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -1483,15 +1503,31 @@ export function ShopInfoGeneral({ form, bodyshop }) {
{fields.map((field, index) => ( {fields.map((field, index) => (
<Form.Item key={field.key}> <Form.Item key={field.key}>
<LayoutFormRow noDivider> <LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}> <Form.Item
label={t("general.labels.label")}
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.labels.md_to_emails_emails")} label={t("bodyshop.labels.md_to_emails_emails")}
key={`${index}emails`} key={`${index}emails`}
name={[field.name, "emails"]} name={[field.name, "emails"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
> >
<Select mode="tags" tokenSeparators={[",", ";"]} /> <FormItemEmail email={form.getFieldValue([field.name, "emails"])} />
</Form.Item> </Form.Item>
<Space> <Space>

View File

@@ -147,7 +147,7 @@ export function SimplifiedPartsJobsListComponent({
title: t("jobs.fields.partsstatus"), title: t("jobs.fields.partsstatus"),
dataIndex: "partsstatus", dataIndex: "partsstatus",
key: "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"), title: t("jobs.fields.comment"),

View File

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