mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Parameter table updates (#5892)
* Add some helper functions for role permission checks on frontend * Update PartParameterTable - Use new user role checks * Fix up more table action permissions * Add table for part parameter template * Add edit and delete actions to new table * Add ability to create new template from table * Fix for BomTable * Refactor RowActions - Require icon - Horizontal menu popout * Refactor row actions for existing tables * Fix BomTable * Bug fix for notifications table * Fix display of TableHoverCard * Disable PanelGroup tooltip when expanded * Fix unused variables
This commit is contained in:
parent
0597ea9216
commit
5abe0eaaad
@ -95,6 +95,7 @@ export function PanelGroup({
|
||||
<Tooltip
|
||||
label={panel.label}
|
||||
key={`panel-tab-tooltip-${panel.name}`}
|
||||
disabled={expanded}
|
||||
>
|
||||
<Tabs.Tab
|
||||
key={`panel-tab-${panel.name}`}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { ActionIcon, Tooltip } from '@mantine/core';
|
||||
import { Menu, Text } from '@mantine/core';
|
||||
import { IconDots } from '@tabler/icons-react';
|
||||
import { ActionIcon, Group, Tooltip } from '@mantine/core';
|
||||
import { Menu } from '@mantine/core';
|
||||
import { IconCopy, IconDots, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
|
||||
import { notYetImplemented } from '../../functions/notifications';
|
||||
@ -10,8 +10,8 @@ import { notYetImplemented } from '../../functions/notifications';
|
||||
export type RowAction = {
|
||||
title: string;
|
||||
color?: string;
|
||||
icon: ReactNode;
|
||||
onClick?: () => void;
|
||||
tooltip?: string;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
@ -27,6 +27,7 @@ export function RowDuplicateAction({
|
||||
title: t`Duplicate`,
|
||||
color: 'green',
|
||||
onClick: onClick,
|
||||
icon: <IconCopy />,
|
||||
hidden: hidden
|
||||
};
|
||||
}
|
||||
@ -43,6 +44,7 @@ export function RowEditAction({
|
||||
title: t`Edit`,
|
||||
color: 'blue',
|
||||
onClick: onClick,
|
||||
icon: <IconEdit />,
|
||||
hidden: hidden
|
||||
};
|
||||
}
|
||||
@ -59,6 +61,7 @@ export function RowDeleteAction({
|
||||
title: t`Delete`,
|
||||
color: 'red',
|
||||
onClick: onClick,
|
||||
icon: <IconTrash />,
|
||||
hidden: hidden
|
||||
};
|
||||
}
|
||||
@ -82,7 +85,7 @@ export function RowActions({
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
event?.nativeEvent?.stopImmediatePropagation();
|
||||
setOpened(true);
|
||||
setOpened(!opened);
|
||||
}
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
@ -91,11 +94,45 @@ export function RowActions({
|
||||
return actions.filter((action) => !action.hidden);
|
||||
}, [actions]);
|
||||
|
||||
// Render a single action icon
|
||||
function RowActionIcon(action: RowAction) {
|
||||
return (
|
||||
<Tooltip withinPortal={true} label={action.title} key={action.title}>
|
||||
<ActionIcon
|
||||
color={action.color}
|
||||
size={20}
|
||||
onClick={(event) => {
|
||||
// Prevent clicking on the action from selecting the row itself
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
event?.nativeEvent?.stopImmediatePropagation();
|
||||
|
||||
if (action.onClick) {
|
||||
action.onClick();
|
||||
} else {
|
||||
notYetImplemented();
|
||||
}
|
||||
|
||||
setOpened(false);
|
||||
}}
|
||||
>
|
||||
{action.icon}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
// If only a single action is available, display that
|
||||
if (visibleActions.length == 1) {
|
||||
return <RowActionIcon {...visibleActions[0]} />;
|
||||
}
|
||||
|
||||
return (
|
||||
visibleActions.length > 0 && (
|
||||
<Menu
|
||||
withinPortal={true}
|
||||
disabled={disabled}
|
||||
position="left"
|
||||
opened={opened}
|
||||
onChange={setOpened}
|
||||
>
|
||||
@ -112,28 +149,11 @@ export function RowActions({
|
||||
</Tooltip>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>{title || t`Actions`}</Menu.Label>
|
||||
{visibleActions.map((action, idx) => (
|
||||
<Menu.Item
|
||||
key={idx}
|
||||
onClick={(event) => {
|
||||
// Prevent clicking on the action from selecting the row itself
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
event?.nativeEvent?.stopImmediatePropagation();
|
||||
if (action.onClick) {
|
||||
action.onClick();
|
||||
} else {
|
||||
notYetImplemented();
|
||||
}
|
||||
}}
|
||||
title={action.tooltip || action.title}
|
||||
>
|
||||
<Text size="xs" color={action.color}>
|
||||
{action.title}
|
||||
</Text>
|
||||
</Menu.Item>
|
||||
))}
|
||||
<Group position="right" spacing="xs" p={8}>
|
||||
{visibleActions.map((action, _idx) => (
|
||||
<RowActionIcon {...action} />
|
||||
))}
|
||||
</Group>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
)
|
||||
|
@ -27,7 +27,7 @@ export function TableHoverCard({
|
||||
}
|
||||
|
||||
return (
|
||||
<HoverCard>
|
||||
<HoverCard withinPortal={true}>
|
||||
<HoverCard.Target>
|
||||
<Group spacing="xs" position="apart" noWrap={true}>
|
||||
{value}
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
import {
|
||||
IconArrowRight,
|
||||
IconCircleCheck,
|
||||
IconSwitch3
|
||||
} from '@tabler/icons-react';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@ -7,7 +12,7 @@ import { bomItemFields } from '../../../forms/BomForms';
|
||||
import { openDeleteApiForm, openEditApiForm } from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { YesNoButton } from '../../items/YesNoButton';
|
||||
import { TableColumn } from '../Column';
|
||||
@ -245,33 +250,34 @@ export function BomTable({
|
||||
return [
|
||||
{
|
||||
title: t`View BOM`,
|
||||
onClick: () => navigate(`/part/${record.part}/`)
|
||||
onClick: () => navigate(`/part/${record.part}/`),
|
||||
icon: <IconArrowRight />
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// TODO: Check user permissions here,
|
||||
// TODO: to determine which actions are allowed
|
||||
|
||||
let actions: RowAction[] = [];
|
||||
|
||||
// TODO: Enable BomItem validation
|
||||
actions.push({
|
||||
title: t`Validate`,
|
||||
hidden: record.validated || !user.checkUserRole('part', 'change')
|
||||
title: t`Validate BOM line`,
|
||||
color: 'green',
|
||||
hidden: record.validated || !user.hasChangeRole(UserRoles.part),
|
||||
icon: <IconCircleCheck />
|
||||
});
|
||||
|
||||
// TODO: Enable editing of substitutes
|
||||
actions.push({
|
||||
title: t`Substitutes`,
|
||||
title: t`Edit Substitutes`,
|
||||
color: 'blue',
|
||||
hidden: !user.checkUserRole('part', 'change')
|
||||
hidden: !user.hasChangeRole(UserRoles.part),
|
||||
icon: <IconSwitch3 />
|
||||
});
|
||||
|
||||
// Action on edit
|
||||
actions.push(
|
||||
RowEditAction({
|
||||
hidden: !user.checkUserRole('part', 'change'),
|
||||
hidden: !user.hasChangeRole(UserRoles.part),
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
url: ApiPaths.bom_list,
|
||||
@ -288,7 +294,7 @@ export function BomTable({
|
||||
// Action on delete
|
||||
actions.push(
|
||||
RowDeleteAction({
|
||||
hidden: !user.checkUserRole('part', 'delete'),
|
||||
hidden: !user.hasDeleteRole(UserRoles.part),
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.bom_list,
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { YesNoButton } from '../../items/YesNoButton';
|
||||
@ -22,6 +23,8 @@ import { RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
export function PartParameterTable({ partId }: { partId: any }) {
|
||||
const { tableKey, refreshTable } = useTableRefresh('part-parameters');
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -94,7 +97,6 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
||||
}, [partId]);
|
||||
|
||||
// Callback for row actions
|
||||
// TODO: Adjust based on user permissions
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
// Actions not allowed for "variant" rows
|
||||
@ -106,6 +108,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
||||
|
||||
actions.push(
|
||||
RowEditAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.part),
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
url: ApiPaths.part_parameter_list,
|
||||
@ -127,6 +130,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
||||
|
||||
actions.push(
|
||||
RowDeleteAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.part),
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.part_parameter_list,
|
||||
@ -144,7 +148,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
||||
|
||||
return actions;
|
||||
},
|
||||
[partId]
|
||||
[partId, user]
|
||||
);
|
||||
|
||||
const addParameter = useCallback(() => {
|
||||
|
@ -0,0 +1,121 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { partParameterTemplateFields } from '../../../forms/PartForms';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
openDeleteApiForm,
|
||||
openEditApiForm
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
|
||||
export function PartParameterTemplateTable() {
|
||||
const { tableKey, refreshTable } = useTableRefresh(
|
||||
'part-parameter-templates'
|
||||
);
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
accessor: 'name',
|
||||
title: t`Name`,
|
||||
sortable: true,
|
||||
switchable: false
|
||||
},
|
||||
{
|
||||
accessor: 'units',
|
||||
title: t`Units`,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'description',
|
||||
title: t`Description`,
|
||||
sortbale: false
|
||||
},
|
||||
{
|
||||
accessor: 'checkbox',
|
||||
title: t`Checkbox`
|
||||
},
|
||||
{
|
||||
accessor: 'choices',
|
||||
title: t`Choices`
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
// Callback for row actions
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
return [
|
||||
RowEditAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.part),
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
url: ApiPaths.part_parameter_template_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit Parameter Template`,
|
||||
fields: partParameterTemplateFields(),
|
||||
successMessage: t`Parameter template updated`,
|
||||
onFormSuccess: refreshTable
|
||||
});
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.part),
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.part_parameter_template_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete Parameter Template`,
|
||||
successMessage: t`Parameter template deleted`,
|
||||
onFormSuccess: refreshTable,
|
||||
preFormContent: <Text>{t`Remove parameter template`}</Text>
|
||||
});
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
const addParameterTemplate = useCallback(() => {
|
||||
openCreateApiForm({
|
||||
url: ApiPaths.part_parameter_template_list,
|
||||
title: t`Create Parameter Template`,
|
||||
fields: partParameterTemplateFields(),
|
||||
successMessage: t`Parameter template created`,
|
||||
onFormSuccess: refreshTable
|
||||
});
|
||||
}, []);
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
return [
|
||||
<AddItemButton
|
||||
tooltip={t`Add parameter template`}
|
||||
onClick={addParameterTemplate}
|
||||
disabled={!user.hasAddRole(UserRoles.part)}
|
||||
/>
|
||||
];
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiPaths.part_parameter_template_list)}
|
||||
tableKey={tableKey}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
rowActions: rowActions,
|
||||
customActionGroups: tableActions
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@ -7,6 +7,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { openCreateApiForm, openDeleteApiForm } from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
@ -20,6 +21,8 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
// Construct table columns for this table
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
function getPart(record: any) {
|
||||
@ -96,24 +99,28 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode {
|
||||
|
||||
// Generate row actions
|
||||
// TODO: Hide if user does not have permission to edit parts
|
||||
const rowActions = useCallback((record: any) => {
|
||||
return [
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.related_part_list,
|
||||
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>
|
||||
),
|
||||
onFormSuccess: refreshTable
|
||||
});
|
||||
}
|
||||
})
|
||||
];
|
||||
}, []);
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
return [
|
||||
RowDeleteAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.part),
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.related_part_list,
|
||||
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>
|
||||
),
|
||||
onFormSuccess: refreshTable
|
||||
});
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
return (
|
||||
<InvenTreeTable
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { notYetImplemented } from '../../../functions/notifications';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { TableColumn } from '../Column';
|
||||
@ -102,16 +101,13 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) {
|
||||
actions.push({
|
||||
title: t`Deactivate`,
|
||||
color: 'red',
|
||||
onClick: () => {
|
||||
notYetImplemented();
|
||||
}
|
||||
icon: <IconCircleX />
|
||||
});
|
||||
} else {
|
||||
actions.push({
|
||||
title: t`Activate`,
|
||||
onClick: () => {
|
||||
notYetImplemented();
|
||||
}
|
||||
color: 'green',
|
||||
icon: <IconCircleCheck />
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { purchaseOrderLineItemFields } from '../../../forms/PurchaseOrderForms';
|
||||
import { openCreateApiForm, openEditApiForm } from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { ActionButton } from '../../buttons/ActionButton';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
@ -45,18 +45,17 @@ export function PurchaseOrderLineItemTable({
|
||||
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
// TODO: Hide certain actions if user does not have required permissions
|
||||
|
||||
let received = (record?.received ?? 0) >= (record?.quantity ?? 0);
|
||||
|
||||
return [
|
||||
{
|
||||
hidden: received,
|
||||
title: t`Receive`,
|
||||
tooltip: t`Receive line item`,
|
||||
title: t`Receive line item`,
|
||||
icon: <IconSquareArrowRight />,
|
||||
color: 'green'
|
||||
},
|
||||
RowEditAction({
|
||||
hidden: !user.hasAddRole(UserRoles.purchase_order),
|
||||
onClick: () => {
|
||||
let supplier = record?.supplier_part_detail?.supplier;
|
||||
|
||||
@ -78,8 +77,12 @@ export function PurchaseOrderLineItemTable({
|
||||
});
|
||||
}
|
||||
}),
|
||||
RowDuplicateAction({}),
|
||||
RowDeleteAction({})
|
||||
RowDuplicateAction({
|
||||
hidden: !user.hasAddRole(UserRoles.purchase_order)
|
||||
}),
|
||||
RowDeleteAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.purchase_order)
|
||||
})
|
||||
];
|
||||
},
|
||||
[orderId, user]
|
||||
@ -228,7 +231,7 @@ export function PurchaseOrderLineItemTable({
|
||||
<AddItemButton
|
||||
tooltip={t`Add line item`}
|
||||
onClick={addLine}
|
||||
hidden={!user?.checkUserRole('purchaseorder', 'add')}
|
||||
hidden={!user?.hasAddRole(UserRoles.purchase_order)}
|
||||
/>,
|
||||
<ActionButton text={t`Receive items`} icon={<IconSquareArrowRight />} />
|
||||
];
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { TableColumn } from '../Column';
|
||||
@ -180,9 +180,9 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
||||
// Row action callback
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
// TODO: Adjust actions based on user permissions
|
||||
return [
|
||||
RowEditAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.purchase_order),
|
||||
onClick: () => {
|
||||
record.pk &&
|
||||
openEditApiForm({
|
||||
@ -196,6 +196,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.purchase_order),
|
||||
onClick: () => {
|
||||
record.pk &&
|
||||
openDeleteApiForm({
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
@ -20,6 +21,8 @@ import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
export function CustomUnitsTable() {
|
||||
const { tableKey, refreshTable } = useTableRefresh('custom-units');
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const columns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -43,40 +46,45 @@ export function CustomUnitsTable() {
|
||||
];
|
||||
}, []);
|
||||
|
||||
const rowActions = useCallback((record: any): RowAction[] => {
|
||||
return [
|
||||
RowEditAction({
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
url: ApiPaths.custom_unit_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit custom unit`,
|
||||
fields: {
|
||||
name: {},
|
||||
definition: {},
|
||||
symbol: {}
|
||||
},
|
||||
onFormSuccess: refreshTable,
|
||||
successMessage: t`Custom unit updated`
|
||||
});
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.custom_unit_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete custom unit`,
|
||||
successMessage: t`Custom unit deleted`,
|
||||
onFormSuccess: refreshTable,
|
||||
preFormContent: (
|
||||
<Text>{t`Are you sure you want to remove this custom unit?`}</Text>
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
];
|
||||
}, []);
|
||||
const rowActions = useCallback(
|
||||
(record: any): RowAction[] => {
|
||||
return [
|
||||
RowEditAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.admin),
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
url: ApiPaths.custom_unit_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit custom unit`,
|
||||
fields: {
|
||||
name: {},
|
||||
definition: {},
|
||||
symbol: {}
|
||||
},
|
||||
onFormSuccess: refreshTable,
|
||||
successMessage: t`Custom unit updated`
|
||||
});
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.admin),
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.custom_unit_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete custom unit`,
|
||||
successMessage: t`Custom unit deleted`,
|
||||
onFormSuccess: refreshTable,
|
||||
preFormContent: (
|
||||
<Text>{t`Are you sure you want to remove this custom unit?`}</Text>
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
const addCustomUnit = useCallback(() => {
|
||||
openCreateApiForm({
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { DescriptionColumn } from '../ColumnRenderers';
|
||||
@ -21,6 +22,8 @@ import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
export function ProjectCodeTable() {
|
||||
const { tableKey, refreshTable } = useTableRefresh('project-code');
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const columns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -32,39 +35,44 @@ export function ProjectCodeTable() {
|
||||
];
|
||||
}, []);
|
||||
|
||||
const rowActions = useCallback((record: any): RowAction[] => {
|
||||
return [
|
||||
RowEditAction({
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
url: ApiPaths.project_code_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit project code`,
|
||||
fields: {
|
||||
code: {},
|
||||
description: {}
|
||||
},
|
||||
onFormSuccess: refreshTable,
|
||||
successMessage: t`Project code updated`
|
||||
});
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.project_code_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete project code`,
|
||||
successMessage: t`Project code deleted`,
|
||||
onFormSuccess: refreshTable,
|
||||
preFormContent: (
|
||||
<Text>{t`Are you sure you want to remove this project code?`}</Text>
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
];
|
||||
}, []);
|
||||
const rowActions = useCallback(
|
||||
(record: any): RowAction[] => {
|
||||
return [
|
||||
RowEditAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.admin),
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
url: ApiPaths.project_code_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit project code`,
|
||||
fields: {
|
||||
code: {},
|
||||
description: {}
|
||||
},
|
||||
onFormSuccess: refreshTable,
|
||||
successMessage: t`Project code updated`
|
||||
});
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.admin),
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.project_code_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete project code`,
|
||||
successMessage: t`Project code deleted`,
|
||||
onFormSuccess: refreshTable,
|
||||
preFormContent: (
|
||||
<Text>{t`Are you sure you want to remove this project code?`}</Text>
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
const addProjectCode = useCallback(() => {
|
||||
openCreateApiForm({
|
||||
|
@ -121,3 +121,13 @@ export function partCategoryFields({}: {}): ApiFormFieldSet {
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
export function partParameterTemplateFields(): ApiFormFieldSet {
|
||||
return {
|
||||
name: {},
|
||||
description: {},
|
||||
units: {},
|
||||
choices: {},
|
||||
checkbox: {}
|
||||
};
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import { StylishText } from '../../../components/items/StylishText';
|
||||
import { PanelGroup, PanelType } from '../../../components/nav/PanelGroup';
|
||||
import { SettingsHeader } from '../../../components/nav/SettingsHeader';
|
||||
import { GlobalSettingList } from '../../../components/settings/SettingList';
|
||||
import { PartParameterTemplateTable } from '../../../components/tables/part/PartParameterTemplateTable';
|
||||
import { CurrencyTable } from '../../../components/tables/settings/CurrencyTable';
|
||||
import { CustomUnitsTable } from '../../../components/tables/settings/CustomUnitsTable';
|
||||
import { ProjectCodeTable } from '../../../components/tables/settings/ProjectCodeTable';
|
||||
@ -220,7 +221,8 @@ export default function SystemSettings() {
|
||||
{
|
||||
name: 'parameters',
|
||||
label: t`Part Parameters`,
|
||||
icon: <IconList />
|
||||
icon: <IconList />,
|
||||
content: <PartParameterTemplateTable />
|
||||
},
|
||||
{
|
||||
name: 'stock',
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Stack } from '@mantine/core';
|
||||
import { IconBellCheck, IconBellExclamation } from '@tabler/icons-react';
|
||||
import {
|
||||
IconBellCheck,
|
||||
IconBellExclamation,
|
||||
IconCircleCheck,
|
||||
IconCircleX,
|
||||
IconTrash
|
||||
} from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { api } from '../App';
|
||||
@ -27,6 +33,8 @@ export default function NotificationsPage() {
|
||||
actions={(record) => [
|
||||
{
|
||||
title: t`Mark as read`,
|
||||
color: 'green',
|
||||
icon: <IconCircleCheck />,
|
||||
onClick: () => {
|
||||
let url = apiUrl(ApiPaths.notifications_list, record.pk);
|
||||
api
|
||||
@ -53,6 +61,7 @@ export default function NotificationsPage() {
|
||||
actions={(record) => [
|
||||
{
|
||||
title: t`Mark as unread`,
|
||||
icon: <IconCircleX />,
|
||||
onClick: () => {
|
||||
let url = apiUrl(ApiPaths.notifications_list, record.pk);
|
||||
|
||||
@ -68,9 +77,10 @@ export default function NotificationsPage() {
|
||||
{
|
||||
title: t`Delete`,
|
||||
color: 'red',
|
||||
icon: <IconTrash />,
|
||||
onClick: () => {
|
||||
api
|
||||
.delete(`/notifications/${record.pk}/`)
|
||||
.delete(apiUrl(ApiPaths.notifications_list, record.pk))
|
||||
.then((response) => {
|
||||
historyRefresh.refreshTable();
|
||||
});
|
||||
|
@ -36,7 +36,7 @@ import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { editCompany } from '../../forms/CompanyForms';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { UserRoles, useUserState } from '../../states/UserState';
|
||||
|
||||
export type CompanyDetailProps = {
|
||||
title: string;
|
||||
@ -161,10 +161,6 @@ export default function CompanyDetail(props: CompanyDetailProps) {
|
||||
}, [id, company]);
|
||||
|
||||
const companyActions = useMemo(() => {
|
||||
// TODO: Finer fidelity on these permissions, perhaps?
|
||||
let canEdit = user.checkUserRole('purchase_order', 'change');
|
||||
let canDelete = user.checkUserRole('purchase_order', 'delete');
|
||||
|
||||
return [
|
||||
<ActionDropdown
|
||||
key="company"
|
||||
@ -172,7 +168,7 @@ export default function CompanyDetail(props: CompanyDetailProps) {
|
||||
icon={<IconDots />}
|
||||
actions={[
|
||||
EditItemAction({
|
||||
disabled: !canEdit,
|
||||
disabled: !user.hasChangeRole(UserRoles.purchase_order),
|
||||
onClick: () => {
|
||||
if (company?.pk) {
|
||||
editCompany({
|
||||
@ -183,7 +179,7 @@ export default function CompanyDetail(props: CompanyDetailProps) {
|
||||
}
|
||||
}),
|
||||
DeleteItemAction({
|
||||
disabled: !canDelete
|
||||
disabled: !user.hasDeleteRole(UserRoles.purchase_order)
|
||||
})
|
||||
]}
|
||||
/>
|
||||
|
@ -5,12 +5,42 @@ import { doClassicLogout } from '../functions/auth';
|
||||
import { ApiPaths, apiUrl } from './ApiState';
|
||||
import { UserProps } from './states';
|
||||
|
||||
/*
|
||||
* Enumeration of available user role groups
|
||||
*/
|
||||
export enum UserRoles {
|
||||
admin = 'admin',
|
||||
build = 'build',
|
||||
part = 'part',
|
||||
part_category = 'part_category',
|
||||
purchase_order = 'purchase_order',
|
||||
return_order = 'return_order',
|
||||
sales_order = 'sales_order',
|
||||
stock = 'stock',
|
||||
stock_location = 'stocklocation',
|
||||
stocktake = 'stocktake'
|
||||
}
|
||||
|
||||
/*
|
||||
* Enumeration of available user permissions within each role group
|
||||
*/
|
||||
export enum UserPermissions {
|
||||
view = 'view',
|
||||
add = 'add',
|
||||
change = 'change',
|
||||
delete = 'delete'
|
||||
}
|
||||
|
||||
interface UserStateProps {
|
||||
user: UserProps | undefined;
|
||||
username: () => string;
|
||||
setUser: (newUser: UserProps) => void;
|
||||
fetchUserState: () => void;
|
||||
checkUserRole: (role: string, permission: string) => boolean;
|
||||
checkUserRole: (role: UserRoles, permission: UserPermissions) => boolean;
|
||||
hasDeleteRole: (role: UserRoles) => boolean;
|
||||
hasChangeRole: (role: UserRoles) => boolean;
|
||||
hasAddRole: (role: UserRoles) => boolean;
|
||||
hasViewRole: (role: UserRoles) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,7 +95,7 @@ export const useUserState = create<UserStateProps>((set, get) => ({
|
||||
console.error('Error fetching user roles:', error);
|
||||
});
|
||||
},
|
||||
checkUserRole: (role: string, permission: string) => {
|
||||
checkUserRole: (role: UserRoles, permission: UserPermissions) => {
|
||||
// Check if the user has the specified permission for the specified role
|
||||
const user: UserProps = get().user as UserProps;
|
||||
|
||||
@ -74,5 +104,17 @@ export const useUserState = create<UserStateProps>((set, get) => ({
|
||||
if (user.roles[role] === undefined) return false;
|
||||
|
||||
return user.roles[role].includes(permission);
|
||||
},
|
||||
hasDeleteRole: (role: UserRoles) => {
|
||||
return get().checkUserRole(role, UserPermissions.delete);
|
||||
},
|
||||
hasChangeRole: (role: UserRoles) => {
|
||||
return get().checkUserRole(role, UserPermissions.change);
|
||||
},
|
||||
hasAddRole: (role: UserRoles) => {
|
||||
return get().checkUserRole(role, UserPermissions.add);
|
||||
},
|
||||
hasViewRole: (role: UserRoles) => {
|
||||
return get().checkUserRole(role, UserPermissions.view);
|
||||
}
|
||||
}));
|
||||
|
Loading…
Reference in New Issue
Block a user