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:
parent
7443d21854
commit
8aade768c3
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
});
|
||||
|
||||
|
@ -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'
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -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>
|
||||
) : (
|
||||
|
@ -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)
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user