2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-30 20:46:47 +00:00

[PUI] subscribe (#8272)

* Add "starred" icon to part category table

* Edit "starred" status for PartCategory in PUI

* Edit "starred" state of part in PUI

* Playground fix
This commit is contained in:
Oliver 2024-10-12 11:55:23 +11:00 committed by GitHub
parent 7443d21854
commit 8aade768c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 103 additions and 35 deletions

View File

@ -270,7 +270,7 @@ class CategoryDetail(CategoryMixin, CustomRetrieveUpdateDestroyAPI):
if 'starred' in data:
starred = str2bool(data.get('starred', False))
self.get_object().set_starred(request.user, starred)
self.get_object().set_starred(request.user, starred, include_parents=False)
response = super().update(request, *args, **kwargs)
@ -1423,7 +1423,9 @@ class PartDetail(PartMixin, RetrieveUpdateDestroyAPI):
if 'starred' in data:
starred = str2bool(data.get('starred', False))
self.get_object().set_starred(request.user, starred)
self.get_object().set_starred(
request.user, starred, include_variants=False, include_categories=False
)
response = super().update(request, *args, **kwargs)

View File

@ -288,11 +288,10 @@ class PartCategory(InvenTree.models.InvenTreeTree):
def get_subscribers(self, include_parents=True):
"""Return a list of users who subscribe to this PartCategory."""
cats = self.get_ancestors(include_self=True)
subscribers = set()
if include_parents:
cats = self.get_ancestors(include_self=True)
queryset = PartCategoryStar.objects.filter(category__in=cats)
else:
queryset = PartCategoryStar.objects.filter(category=self)
@ -306,12 +305,12 @@ class PartCategory(InvenTree.models.InvenTreeTree):
"""Returns True if the specified user subscribes to this category."""
return user in self.get_subscribers(**kwargs)
def set_starred(self, user, status: bool) -> None:
def set_starred(self, user, status: bool, **kwargs) -> None:
"""Set the "subscription" status of this PartCategory against the specified user."""
if not user:
return
if self.is_starred_by(user) == status:
if self.is_starred_by(user, **kwargs) == status:
return
if status:
@ -1451,13 +1450,13 @@ class Part(
"""Return True if the specified user subscribes to this part."""
return user in self.get_subscribers(**kwargs)
def set_starred(self, user, status):
def set_starred(self, user, status, **kwargs):
"""Set the "subscription" status of this Part against the specified user."""
if not user:
return
# Already subscribed?
if self.is_starred_by(user) == status:
if self.is_starred_by(user, **kwargs) == status:
return
if status:

View File

@ -61,7 +61,14 @@ export function usePartFields({
salable: {},
virtual: {},
locked: {},
active: {}
active: {},
starred: {
field_type: 'boolean',
label: t`Subscribed`,
description: t`Subscribe to notifications for this part`,
disabled: false,
required: false
}
};
// Additional fields for creation
@ -111,6 +118,10 @@ export function usePartFields({
delete fields['default_expiry'];
}
if (create) {
delete fields['starred'];
}
return fields;
}, [create, settings]);
}
@ -118,25 +129,44 @@ export function usePartFields({
/**
* Construct a set of fields for creating / editing a PartCategory instance
*/
export function partCategoryFields(): ApiFormFieldSet {
let fields: ApiFormFieldSet = {
parent: {
description: t`Parent part category`,
required: false
},
name: {},
description: {},
default_location: {
filters: {
structural: false
export function partCategoryFields({
create
}: {
create?: boolean;
}): ApiFormFieldSet {
let fields: ApiFormFieldSet = useMemo(() => {
let fields: ApiFormFieldSet = {
parent: {
description: t`Parent part category`,
required: false
},
name: {},
description: {},
default_location: {
filters: {
structural: false
}
},
default_keywords: {},
structural: {},
starred: {
field_type: 'boolean',
label: t`Subscribed`,
description: t`Subscribe to notifications for this category`,
disabled: false,
required: false
},
icon: {
field_type: 'icon'
}
},
default_keywords: {},
structural: {},
icon: {
field_type: 'icon'
};
if (create) {
delete fields['starred'];
}
};
return fields;
}, [create]);
return fields;
}

View File

@ -3,6 +3,7 @@ import {
Icon123,
IconArrowBigDownLineFilled,
IconArrowMerge,
IconBell,
IconBinaryTree2,
IconBookmarks,
IconBox,
@ -159,6 +160,8 @@ const icons = {
issue: IconBrandTelegram,
complete: IconCircleCheck,
deliver: IconTruckDelivery,
bell: IconBell,
notification: IconBell,
// Part Icons
active: IconCheck,

View File

@ -27,9 +27,10 @@ import {
} from '../../hooks/UseForm';
// Generate some example forms using the modal API forms interface
const fields = partCategoryFields();
function ApiFormsPlayground() {
const fields = partCategoryFields({});
const editCategory = useEditApiFormModal({
url: ApiEndpoints.category_list,
pk: 2,

View File

@ -109,6 +109,12 @@ export default function CategoryDetail() {
label: t`Parent Category`,
model: ModelType.partcategory,
hidden: !category?.parent
},
{
type: 'boolean',
name: 'starred',
icon: 'notification',
label: t`Subscribed`
}
];
@ -165,7 +171,7 @@ export default function CategoryDetail() {
url: ApiEndpoints.category_list,
pk: id,
title: t`Edit Part Category`,
fields: partCategoryFields(),
fields: partCategoryFields({}),
onFormSuccess: refreshInstance
});

View File

@ -358,6 +358,12 @@ export default function PartDetail() {
type: 'boolean',
name: 'virtual',
label: t`Virtual Part`
},
{
type: 'boolean',
name: 'starred',
label: t`Subscribed`,
icon: 'bell'
}
];

View File

@ -3,7 +3,7 @@
*/
import { t } from '@lingui/macro';
import { Anchor, Group, Skeleton, Text, Tooltip } from '@mantine/core';
import { IconExclamationCircle, IconLock } from '@tabler/icons-react';
import { IconBell, IconExclamationCircle, IconLock } from '@tabler/icons-react';
import { YesNoButton } from '../components/buttons/YesNoButton';
import { Thumbnail } from '../components/images/Thumbnail';
@ -42,6 +42,11 @@ export function PartColumn({
<IconLock size={16} />
</Tooltip>
)}
{part?.starred && (
<Tooltip label={t`You are subscribed to notifications for this part`}>
<IconBell size={16} color="green" />
</Tooltip>
)}
</Group>
</Group>
) : (

View File

@ -1,5 +1,6 @@
import { t } from '@lingui/macro';
import { Group } from '@mantine/core';
import { Group, Tooltip } from '@mantine/core';
import { IconBell } from '@tabler/icons-react';
import { useCallback, useMemo, useState } from 'react';
import { AddItemButton } from '../../components/buttons/AddItemButton';
@ -36,9 +37,20 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
sortable: true,
switchable: false,
render: (record: any) => (
<Group gap="xs">
{record.icon && <ApiIcon name={record.icon} />}
{record.name}
<Group gap="xs" wrap="nowrap" justify="space-between">
<Group gap="xs" wrap="nowrap">
{record.icon && <ApiIcon name={record.icon} />}
{record.name}
</Group>
<Group gap="xs" justify="flex-end" wrap="nowrap">
{record.starred && (
<Tooltip
label={t`You are subscribed to notifications for this category`}
>
<IconBell color="green" size={16} />
</Tooltip>
)}
</Group>
</Group>
)
},
@ -81,10 +93,12 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
];
}, []);
const newCategoryFields = partCategoryFields({ create: true });
const newCategory = useCreateApiFormModal({
url: ApiEndpoints.category_list,
title: t`New Part Category`,
fields: partCategoryFields(),
fields: newCategoryFields,
focus: 'name',
initialData: {
parent: parentId
@ -96,11 +110,13 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
const [selectedCategory, setSelectedCategory] = useState<number>(-1);
const editCategoryFields = partCategoryFields({ create: false });
const editCategory = useEditApiFormModal({
url: ApiEndpoints.category_list,
pk: selectedCategory,
title: t`Edit Part Category`,
fields: partCategoryFields(),
fields: editCategoryFields,
onFormSuccess: (record: any) => table.updateRecord(record)
});