Frontend updates

This commit is contained in:
Oliver Walters 2026-06-13 13:11:17 +00:00
parent e31a242238
commit 9a386428ba
4 changed files with 131 additions and 29 deletions

View File

@ -57,7 +57,7 @@ import {
} from '@tabler/icons-react';
import { useShallow } from 'zustand/react/shallow';
import { formatDate } from '../../defaults/formatters';
import { useNoteFields } from '../../forms/CommonForms';
import { useNoteFields, useNoteTemplateFields } from '../../forms/CommonForms';
import {
useCreateApiFormModal,
useDeleteApiFormModal,
@ -111,10 +111,12 @@ function NoteInfoHover({ note }: { note: any }) {
export default function NotesEditor({
modelType,
modelId,
templateMode = false,
setDirtyCallback
}: Readonly<{
modelType: ModelType;
modelId: number;
modelType?: ModelType;
modelId?: number;
templateMode?: boolean;
setDirtyCallback?: (dirty: boolean) => void;
}>) {
const api = useApi();
@ -173,23 +175,22 @@ export default function NotesEditor({
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({
queryKey: ['notes', modelType, modelId],
queryKey: ['notes', modelType, modelId, templateMode],
queryFn: async () => {
const params: Record<string, any> = templateMode
? { template: true }
: { model_id: modelId, model_type: modelType };
return api
.get(apiUrl(ApiEndpoints.note_list), {
params: {
model_id: modelId,
model_type: modelType
}
})
.get(apiUrl(ApiEndpoints.note_list), { params })
.then((response) => response.data ?? []);
},
staleTime: 0,
refetchOnWindowFocus: false,
refetchOnMount: true,
enabled: !!modelId && !!modelType
enabled: templateMode ? true : !!modelId && !!modelType
});
const [selectedNote, setSelectedNote] = useState<any>(undefined);
@ -244,11 +245,15 @@ export default function NotesEditor({
const canEdit: boolean = useMemo(
() =>
user.hasChangePermission(modelType) &&
(templateMode
? user.isStaff()
: modelType
? user.hasChangePermission(modelType)
: false) &&
notesQuery.isFetched &&
notesQuery.isSuccess &&
!!notesQuery.data,
[user, modelType, notesQuery]
[user, modelType, templateMode, notesQuery]
);
const isInTable = useEditorState({
@ -271,11 +276,16 @@ export default function NotesEditor({
return notesQuery.data && notesQuery.data.length > 0;
}, [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({
title: t`Add Note`,
fields: noteFields,
title: templateMode ? t`Add Note Template` : t`Add Note`,
fields: activeFields,
url: apiUrl(ApiEndpoints.note_list),
method: 'POST',
successMessage: null,
@ -287,7 +297,7 @@ export default function NotesEditor({
});
const deleteNote = useDeleteApiFormModal({
title: t`Delete Note`,
title: templateMode ? t`Delete Note Template` : t`Delete Note`,
url: apiUrl(ApiEndpoints.note_list),
pk: selectedNoteId,
onFormSuccess: () => {
@ -297,8 +307,8 @@ export default function NotesEditor({
});
const editNote = useEditApiFormModal({
title: t`Edit Note`,
fields: noteFields,
title: templateMode ? t`Edit Note Template` : t`Edit Note`,
fields: activeFields,
url: apiUrl(ApiEndpoints.note_list),
pk: selectedNoteId,
onFormSuccess: (response: any) => {
@ -442,9 +452,7 @@ export default function NotesEditor({
tooltip={t`Note Actions`}
actions={[
EditItemAction({
hidden:
!selectedNote ||
!user.hasChangePermission(modelType),
hidden: !selectedNote || !canEdit,
onClick: () => {
editNote.open();
}
@ -452,7 +460,11 @@ export default function NotesEditor({
DeleteItemAction({
hidden:
!selectedNote ||
!user.hasDeletePermission(modelType),
!(templateMode
? user.isStaff()
: modelType
? user.hasDeletePermission(modelType)
: false),
onClick: () => {
deleteNote.open();
}
@ -626,7 +638,7 @@ export default function NotesEditor({
</RichTextEditor>
) : (
<Alert title={t`Notes`} icon={<IconInfoCircle />}>
{t`There are no notes yet for this item.`}
{t`There are no notes here yet.`}
</Alert>
)}
</Paper>

View File

@ -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({
modelType,
modelId
@ -292,6 +310,27 @@ export function useNoteFields({
modelType: ModelType;
modelId: number;
}): 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 {
model_type: {
@ -302,9 +341,34 @@ export function useNoteFields({
hidden: true,
value: modelId
},
title: {},
description: {},
primary: {}
template_source: {
field_type: 'related field',
label: t`From Template`,
description: t`Optionally pre-fill this note from an existing template`,
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]);
}, [modelType, modelId, title, description, content, fetchTemplate]);
}

View File

@ -11,6 +11,7 @@ import {
IconList,
IconListDetails,
IconMail,
IconNotes,
IconPackages,
IconPlugConnected,
IconQrcode,
@ -71,6 +72,8 @@ const MachineManagementPanel = Loadable(
const ParameterPanel = Loadable(lazy(() => import('./ParameterPanel')));
const NoteTemplatePanel = Loadable(lazy(() => import('./NoteTemplatePanel')));
const ErrorReportTable = Loadable(
lazy(() => import('../../../../tables/settings/ErrorTable'))
);
@ -195,6 +198,13 @@ export default function AdminCenter() {
content: <ParameterPanel />,
hidden: !user.hasViewRole(UserRoles.part)
},
{
name: 'notes',
label: t`Note Templates`,
icon: <IconNotes />,
content: <NoteTemplatePanel />,
hidden: !user.isStaff()
},
{
name: 'category-parameters',
label: t`Category Parameters`,
@ -274,6 +284,7 @@ export default function AdminCenter() {
panelIDs: [
'parameters',
'category-parameters',
'notes',
'location-types',
'stocktake'
]

View File

@ -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>
);
}