mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-02 10:18:42 +00:00
Frontend updates
This commit is contained in:
@@ -57,7 +57,7 @@ import {
|
|||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
import { formatDate } from '../../defaults/formatters';
|
import { formatDate } from '../../defaults/formatters';
|
||||||
import { useNoteFields } from '../../forms/CommonForms';
|
import { useNoteFields, useNoteTemplateFields } from '../../forms/CommonForms';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useDeleteApiFormModal,
|
useDeleteApiFormModal,
|
||||||
@@ -111,10 +111,12 @@ function NoteInfoHover({ note }: { note: any }) {
|
|||||||
export default function NotesEditor({
|
export default function NotesEditor({
|
||||||
modelType,
|
modelType,
|
||||||
modelId,
|
modelId,
|
||||||
|
templateMode = false,
|
||||||
setDirtyCallback
|
setDirtyCallback
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
modelType: ModelType;
|
modelType?: ModelType;
|
||||||
modelId: number;
|
modelId?: number;
|
||||||
|
templateMode?: boolean;
|
||||||
setDirtyCallback?: (dirty: boolean) => void;
|
setDirtyCallback?: (dirty: boolean) => void;
|
||||||
}>) {
|
}>) {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
@@ -173,23 +175,22 @@ export default function NotesEditor({
|
|||||||
onUpdate: () => setIsDirty(true)
|
onUpdate: () => setIsDirty(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch the available notes for the given model type and ID
|
// Fetch the available notes for the given model type and ID (or all templates)
|
||||||
const notesQuery = useQuery({
|
const notesQuery = useQuery({
|
||||||
queryKey: ['notes', modelType, modelId],
|
queryKey: ['notes', modelType, modelId, templateMode],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
const params: Record<string, any> = templateMode
|
||||||
|
? { template: true }
|
||||||
|
: { model_id: modelId, model_type: modelType };
|
||||||
|
|
||||||
return api
|
return api
|
||||||
.get(apiUrl(ApiEndpoints.note_list), {
|
.get(apiUrl(ApiEndpoints.note_list), { params })
|
||||||
params: {
|
|
||||||
model_id: modelId,
|
|
||||||
model_type: modelType
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((response) => response.data ?? []);
|
.then((response) => response.data ?? []);
|
||||||
},
|
},
|
||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
enabled: !!modelId && !!modelType
|
enabled: templateMode ? true : !!modelId && !!modelType
|
||||||
});
|
});
|
||||||
|
|
||||||
const [selectedNote, setSelectedNote] = useState<any>(undefined);
|
const [selectedNote, setSelectedNote] = useState<any>(undefined);
|
||||||
@@ -244,11 +245,15 @@ export default function NotesEditor({
|
|||||||
|
|
||||||
const canEdit: boolean = useMemo(
|
const canEdit: boolean = useMemo(
|
||||||
() =>
|
() =>
|
||||||
user.hasChangePermission(modelType) &&
|
(templateMode
|
||||||
|
? user.isStaff()
|
||||||
|
: modelType
|
||||||
|
? user.hasChangePermission(modelType)
|
||||||
|
: false) &&
|
||||||
notesQuery.isFetched &&
|
notesQuery.isFetched &&
|
||||||
notesQuery.isSuccess &&
|
notesQuery.isSuccess &&
|
||||||
!!notesQuery.data,
|
!!notesQuery.data,
|
||||||
[user, modelType, notesQuery]
|
[user, modelType, templateMode, notesQuery]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isInTable = useEditorState({
|
const isInTable = useEditorState({
|
||||||
@@ -271,11 +276,16 @@ export default function NotesEditor({
|
|||||||
return notesQuery.data && notesQuery.data.length > 0;
|
return notesQuery.data && notesQuery.data.length > 0;
|
||||||
}, [notesQuery.data]);
|
}, [notesQuery.data]);
|
||||||
|
|
||||||
const noteFields = useNoteFields({ modelType: modelType, modelId: modelId });
|
const noteFields = useNoteFields({
|
||||||
|
modelType: modelType!,
|
||||||
|
modelId: modelId!
|
||||||
|
});
|
||||||
|
const noteTemplateFields = useNoteTemplateFields();
|
||||||
|
const activeFields = templateMode ? noteTemplateFields : noteFields;
|
||||||
|
|
||||||
const createNote = useCreateApiFormModal({
|
const createNote = useCreateApiFormModal({
|
||||||
title: t`Add Note`,
|
title: templateMode ? t`Add Note Template` : t`Add Note`,
|
||||||
fields: noteFields,
|
fields: activeFields,
|
||||||
url: apiUrl(ApiEndpoints.note_list),
|
url: apiUrl(ApiEndpoints.note_list),
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
successMessage: null,
|
successMessage: null,
|
||||||
@@ -287,7 +297,7 @@ export default function NotesEditor({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const deleteNote = useDeleteApiFormModal({
|
const deleteNote = useDeleteApiFormModal({
|
||||||
title: t`Delete Note`,
|
title: templateMode ? t`Delete Note Template` : t`Delete Note`,
|
||||||
url: apiUrl(ApiEndpoints.note_list),
|
url: apiUrl(ApiEndpoints.note_list),
|
||||||
pk: selectedNoteId,
|
pk: selectedNoteId,
|
||||||
onFormSuccess: () => {
|
onFormSuccess: () => {
|
||||||
@@ -297,8 +307,8 @@ export default function NotesEditor({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const editNote = useEditApiFormModal({
|
const editNote = useEditApiFormModal({
|
||||||
title: t`Edit Note`,
|
title: templateMode ? t`Edit Note Template` : t`Edit Note`,
|
||||||
fields: noteFields,
|
fields: activeFields,
|
||||||
url: apiUrl(ApiEndpoints.note_list),
|
url: apiUrl(ApiEndpoints.note_list),
|
||||||
pk: selectedNoteId,
|
pk: selectedNoteId,
|
||||||
onFormSuccess: (response: any) => {
|
onFormSuccess: (response: any) => {
|
||||||
@@ -442,9 +452,7 @@ export default function NotesEditor({
|
|||||||
tooltip={t`Note Actions`}
|
tooltip={t`Note Actions`}
|
||||||
actions={[
|
actions={[
|
||||||
EditItemAction({
|
EditItemAction({
|
||||||
hidden:
|
hidden: !selectedNote || !canEdit,
|
||||||
!selectedNote ||
|
|
||||||
!user.hasChangePermission(modelType),
|
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
editNote.open();
|
editNote.open();
|
||||||
}
|
}
|
||||||
@@ -452,7 +460,11 @@ export default function NotesEditor({
|
|||||||
DeleteItemAction({
|
DeleteItemAction({
|
||||||
hidden:
|
hidden:
|
||||||
!selectedNote ||
|
!selectedNote ||
|
||||||
!user.hasDeletePermission(modelType),
|
!(templateMode
|
||||||
|
? user.isStaff()
|
||||||
|
: modelType
|
||||||
|
? user.hasDeletePermission(modelType)
|
||||||
|
: false),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
deleteNote.open();
|
deleteNote.open();
|
||||||
}
|
}
|
||||||
@@ -626,7 +638,7 @@ export default function NotesEditor({
|
|||||||
</RichTextEditor>
|
</RichTextEditor>
|
||||||
) : (
|
) : (
|
||||||
<Alert title={t`Notes`} icon={<IconInfoCircle />}>
|
<Alert title={t`Notes`} icon={<IconInfoCircle />}>
|
||||||
{t`There are no notes yet for this item.`}
|
{t`There are no notes here yet.`}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -285,6 +285,24 @@ export function useParameterFields({
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useNoteTemplateFields(): ApiFormFieldSet {
|
||||||
|
return useMemo(() => {
|
||||||
|
return {
|
||||||
|
template: {
|
||||||
|
hidden: true,
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
model_type: {
|
||||||
|
label: t`Model Type`,
|
||||||
|
description: t`Limit this template to a specific model type, or leave blank for all models`,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
title: {},
|
||||||
|
description: {}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
export function useNoteFields({
|
export function useNoteFields({
|
||||||
modelType,
|
modelType,
|
||||||
modelId
|
modelId
|
||||||
@@ -292,6 +310,27 @@ export function useNoteFields({
|
|||||||
modelType: ModelType;
|
modelType: ModelType;
|
||||||
modelId: number;
|
modelId: number;
|
||||||
}): ApiFormFieldSet {
|
}): ApiFormFieldSet {
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
|
const [title, setTitle] = useState<string>('');
|
||||||
|
const [description, setDescription] = useState<string>('');
|
||||||
|
const [content, setContent] = useState<string>('');
|
||||||
|
|
||||||
|
const fetchTemplate = useCallback(
|
||||||
|
(pk: number | null) => {
|
||||||
|
if (!pk) return;
|
||||||
|
api
|
||||||
|
.get(apiUrl(ApiEndpoints.note_list, pk))
|
||||||
|
.then((response) => {
|
||||||
|
setTitle(response.data.title ?? '');
|
||||||
|
setDescription(response.data.description ?? '');
|
||||||
|
setContent(response.data.content ?? '');
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
},
|
||||||
|
[api]
|
||||||
|
);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return {
|
return {
|
||||||
model_type: {
|
model_type: {
|
||||||
@@ -302,9 +341,34 @@ export function useNoteFields({
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
value: modelId
|
value: modelId
|
||||||
},
|
},
|
||||||
title: {},
|
template_source: {
|
||||||
description: {},
|
field_type: 'related field',
|
||||||
primary: {}
|
label: t`From Template`,
|
||||||
};
|
description: t`Optionally pre-fill this note from an existing template`,
|
||||||
}, [modelType, modelId]);
|
api_url: apiUrl(ApiEndpoints.note_list),
|
||||||
|
filters: {
|
||||||
|
template: true,
|
||||||
|
model_type: modelType
|
||||||
|
},
|
||||||
|
pk_field: 'pk',
|
||||||
|
render_description_field: 'description',
|
||||||
|
required: false,
|
||||||
|
onValueChange: (value: any) => fetchTemplate(value),
|
||||||
|
value: null
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
value: title,
|
||||||
|
onValueChange: (value: any) => setTitle(value)
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
value: description,
|
||||||
|
onValueChange: (value: any) => setDescription(value)
|
||||||
|
},
|
||||||
|
primary: {},
|
||||||
|
content: {
|
||||||
|
hidden: true,
|
||||||
|
value: content
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [modelType, modelId, title, description, content, fetchTemplate]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
IconList,
|
IconList,
|
||||||
IconListDetails,
|
IconListDetails,
|
||||||
IconMail,
|
IconMail,
|
||||||
|
IconNotes,
|
||||||
IconPackages,
|
IconPackages,
|
||||||
IconPlugConnected,
|
IconPlugConnected,
|
||||||
IconQrcode,
|
IconQrcode,
|
||||||
@@ -71,6 +72,8 @@ const MachineManagementPanel = Loadable(
|
|||||||
|
|
||||||
const ParameterPanel = Loadable(lazy(() => import('./ParameterPanel')));
|
const ParameterPanel = Loadable(lazy(() => import('./ParameterPanel')));
|
||||||
|
|
||||||
|
const NoteTemplatePanel = Loadable(lazy(() => import('./NoteTemplatePanel')));
|
||||||
|
|
||||||
const ErrorReportTable = Loadable(
|
const ErrorReportTable = Loadable(
|
||||||
lazy(() => import('../../../../tables/settings/ErrorTable'))
|
lazy(() => import('../../../../tables/settings/ErrorTable'))
|
||||||
);
|
);
|
||||||
@@ -195,6 +198,13 @@ export default function AdminCenter() {
|
|||||||
content: <ParameterPanel />,
|
content: <ParameterPanel />,
|
||||||
hidden: !user.hasViewRole(UserRoles.part)
|
hidden: !user.hasViewRole(UserRoles.part)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'notes',
|
||||||
|
label: t`Note Templates`,
|
||||||
|
icon: <IconNotes />,
|
||||||
|
content: <NoteTemplatePanel />,
|
||||||
|
hidden: !user.isStaff()
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'category-parameters',
|
name: 'category-parameters',
|
||||||
label: t`Category Parameters`,
|
label: t`Category Parameters`,
|
||||||
@@ -274,6 +284,7 @@ export default function AdminCenter() {
|
|||||||
panelIDs: [
|
panelIDs: [
|
||||||
'parameters',
|
'parameters',
|
||||||
'category-parameters',
|
'category-parameters',
|
||||||
|
'notes',
|
||||||
'location-types',
|
'location-types',
|
||||||
'stocktake'
|
'stocktake'
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { Alert, Stack } from '@mantine/core';
|
||||||
|
import { IconInfoCircle } from '@tabler/icons-react';
|
||||||
|
import NotesEditor from '../../../../components/editors/NotesEditor';
|
||||||
|
|
||||||
|
export default function NoteTemplatePanel() {
|
||||||
|
return (
|
||||||
|
<Stack gap='xs'>
|
||||||
|
<Alert color='blue' icon={<IconInfoCircle />} title={t`Note Templates`}>
|
||||||
|
{t`Note templates can be used to create pre-defined notes which can be easily added to any model instance.`}
|
||||||
|
</Alert>
|
||||||
|
<NotesEditor templateMode />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user