edit / delete existing notes from the UI

This commit is contained in:
Oliver Walters
2026-05-24 10:33:09 +00:00
parent 9e91663d07
commit 5813c4baea
3 changed files with 143 additions and 56 deletions
+2 -1
View File
@@ -865,7 +865,8 @@ class NoteList(NoteMixin, ListCreateAPI):
filter_backends = SEARCH_ORDER_FILTER filter_backends = SEARCH_ORDER_FILTER
filterset_class = NoteFilter filterset_class = NoteFilter
ordering_fields = ['model_id', 'model_type', 'user', 'creation'] ordering = '-primary'
ordering_fields = ['model_id', 'model_type', 'user', 'creation', 'primary']
search_fields = ['content', 'model_id', 'model_type', 'user__username'] search_fields = ['content', 'model_id', 'model_type', 'user__username']
unique_create_fields = ['model_type', 'model_id'] unique_create_fields = ['model_type', 'model_id']
@@ -17,7 +17,9 @@ import { useCreateBlockNote } from '@blocknote/react';
import { import {
ActionIcon, ActionIcon,
Alert, Alert,
Badge,
Box, Box,
Button,
Flex, Flex,
Group, Group,
Paper, Paper,
@@ -30,11 +32,21 @@ import {
IconCirclePlus, IconCirclePlus,
IconDeviceFloppy, IconDeviceFloppy,
IconInfoCircle, IconInfoCircle,
IconReload IconReload,
IconStar
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useNoteFields } from '../../forms/CommonForms'; import { useNoteFields } from '../../forms/CommonForms';
import { useCreateApiFormModal } from '../../hooks/UseForm'; import {
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import {
DeleteItemAction,
EditItemAction,
OptionsActionDropdown
} from '../items/ActionDropdown';
export default function NotesEditor({ export default function NotesEditor({
modelType, modelType,
@@ -91,10 +103,10 @@ export default function NotesEditor({
if (blocks) { if (blocks) {
editor.replaceBlocks(editor.document, blocks); editor.replaceBlocks(editor.document, blocks);
}
} else { } else {
editor.replaceBlocks(editor.document, []); editor.replaceBlocks(editor.document, []);
} }
}
setIsDirty(false); setIsDirty(false);
}, },
@@ -147,6 +159,29 @@ export default function NotesEditor({
} }
}); });
const deleteNote = useDeleteApiFormModal({
title: t`Delete Note`,
url: apiUrl(ApiEndpoints.note_list),
pk: selectedNote,
onFormSuccess: () => {
setSelectedNote(undefined);
notesQuery.refetch();
}
});
const editNote = useEditApiFormModal({
title: t`Edit Note`,
fields: noteFields,
url: apiUrl(ApiEndpoints.note_list),
pk: selectedNote,
onFormSuccess: (response: any) => {
notesQuery.refetch().then(() => {
// Select the updated note
setSelectedNote(response.pk);
});
}
});
const reloadNote = useCallback(() => { const reloadNote = useCallback(() => {
loadNote(selectedNote ?? -1); loadNote(selectedNote ?? -1);
}, [selectedNote, loadNote]); }, [selectedNote, loadNote]);
@@ -199,9 +234,74 @@ export default function NotesEditor({
return ( return (
<> <>
{createNote.modal} {createNote.modal}
{deleteNote.modal}
{editNote.modal}
<Flex align='left'> <Flex align='left'>
<Box style={{ flex: 1 }}> <Box style={{ flex: 1 }} p='sm'>
<Paper p='sm' shadow='sm' withBorder> <Stack gap='xs'>
{selectedNote && (
<Paper p='xs' shadow='sm' withBorder>
<Group justify='space-between'>
<Group justify='left' gap='lg'>
<Text>Note Title Here</Text>
<Text size='sm'>Note description here</Text>
</Group>
{canEdit && (
<Group justify='right' gap='xs'>
{isDirty && (
<Badge color='yellow'>{t`Unsaved Changes`}</Badge>
)}
{isDirty && (
<Tooltip label={t`Save Notes (Ctrl+S)`}>
<ActionIcon
variant='transparent'
color={isDirty ? 'yellow' : undefined}
onClick={saveNote}
disabled={!canEdit || !isDirty}
>
<IconDeviceFloppy />
</ActionIcon>
</Tooltip>
)}
{isDirty && (
<Tooltip label={t`Reset Notes`}>
<ActionIcon
variant='transparent'
color='red'
onClick={reloadNote}
disabled={!canEdit || !isDirty}
>
<IconReload />
</ActionIcon>
</Tooltip>
)}
<OptionsActionDropdown
tooltip={t`Note Actions`}
actions={[
EditItemAction({
hidden:
!selectedNote ||
!user.hasChangePermission(modelType),
onClick: () => {
editNote.open();
}
}),
DeleteItemAction({
hidden:
!selectedNote ||
!user.hasDeletePermission(modelType),
onClick: () => {
deleteNote.open();
}
})
]}
/>
</Group>
)}
</Group>
</Paper>
)}
<Paper p='xs' shadow='sm' withBorder>
{hasNotes ? ( {hasNotes ? (
<Stack gap='xs'> <Stack gap='xs'>
<BlockNoteView <BlockNoteView
@@ -216,43 +316,19 @@ export default function NotesEditor({
</Alert> </Alert>
)} )}
</Paper> </Paper>
</Stack>
</Box> </Box>
<Paper p='sm' shadow='sm' withBorder ml='md' style={{ width: '200px' }}> <Paper p='sm' shadow='sm' withBorder ml='md' style={{ width: '200px' }}>
<Stack gap='xs'> <Stack gap='xs'>
{canEdit && ( <Button
<Group justify='space-apart' grow>
<Tooltip label={t`Save Notes (Ctrl+S)`}>
<ActionIcon
variant='transparent'
color={isDirty ? 'yellow' : undefined}
onClick={saveNote}
disabled={!canEdit || !isDirty}
>
<IconDeviceFloppy />
</ActionIcon>
</Tooltip>
<Tooltip label={t`Reset Notes`}>
<ActionIcon
variant='transparent'
color='red'
onClick={reloadNote}
disabled={!canEdit || !isDirty}
>
<IconReload />
</ActionIcon>
</Tooltip>
<Tooltip label={t`New Note`}>
<ActionIcon
variant='transparent'
color='green' color='green'
leftSection={<IconCirclePlus />}
onClick={createNote.open} onClick={createNote.open}
disabled={!canEdit || isDirty} disabled={!canEdit || isDirty}
> >
<IconCirclePlus /> {t`Add Note`}
</ActionIcon> </Button>
</Tooltip>
</Group>
)}
<Tabs <Tabs
orientation='vertical' orientation='vertical'
placement='right' placement='right'
@@ -266,8 +342,17 @@ export default function NotesEditor({
value={note.pk?.toString()} value={note.pk?.toString()}
onClick={() => setSelectedNote(note.pk)} onClick={() => setSelectedNote(note.pk)}
> >
<Group gap='xs' wrap='nowrap'> <Group gap='xs' wrap='nowrap' justify='space-between'>
<Text size='sm'>{note.title}</Text> <Text size='sm'>{note.title}</Text>
{note.primary && (
<ActionIcon
size='xs'
color='yellow'
variant='transparent'
>
<IconStar />
</ActionIcon>
)}
</Group> </Group>
</Tabs.Tab> </Tabs.Tab>
))} ))}
+2 -1
View File
@@ -302,7 +302,8 @@ export function useNoteFields({
value: modelId value: modelId
}, },
title: {}, title: {},
description: {} description: {},
primary: {}
}; };
}, [modelType, modelId]); }, [modelType, modelId]);
} }