mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-01 04:56:45 +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:
|
if 'starred' in data:
|
||||||
starred = str2bool(data.get('starred', False))
|
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)
|
response = super().update(request, *args, **kwargs)
|
||||||
|
|
||||||
@ -1423,7 +1423,9 @@ class PartDetail(PartMixin, RetrieveUpdateDestroyAPI):
|
|||||||
if 'starred' in data:
|
if 'starred' in data:
|
||||||
starred = str2bool(data.get('starred', False))
|
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)
|
response = super().update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -288,11 +288,10 @@ class PartCategory(InvenTree.models.InvenTreeTree):
|
|||||||
|
|
||||||
def get_subscribers(self, include_parents=True):
|
def get_subscribers(self, include_parents=True):
|
||||||
"""Return a list of users who subscribe to this PartCategory."""
|
"""Return a list of users who subscribe to this PartCategory."""
|
||||||
cats = self.get_ancestors(include_self=True)
|
|
||||||
|
|
||||||
subscribers = set()
|
subscribers = set()
|
||||||
|
|
||||||
if include_parents:
|
if include_parents:
|
||||||
|
cats = self.get_ancestors(include_self=True)
|
||||||
queryset = PartCategoryStar.objects.filter(category__in=cats)
|
queryset = PartCategoryStar.objects.filter(category__in=cats)
|
||||||
else:
|
else:
|
||||||
queryset = PartCategoryStar.objects.filter(category=self)
|
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."""
|
"""Returns True if the specified user subscribes to this category."""
|
||||||
return user in self.get_subscribers(**kwargs)
|
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."""
|
"""Set the "subscription" status of this PartCategory against the specified user."""
|
||||||
if not user:
|
if not user:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.is_starred_by(user) == status:
|
if self.is_starred_by(user, **kwargs) == status:
|
||||||
return
|
return
|
||||||
|
|
||||||
if status:
|
if status:
|
||||||
@ -1451,13 +1450,13 @@ class Part(
|
|||||||
"""Return True if the specified user subscribes to this part."""
|
"""Return True if the specified user subscribes to this part."""
|
||||||
return user in self.get_subscribers(**kwargs)
|
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."""
|
"""Set the "subscription" status of this Part against the specified user."""
|
||||||
if not user:
|
if not user:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Already subscribed?
|
# Already subscribed?
|
||||||
if self.is_starred_by(user) == status:
|
if self.is_starred_by(user, **kwargs) == status:
|
||||||
return
|
return
|
||||||
|
|
||||||
if status:
|
if status:
|
||||||
|
@ -61,7 +61,14 @@ export function usePartFields({
|
|||||||
salable: {},
|
salable: {},
|
||||||
virtual: {},
|
virtual: {},
|
||||||
locked: {},
|
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
|
// Additional fields for creation
|
||||||
@ -111,6 +118,10 @@ export function usePartFields({
|
|||||||
delete fields['default_expiry'];
|
delete fields['default_expiry'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (create) {
|
||||||
|
delete fields['starred'];
|
||||||
|
}
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
}, [create, settings]);
|
}, [create, settings]);
|
||||||
}
|
}
|
||||||
@ -118,7 +129,12 @@ export function usePartFields({
|
|||||||
/**
|
/**
|
||||||
* Construct a set of fields for creating / editing a PartCategory instance
|
* Construct a set of fields for creating / editing a PartCategory instance
|
||||||
*/
|
*/
|
||||||
export function partCategoryFields(): ApiFormFieldSet {
|
export function partCategoryFields({
|
||||||
|
create
|
||||||
|
}: {
|
||||||
|
create?: boolean;
|
||||||
|
}): ApiFormFieldSet {
|
||||||
|
let fields: ApiFormFieldSet = useMemo(() => {
|
||||||
let fields: ApiFormFieldSet = {
|
let fields: ApiFormFieldSet = {
|
||||||
parent: {
|
parent: {
|
||||||
description: t`Parent part category`,
|
description: t`Parent part category`,
|
||||||
@ -133,11 +149,25 @@ export function partCategoryFields(): ApiFormFieldSet {
|
|||||||
},
|
},
|
||||||
default_keywords: {},
|
default_keywords: {},
|
||||||
structural: {},
|
structural: {},
|
||||||
|
starred: {
|
||||||
|
field_type: 'boolean',
|
||||||
|
label: t`Subscribed`,
|
||||||
|
description: t`Subscribe to notifications for this category`,
|
||||||
|
disabled: false,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
icon: {
|
icon: {
|
||||||
field_type: 'icon'
|
field_type: 'icon'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (create) {
|
||||||
|
delete fields['starred'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}, [create]);
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
Icon123,
|
Icon123,
|
||||||
IconArrowBigDownLineFilled,
|
IconArrowBigDownLineFilled,
|
||||||
IconArrowMerge,
|
IconArrowMerge,
|
||||||
|
IconBell,
|
||||||
IconBinaryTree2,
|
IconBinaryTree2,
|
||||||
IconBookmarks,
|
IconBookmarks,
|
||||||
IconBox,
|
IconBox,
|
||||||
@ -159,6 +160,8 @@ const icons = {
|
|||||||
issue: IconBrandTelegram,
|
issue: IconBrandTelegram,
|
||||||
complete: IconCircleCheck,
|
complete: IconCircleCheck,
|
||||||
deliver: IconTruckDelivery,
|
deliver: IconTruckDelivery,
|
||||||
|
bell: IconBell,
|
||||||
|
notification: IconBell,
|
||||||
|
|
||||||
// Part Icons
|
// Part Icons
|
||||||
active: IconCheck,
|
active: IconCheck,
|
||||||
|
@ -27,9 +27,10 @@ import {
|
|||||||
} from '../../hooks/UseForm';
|
} from '../../hooks/UseForm';
|
||||||
|
|
||||||
// Generate some example forms using the modal API forms interface
|
// Generate some example forms using the modal API forms interface
|
||||||
const fields = partCategoryFields();
|
|
||||||
|
|
||||||
function ApiFormsPlayground() {
|
function ApiFormsPlayground() {
|
||||||
|
const fields = partCategoryFields({});
|
||||||
|
|
||||||
const editCategory = useEditApiFormModal({
|
const editCategory = useEditApiFormModal({
|
||||||
url: ApiEndpoints.category_list,
|
url: ApiEndpoints.category_list,
|
||||||
pk: 2,
|
pk: 2,
|
||||||
|
@ -109,6 +109,12 @@ export default function CategoryDetail() {
|
|||||||
label: t`Parent Category`,
|
label: t`Parent Category`,
|
||||||
model: ModelType.partcategory,
|
model: ModelType.partcategory,
|
||||||
hidden: !category?.parent
|
hidden: !category?.parent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
name: 'starred',
|
||||||
|
icon: 'notification',
|
||||||
|
label: t`Subscribed`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -165,7 +171,7 @@ export default function CategoryDetail() {
|
|||||||
url: ApiEndpoints.category_list,
|
url: ApiEndpoints.category_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
title: t`Edit Part Category`,
|
title: t`Edit Part Category`,
|
||||||
fields: partCategoryFields(),
|
fields: partCategoryFields({}),
|
||||||
onFormSuccess: refreshInstance
|
onFormSuccess: refreshInstance
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -358,6 +358,12 @@ export default function PartDetail() {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
name: 'virtual',
|
name: 'virtual',
|
||||||
label: t`Virtual Part`
|
label: t`Virtual Part`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
name: 'starred',
|
||||||
|
label: t`Subscribed`,
|
||||||
|
icon: 'bell'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Anchor, Group, Skeleton, Text, Tooltip } from '@mantine/core';
|
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 { YesNoButton } from '../components/buttons/YesNoButton';
|
||||||
import { Thumbnail } from '../components/images/Thumbnail';
|
import { Thumbnail } from '../components/images/Thumbnail';
|
||||||
@ -42,6 +42,11 @@ export function PartColumn({
|
|||||||
<IconLock size={16} />
|
<IconLock size={16} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{part?.starred && (
|
||||||
|
<Tooltip label={t`You are subscribed to notifications for this part`}>
|
||||||
|
<IconBell size={16} color="green" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { t } from '@lingui/macro';
|
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 { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
@ -36,10 +37,21 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: false,
|
switchable: false,
|
||||||
render: (record: any) => (
|
render: (record: any) => (
|
||||||
<Group gap="xs">
|
<Group gap="xs" wrap="nowrap" justify="space-between">
|
||||||
|
<Group gap="xs" wrap="nowrap">
|
||||||
{record.icon && <ApiIcon name={record.icon} />}
|
{record.icon && <ApiIcon name={record.icon} />}
|
||||||
{record.name}
|
{record.name}
|
||||||
</Group>
|
</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>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
DescriptionColumn({}),
|
DescriptionColumn({}),
|
||||||
@ -81,10 +93,12 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
|
|||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const newCategoryFields = partCategoryFields({ create: true });
|
||||||
|
|
||||||
const newCategory = useCreateApiFormModal({
|
const newCategory = useCreateApiFormModal({
|
||||||
url: ApiEndpoints.category_list,
|
url: ApiEndpoints.category_list,
|
||||||
title: t`New Part Category`,
|
title: t`New Part Category`,
|
||||||
fields: partCategoryFields(),
|
fields: newCategoryFields,
|
||||||
focus: 'name',
|
focus: 'name',
|
||||||
initialData: {
|
initialData: {
|
||||||
parent: parentId
|
parent: parentId
|
||||||
@ -96,11 +110,13 @@ export function PartCategoryTable({ parentId }: Readonly<{ parentId?: any }>) {
|
|||||||
|
|
||||||
const [selectedCategory, setSelectedCategory] = useState<number>(-1);
|
const [selectedCategory, setSelectedCategory] = useState<number>(-1);
|
||||||
|
|
||||||
|
const editCategoryFields = partCategoryFields({ create: false });
|
||||||
|
|
||||||
const editCategory = useEditApiFormModal({
|
const editCategory = useEditApiFormModal({
|
||||||
url: ApiEndpoints.category_list,
|
url: ApiEndpoints.category_list,
|
||||||
pk: selectedCategory,
|
pk: selectedCategory,
|
||||||
title: t`Edit Part Category`,
|
title: t`Edit Part Category`,
|
||||||
fields: partCategoryFields(),
|
fields: editCategoryFields,
|
||||||
onFormSuccess: (record: any) => table.updateRecord(record)
|
onFormSuccess: (record: any) => table.updateRecord(record)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user