More tables (#5999)

* Move CompanyTable

* Add "Contacts" table to company detail page

* Typo fix

* Remove 'notes' column

* Add preFormWarning and preFormSuccess to API form

- Refactor existing deletion forms

* Adds "address" table for company view

* Prevent wrapping on inline model rendereing

* Refactor new tables

* Fix unused imports
This commit is contained in:
Oliver 2023-11-29 14:05:22 +11:00 committed by GitHub
parent f0f72a8742
commit e4c5bfc2fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 415 additions and 45 deletions

View File

@ -70,6 +70,8 @@ export interface ApiFormProps {
fetchInitialData?: boolean;
ignorePermissionCheck?: boolean;
preFormContent?: JSX.Element;
preFormWarning?: string;
preFormSuccess?: string;
postFormContent?: JSX.Element;
successMessage?: string;
onFormSuccess?: (data: any) => void;
@ -287,7 +289,10 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
// Optionally show a success message
if (props.successMessage) {
notifications.hide('form-success');
notifications.show({
id: 'form-success',
title: t`Success`,
message: props.successMessage,
color: 'green'
@ -371,6 +376,16 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
</Alert>
)}
{props.preFormContent}
{props.preFormSuccess && (
<Alert color="green" radius="sm">
{props.preFormSuccess}
</Alert>
)}
{props.preFormWarning && (
<Alert color="orange" radius="sm">
{props.preFormWarning}
</Alert>
)}
<Stack spacing="xs">
{Object.entries(props.fields ?? {}).map(([fieldName, field]) => (
<ApiFormField

View File

@ -1,15 +1,19 @@
import { t } from '@lingui/macro';
import { Badge } from '@mantine/core';
export function YesNoButton({ value }: { value: boolean }) {
import { isTrue } from '../../functions/conversion';
export function YesNoButton({ value }: { value: any }) {
const v = isTrue(value);
return (
<Badge
color={value ? 'lime.5' : 'red.6'}
color={v ? 'lime.5' : 'red.6'}
variant="filled"
radius="lg"
size="sm"
>
{value ? t`Yes` : t`No`}
{v ? t`Yes` : t`No`}
</Badge>
);
}

View File

@ -110,8 +110,8 @@ export function RenderInlineModel({
// TODO: Handle URL
return (
<Group spacing="xs" position="apart">
<Group spacing="xs" position="left">
<Group spacing="xs" position="apart" noWrap={true}>
<Group spacing="xs" position="left" noWrap={true}>
{image && Thumbnail({ src: image, size: 18 })}
<Text size="sm">{primary}</Text>
{secondary && <Text size="xs">{secondary}</Text>}

View File

@ -306,9 +306,7 @@ export function BomTable({
title: t`Delete Bom Item`,
successMessage: t`Bom item deleted`,
onFormSuccess: table.refreshTable,
preFormContent: (
<Text>{t`Are you sure you want to remove this BOM item?`}</Text>
)
preFormWarning: t`Are you sure you want to remove this BOM item?`
});
}
})

View File

@ -0,0 +1,196 @@
import { t } from '@lingui/macro';
import { useCallback, useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
import { UserRoles } from '../../../enums/Roles';
import { addressFields } from '../../../forms/CompanyForms';
import {
openCreateApiForm,
openDeleteApiForm,
openEditApiForm
} from '../../../functions/forms';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { YesNoButton } from '../../items/YesNoButton';
import { TableColumn } from '../Column';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions';
export function AddressTable({
companyId,
params
}: {
companyId: number;
params?: any;
}) {
const user = useUserState();
const table = useTable('address');
const columns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'title',
title: t`Title`,
sortable: true,
switchable: false
},
{
accessor: 'primary',
title: t`Primary`,
switchable: false,
sortable: false,
render: (record: any) => YesNoButton({ value: record.primary })
},
{
accessor: 'address',
title: t`Address`,
sortable: false,
switchable: false,
render: (record: any) => {
let address = '';
if (record?.line1) {
address += record.line1;
}
if (record?.line2) {
address += ' ' + record.line2;
}
return address.trim();
}
},
{
accessor: 'postal_code',
title: t`Postal Code`,
sortable: false,
switchable: true
},
{
accessor: 'postal_city',
title: t`City`,
sortable: false,
switchable: true
},
{
accessor: 'province',
title: t`State / Province`,
sortable: false,
switchable: true
},
{
accessor: 'country',
title: t`Country`,
sortable: false,
switchable: true
},
{
accessor: 'shipping_notes',
title: t`Courier Notes`,
sortable: false,
switchable: true
},
{
accessor: 'internal_shipping_notes',
title: t`Internal Notes`,
sortable: false,
switchable: true
},
{
accessor: 'link',
title: t`Link`,
sortable: false,
switchable: true
}
];
}, []);
const rowActions = useCallback(
(record: any) => {
let can_edit =
user.hasChangeRole(UserRoles.purchase_order) ||
user.hasChangeRole(UserRoles.sales_order);
let can_delete =
user.hasDeleteRole(UserRoles.purchase_order) ||
user.hasDeleteRole(UserRoles.sales_order);
return [
RowEditAction({
hidden: !can_edit,
onClick: () => {
openEditApiForm({
url: ApiPaths.address_list,
pk: record.pk,
title: t`Edit Address`,
fields: addressFields(),
successMessage: t`Address updated`,
onFormSuccess: table.refreshTable
});
}
}),
RowDeleteAction({
hidden: !can_delete,
onClick: () => {
openDeleteApiForm({
url: ApiPaths.address_list,
pk: record.pk,
title: t`Delete Address`,
successMessage: t`Address deleted`,
onFormSuccess: table.refreshTable,
preFormWarning: t`Are you sure you want to delete this address?`
});
}
})
];
},
[user]
);
const addAddress = useCallback(() => {
let fields = addressFields();
fields['company'].value = companyId;
openCreateApiForm({
url: ApiPaths.address_list,
title: t`Add Address`,
fields: fields,
successMessage: t`Address created`,
onFormSuccess: table.refreshTable
});
}, [companyId]);
const tableActions = useMemo(() => {
let can_add =
user.hasChangeRole(UserRoles.purchase_order) ||
user.hasChangeRole(UserRoles.sales_order);
return [
<AddItemButton
tooltip={t`Add Address`}
onClick={addAddress}
disabled={!can_add}
/>
];
}, [user]);
return (
<InvenTreeTable
url={apiUrl(ApiPaths.address_list)}
tableState={table}
columns={columns}
props={{
rowActions: rowActions,
customActionGroups: tableActions,
params: {
...params,
company: companyId
}
}}
/>
);
}

View File

@ -0,0 +1,144 @@
import { t } from '@lingui/macro';
import { useCallback, useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
import { UserRoles } from '../../../enums/Roles';
import { contactFields } from '../../../forms/CompanyForms';
import {
openCreateApiForm,
openDeleteApiForm,
openEditApiForm
} from '../../../functions/forms';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { TableColumn } from '../Column';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions';
export function ContactTable({
companyId,
params
}: {
companyId: number;
params?: any;
}) {
const user = useUserState();
const table = useTable('contact');
const columns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'name',
title: t`Name`,
sortable: true,
switchable: false
},
{
accessor: 'phone',
title: t`Phone`,
switchable: true,
sortable: false
},
{
accessor: 'email',
title: t`Email`,
switchable: true,
sortable: false
},
{
accessor: 'role',
title: t`Role`,
switchable: true,
sortable: false
}
];
}, []);
const rowActions = useCallback(
(record: any) => {
let can_edit =
user.hasChangeRole(UserRoles.purchase_order) ||
user.hasChangeRole(UserRoles.sales_order);
let can_delete =
user.hasDeleteRole(UserRoles.purchase_order) ||
user.hasDeleteRole(UserRoles.sales_order);
return [
RowEditAction({
hidden: !can_edit,
onClick: () => {
openEditApiForm({
url: ApiPaths.contact_list,
pk: record.pk,
title: t`Edit Contact`,
fields: contactFields(),
successMessage: t`Contact updated`,
onFormSuccess: table.refreshTable
});
}
}),
RowDeleteAction({
hidden: !can_delete,
onClick: () => {
openDeleteApiForm({
url: ApiPaths.contact_list,
pk: record.pk,
title: t`Delete Contact`,
successMessage: t`Contact deleted`,
onFormSuccess: table.refreshTable,
preFormWarning: t`Are you sure you want to delete this contact?`
});
}
})
];
},
[user]
);
const addContact = useCallback(() => {
var fields = contactFields();
fields['company'].value = companyId;
openCreateApiForm({
url: ApiPaths.contact_list,
title: t`Create Contact`,
fields: fields,
successMessage: t`Contact created`,
onFormSuccess: table.refreshTable
});
}, [companyId]);
const tableActions = useMemo(() => {
let can_add =
user.hasAddRole(UserRoles.purchase_order) ||
user.hasAddRole(UserRoles.sales_order);
return [
<AddItemButton
tooltip={t`Add contact`}
onClick={addContact}
disabled={!can_add}
/>
];
}, [user]);
return (
<InvenTreeTable
url={apiUrl(ApiPaths.contact_list)}
tableState={table}
columns={columns}
props={{
rowActions: rowActions,
customActionGroups: tableActions,
params: {
...params,
company: companyId
}
}}
/>
);
}

View File

@ -140,9 +140,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
title: t`Delete Part Parameter`,
successMessage: t`Part parameter deleted`,
onFormSuccess: table.refreshTable,
preFormContent: (
<Text>{t`Are you sure you want to remove this parameter?`}</Text>
)
preFormWarning: t`Are you sure you want to remove this parameter?`
});
}
})

View File

@ -1,5 +1,4 @@
import { t } from '@lingui/macro';
import { Text } from '@mantine/core';
import { useCallback, useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
@ -39,7 +38,7 @@ export function PartParameterTemplateTable() {
{
accessor: 'description',
title: t`Description`,
sortbale: false
sortable: false
},
{
accessor: 'checkbox',
@ -78,7 +77,7 @@ export function PartParameterTemplateTable() {
title: t`Delete Parameter Template`,
successMessage: t`Parameter template deleted`,
onFormSuccess: table.refreshTable,
preFormContent: <Text>{t`Remove parameter template`}</Text>
preFormWarning: t`Are you sure you want to remove this parameter template?`
});
}
})

View File

@ -1,7 +1,7 @@
import { PartListTable } from './PartTable';
/**
* Display variant parts for thespecified parent part
* Display variant parts for the specified parent part
*/
export function PartVariantTable({ partId }: { partId: string }) {
return (

View File

@ -112,9 +112,7 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode {
pk: record.pk,
title: t`Delete Related Part`,
successMessage: t`Related part deleted`,
preFormContent: (
<Text>{t`Are you sure you want to remove this relationship?`}</Text>
),
preFormWarning: t`Are you sure you want to remove this relationship?`,
onFormSuccess: table.refreshTable
});
}

View File

@ -210,9 +210,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
title: t`Delete Supplier Part`,
successMessage: t`Supplier part deleted`,
onFormSuccess: table.refreshTable,
preFormContent: (
<Text>{t`Are you sure you want to remove this supplier part?`}</Text>
)
preFormWarning: t`Are you sure you want to remove this supplier part?`
});
}
})

View File

@ -1,5 +1,4 @@
import { t } from '@lingui/macro';
import { Text } from '@mantine/core';
import { useCallback, useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
@ -77,9 +76,7 @@ export function CustomUnitsTable() {
title: t`Delete custom unit`,
successMessage: t`Custom unit deleted`,
onFormSuccess: table.refreshTable,
preFormContent: (
<Text>{t`Are you sure you want to remove this custom unit?`}</Text>
)
preFormWarning: t`Are you sure you want to remove this custom unit?`
});
}
})

View File

@ -1,5 +1,4 @@
import { t } from '@lingui/macro';
import { Text } from '@mantine/core';
import { useCallback, useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
@ -55,9 +54,7 @@ export function GroupTable() {
title: t`Delete group`,
successMessage: t`Group deleted`,
onFormSuccess: table.refreshTable,
preFormContent: (
<Text>{t`Are you sure you want to delete this group?`}</Text>
)
preFormWarning: t`Are you sure you want to delete this group?`
});
}
})

View File

@ -1,5 +1,4 @@
import { t } from '@lingui/macro';
import { Text } from '@mantine/core';
import { useCallback, useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
@ -67,9 +66,7 @@ export function ProjectCodeTable() {
title: t`Delete project code`,
successMessage: t`Project code deleted`,
onFormSuccess: table.refreshTable,
preFormContent: (
<Text>{t`Are you sure you want to remove this project code?`}</Text>
)
preFormWarning: t`Are you sure you want to remove this project code?`
});
}
})

View File

@ -1,5 +1,4 @@
import { t } from '@lingui/macro';
import { Text } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { useCallback, useMemo, useState } from 'react';
@ -116,9 +115,7 @@ export function UserTable() {
title: t`Delete user`,
successMessage: t`User deleted`,
onFormSuccess: table.refreshTable,
preFormContent: (
<Text>{t`Are you sure you want to delete this user?`}</Text>
)
preFormWarning: t`Are you sure you want to delete this user?`
});
}
})

View File

@ -1,5 +1,4 @@
import { t } from '@lingui/macro';
import { Text } from '@mantine/core';
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
import { ApiPaths } from '../enums/ApiEndpoints';
@ -126,8 +125,6 @@ export function deleteAttachment({
successMessage: t`Attachment deleted`,
onFormSuccess: callback,
fields: {},
preFormContent: (
<Text>{t`Are you sure you want to delete this attachment?`}</Text>
)
preFormWarning: t`Are you sure you want to delete this attachment?`
});
}

View File

@ -125,3 +125,34 @@ export function editCompany({
onFormSuccess: callback
});
}
export function contactFields(): ApiFormFieldSet {
return {
company: {
hidden: true
},
name: {},
phone: {},
email: {},
role: {}
};
}
export function addressFields(): ApiFormFieldSet {
return {
company: {
hidden: true
},
title: {},
primary: {},
line1: {},
line2: {},
postal_code: {},
postal_city: {},
province: {},
country: {},
shipping_notes: {},
internal_shipping_notes: {},
link: {}
};
}

View File

@ -27,6 +27,8 @@ import { Breadcrumb } from '../../components/nav/BreadcrumbList';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup } from '../../components/nav/PanelGroup';
import { PanelType } from '../../components/nav/PanelGroup';
import { AddressTable } from '../../components/tables/company/AddressTable';
import { ContactTable } from '../../components/tables/company/ContactTable';
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
import { PurchaseOrderTable } from '../../components/tables/purchasing/PurchaseOrderTable';
import { ReturnOrderTable } from '../../components/tables/sales/ReturnOrderTable';
@ -128,12 +130,14 @@ export default function CompanyDetail(props: CompanyDetailProps) {
{
name: 'contacts',
label: t`Contacts`,
icon: <IconUsersGroup />
icon: <IconUsersGroup />,
content: company?.pk && <ContactTable companyId={company.pk} />
},
{
name: 'addresses',
label: t`Addresses`,
icon: <IconMap2 />
icon: <IconMap2 />,
content: company?.pk && <AddressTable companyId={company.pk} />
},
{
name: 'attachments',

View File

@ -9,7 +9,7 @@ import { useMemo } from 'react';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup } from '../../components/nav/PanelGroup';
import { CompanyTable } from '../../components/tables/general/CompanyTable';
import { CompanyTable } from '../../components/tables/company/CompanyTable';
import { PurchaseOrderTable } from '../../components/tables/purchasing/PurchaseOrderTable';
export default function PurchasingIndex() {

View File

@ -9,7 +9,7 @@ import { useMemo } from 'react';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup } from '../../components/nav/PanelGroup';
import { CompanyTable } from '../../components/tables/general/CompanyTable';
import { CompanyTable } from '../../components/tables/company/CompanyTable';
import { ReturnOrderTable } from '../../components/tables/sales/ReturnOrderTable';
import { SalesOrderTable } from '../../components/tables/sales/SalesOrderTable';