diff --git a/src/frontend/src/components/nav/PanelGroup.tsx b/src/frontend/src/components/nav/PanelGroup.tsx
index bf532bddeb..9a155b9a77 100644
--- a/src/frontend/src/components/nav/PanelGroup.tsx
+++ b/src/frontend/src/components/nav/PanelGroup.tsx
@@ -95,6 +95,7 @@ export function PanelGroup({
void;
- tooltip?: string;
hidden?: boolean;
};
@@ -27,6 +27,7 @@ export function RowDuplicateAction({
title: t`Duplicate`,
color: 'green',
onClick: onClick,
+ icon: ,
hidden: hidden
};
}
@@ -43,6 +44,7 @@ export function RowEditAction({
title: t`Edit`,
color: 'blue',
onClick: onClick,
+ icon: ,
hidden: hidden
};
}
@@ -59,6 +61,7 @@ export function RowDeleteAction({
title: t`Delete`,
color: 'red',
onClick: onClick,
+ icon: ,
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 (
+
+ {
+ // 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}
+
+
+ );
+ }
+
+ // If only a single action is available, display that
+ if (visibleActions.length == 1) {
+ return ;
+ }
+
return (
visibleActions.length > 0 && (
- {title || t`Actions`}
- {visibleActions.map((action, idx) => (
- {
- // 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}
- >
-
- {action.title}
-
-
- ))}
+
+ {visibleActions.map((action, _idx) => (
+
+ ))}
+
)
diff --git a/src/frontend/src/components/tables/TableHoverCard.tsx b/src/frontend/src/components/tables/TableHoverCard.tsx
index b43f134e7a..9975e3eae7 100644
--- a/src/frontend/src/components/tables/TableHoverCard.tsx
+++ b/src/frontend/src/components/tables/TableHoverCard.tsx
@@ -27,7 +27,7 @@ export function TableHoverCard({
}
return (
-
+
{value}
diff --git a/src/frontend/src/components/tables/bom/BomTable.tsx b/src/frontend/src/components/tables/bom/BomTable.tsx
index 29cebb4dcd..73ac081edf 100644
--- a/src/frontend/src/components/tables/bom/BomTable.tsx
+++ b/src/frontend/src/components/tables/bom/BomTable.tsx
@@ -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:
}
];
}
- // 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:
});
// 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:
});
// 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,
diff --git a/src/frontend/src/components/tables/part/PartParameterTable.tsx b/src/frontend/src/components/tables/part/PartParameterTable.tsx
index 855f00e763..fe7da2b1d0 100644
--- a/src/frontend/src/components/tables/part/PartParameterTable.tsx
+++ b/src/frontend/src/components/tables/part/PartParameterTable.tsx
@@ -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(() => {
diff --git a/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx b/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx
new file mode 100644
index 0000000000..46987253a2
--- /dev/null
+++ b/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx
@@ -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: {t`Remove parameter template`}
+ });
+ }
+ })
+ ];
+ },
+ [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 [
+
+ ];
+ }, [user]);
+
+ return (
+
+ );
+}
diff --git a/src/frontend/src/components/tables/part/RelatedPartTable.tsx b/src/frontend/src/components/tables/part/RelatedPartTable.tsx
index 477be0a5f7..2b76f1e520 100644
--- a/src/frontend/src/components/tables/part/RelatedPartTable.tsx
+++ b/src/frontend/src/components/tables/part/RelatedPartTable.tsx
@@ -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: (
- {t`Are you sure you want to remove this relationship?`}
- ),
- 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: (
+ {t`Are you sure you want to remove this relationship?`}
+ ),
+ onFormSuccess: refreshTable
+ });
+ }
+ })
+ ];
+ },
+ [user]
+ );
return (
{
- notYetImplemented();
- }
+ icon:
});
} else {
actions.push({
title: t`Activate`,
- onClick: () => {
- notYetImplemented();
- }
+ color: 'green',
+ icon:
});
}
}
diff --git a/src/frontend/src/components/tables/purchasing/PurchaseOrderLineItemTable.tsx b/src/frontend/src/components/tables/purchasing/PurchaseOrderLineItemTable.tsx
index c3d2746172..d758b31b71 100644
--- a/src/frontend/src/components/tables/purchasing/PurchaseOrderLineItemTable.tsx
+++ b/src/frontend/src/components/tables/purchasing/PurchaseOrderLineItemTable.tsx
@@ -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: ,
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({
,
} />
];
diff --git a/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx b/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx
index e3e1276885..96ad0f17e1 100644
--- a/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx
+++ b/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx
@@ -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({
diff --git a/src/frontend/src/components/tables/settings/CustomUnitsTable.tsx b/src/frontend/src/components/tables/settings/CustomUnitsTable.tsx
index 30f861c83b..5a3d7f9c6a 100644
--- a/src/frontend/src/components/tables/settings/CustomUnitsTable.tsx
+++ b/src/frontend/src/components/tables/settings/CustomUnitsTable.tsx
@@ -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: (
- {t`Are you sure you want to remove this custom unit?`}
- )
- });
- }
- })
- ];
- }, []);
+ 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: (
+ {t`Are you sure you want to remove this custom unit?`}
+ )
+ });
+ }
+ })
+ ];
+ },
+ [user]
+ );
const addCustomUnit = useCallback(() => {
openCreateApiForm({
diff --git a/src/frontend/src/components/tables/settings/ProjectCodeTable.tsx b/src/frontend/src/components/tables/settings/ProjectCodeTable.tsx
index 8b7270828f..c67f443e56 100644
--- a/src/frontend/src/components/tables/settings/ProjectCodeTable.tsx
+++ b/src/frontend/src/components/tables/settings/ProjectCodeTable.tsx
@@ -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: (
- {t`Are you sure you want to remove this project code?`}
- )
- });
- }
- })
- ];
- }, []);
+ 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: (
+ {t`Are you sure you want to remove this project code?`}
+ )
+ });
+ }
+ })
+ ];
+ },
+ [user]
+ );
const addProjectCode = useCallback(() => {
openCreateApiForm({
diff --git a/src/frontend/src/forms/PartForms.tsx b/src/frontend/src/forms/PartForms.tsx
index 49d6b6f5d9..8993d74ba5 100644
--- a/src/frontend/src/forms/PartForms.tsx
+++ b/src/frontend/src/forms/PartForms.tsx
@@ -121,3 +121,13 @@ export function partCategoryFields({}: {}): ApiFormFieldSet {
return fields;
}
+
+export function partParameterTemplateFields(): ApiFormFieldSet {
+ return {
+ name: {},
+ description: {},
+ units: {},
+ choices: {},
+ checkbox: {}
+ };
+}
diff --git a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx
index fbeaa428f1..940bda1def 100644
--- a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx
+++ b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx
@@ -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:
+ icon: ,
+ content:
},
{
name: 'stock',
diff --git a/src/frontend/src/pages/Notifications.tsx b/src/frontend/src/pages/Notifications.tsx
index 0c2b1c4f78..2a9fabaa7e 100644
--- a/src/frontend/src/pages/Notifications.tsx
+++ b/src/frontend/src/pages/Notifications.tsx
@@ -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: ,
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: ,
onClick: () => {
let url = apiUrl(ApiPaths.notifications_list, record.pk);
@@ -68,9 +77,10 @@ export default function NotificationsPage() {
{
title: t`Delete`,
color: 'red',
+ icon: ,
onClick: () => {
api
- .delete(`/notifications/${record.pk}/`)
+ .delete(apiUrl(ApiPaths.notifications_list, record.pk))
.then((response) => {
historyRefresh.refreshTable();
});
diff --git a/src/frontend/src/pages/company/CompanyDetail.tsx b/src/frontend/src/pages/company/CompanyDetail.tsx
index 887681889c..d75c84346c 100644
--- a/src/frontend/src/pages/company/CompanyDetail.tsx
+++ b/src/frontend/src/pages/company/CompanyDetail.tsx
@@ -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 [
}
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)
})
]}
/>
diff --git a/src/frontend/src/states/UserState.tsx b/src/frontend/src/states/UserState.tsx
index 37f0eb8f09..e778b1fcde 100644
--- a/src/frontend/src/states/UserState.tsx
+++ b/src/frontend/src/states/UserState.tsx
@@ -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((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((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);
}
}));