mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-02 10:18:42 +00:00
Display note info
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
Group,
|
Group,
|
||||||
|
HoverCard,
|
||||||
Paper,
|
Paper,
|
||||||
Stack,
|
Stack,
|
||||||
Tabs,
|
Tabs,
|
||||||
@@ -35,6 +36,7 @@ import {
|
|||||||
IconReload,
|
IconReload,
|
||||||
IconStar
|
IconStar
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
|
import { formatDate } from '../../defaults/formatters';
|
||||||
import { useNoteFields } from '../../forms/CommonForms';
|
import { useNoteFields } from '../../forms/CommonForms';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
@@ -47,6 +49,43 @@ import {
|
|||||||
EditItemAction,
|
EditItemAction,
|
||||||
OptionsActionDropdown
|
OptionsActionDropdown
|
||||||
} from '../items/ActionDropdown';
|
} from '../items/ActionDropdown';
|
||||||
|
import { RenderUser } from '../render/User';
|
||||||
|
|
||||||
|
function NoteInfoHover({ note }: { note: any }) {
|
||||||
|
if (!note?.pk) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HoverCard position='top-start'>
|
||||||
|
<HoverCard.Target>
|
||||||
|
<ActionIcon variant='transparent'>
|
||||||
|
<IconInfoCircle />
|
||||||
|
</ActionIcon>
|
||||||
|
</HoverCard.Target>
|
||||||
|
<HoverCard.Dropdown>
|
||||||
|
<Paper p='sm' shadow='sm' withBorder>
|
||||||
|
<Stack gap='xs'>
|
||||||
|
{note.updated && (
|
||||||
|
<Group gap='xs' justify='space-between'>
|
||||||
|
<Text fw='bold'>{t`Updated`}</Text>
|
||||||
|
<Text size='xs'>
|
||||||
|
{formatDate(note.updated, { showTime: true })}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
{note.updated_by_detail && (
|
||||||
|
<Group gap='xs' justify='space-between'>
|
||||||
|
<Text fw='bold'>{t`Updated by`}</Text>
|
||||||
|
<RenderUser instance={note.updated_by_detail} />
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</HoverCard.Dropdown>
|
||||||
|
</HoverCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function NotesEditor({
|
export default function NotesEditor({
|
||||||
modelType,
|
modelType,
|
||||||
@@ -67,7 +106,7 @@ export default function NotesEditor({
|
|||||||
const [isDirty, setIsDirty] = useState(false);
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
|
|
||||||
// The ID of the selected note
|
// The ID of the selected note
|
||||||
const [selectedNote, setSelectedNote] = useState<number | undefined>(
|
const [selectedNoteId, setSelectedNoteId] = useState<number | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -94,10 +133,14 @@ export default function NotesEditor({
|
|||||||
return editor.onChange(() => setIsDirty(true));
|
return editor.onChange(() => setIsDirty(true));
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
|
const [selectedNote, setSelectedNote] = useState<any>(undefined);
|
||||||
|
|
||||||
const loadNote = useCallback(
|
const loadNote = useCallback(
|
||||||
(noteId: number) => {
|
(noteId: number) => {
|
||||||
const note = notesQuery.data?.find((note: any) => note.pk === noteId);
|
const note = notesQuery.data?.find((note: any) => note.pk === noteId);
|
||||||
|
|
||||||
|
setSelectedNote(note);
|
||||||
|
|
||||||
if (note) {
|
if (note) {
|
||||||
const blocks = editor.tryParseHTMLToBlocks(note.content ?? '');
|
const blocks = editor.tryParseHTMLToBlocks(note.content ?? '');
|
||||||
|
|
||||||
@@ -114,20 +157,20 @@ export default function NotesEditor({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadNote(selectedNote ?? -1);
|
loadNote(selectedNoteId ?? -1);
|
||||||
}, [editor, selectedNote, notesQuery.data]);
|
}, [editor, selectedNoteId, notesQuery.data]);
|
||||||
|
|
||||||
// Adjust the note selection
|
// Adjust the note selection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!notesQuery.data) return;
|
if (!notesQuery.data) return;
|
||||||
|
|
||||||
const stillExists =
|
const stillExists =
|
||||||
selectedNote &&
|
selectedNoteId &&
|
||||||
notesQuery.data.some((note: any) => note.pk === selectedNote);
|
notesQuery.data.some((note: any) => note.pk === selectedNoteId);
|
||||||
if (stillExists) return;
|
if (stillExists) return;
|
||||||
|
|
||||||
const primary = notesQuery.data.find((note: any) => note.primary);
|
const primary = notesQuery.data.find((note: any) => note.primary);
|
||||||
setSelectedNote((primary ?? notesQuery.data[0])?.pk ?? undefined);
|
setSelectedNoteId((primary ?? notesQuery.data[0])?.pk ?? undefined);
|
||||||
}, [notesQuery.data]);
|
}, [notesQuery.data]);
|
||||||
|
|
||||||
const canEdit = useMemo(
|
const canEdit = useMemo(
|
||||||
@@ -154,7 +197,7 @@ export default function NotesEditor({
|
|||||||
onFormSuccess: (response: any) => {
|
onFormSuccess: (response: any) => {
|
||||||
notesQuery.refetch().then(() => {
|
notesQuery.refetch().then(() => {
|
||||||
// Select the newly created note
|
// Select the newly created note
|
||||||
setSelectedNote(response.pk);
|
setSelectedNoteId(response.pk);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -162,9 +205,9 @@ export default function NotesEditor({
|
|||||||
const deleteNote = useDeleteApiFormModal({
|
const deleteNote = useDeleteApiFormModal({
|
||||||
title: t`Delete Note`,
|
title: t`Delete Note`,
|
||||||
url: apiUrl(ApiEndpoints.note_list),
|
url: apiUrl(ApiEndpoints.note_list),
|
||||||
pk: selectedNote,
|
pk: selectedNoteId,
|
||||||
onFormSuccess: () => {
|
onFormSuccess: () => {
|
||||||
setSelectedNote(undefined);
|
setSelectedNoteId(undefined);
|
||||||
notesQuery.refetch();
|
notesQuery.refetch();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -173,21 +216,21 @@ export default function NotesEditor({
|
|||||||
title: t`Edit Note`,
|
title: t`Edit Note`,
|
||||||
fields: noteFields,
|
fields: noteFields,
|
||||||
url: apiUrl(ApiEndpoints.note_list),
|
url: apiUrl(ApiEndpoints.note_list),
|
||||||
pk: selectedNote,
|
pk: selectedNoteId,
|
||||||
onFormSuccess: (response: any) => {
|
onFormSuccess: (response: any) => {
|
||||||
notesQuery.refetch().then(() => {
|
notesQuery.refetch().then(() => {
|
||||||
// Select the updated note
|
// Select the updated note
|
||||||
setSelectedNote(response.pk);
|
setSelectedNoteId(response.pk);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const reloadNote = useCallback(() => {
|
const reloadNote = useCallback(() => {
|
||||||
loadNote(selectedNote ?? -1);
|
loadNote(selectedNoteId ?? -1);
|
||||||
}, [selectedNote, loadNote]);
|
}, [selectedNoteId, loadNote]);
|
||||||
|
|
||||||
const saveNote = useCallback(() => {
|
const saveNote = useCallback(() => {
|
||||||
if (!selectedNote) {
|
if (!selectedNoteId || !editor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,8 +240,8 @@ export default function NotesEditor({
|
|||||||
|
|
||||||
// Sanitize the HTML content before sending to the server (or ensure it's sanitized on the back-end)
|
// Sanitize the HTML content before sending to the server (or ensure it's sanitized on the back-end)
|
||||||
|
|
||||||
if (selectedNote) {
|
if (selectedNoteId) {
|
||||||
const url = apiUrl(ApiEndpoints.note_list, selectedNote);
|
const url = apiUrl(ApiEndpoints.note_list, selectedNoteId);
|
||||||
|
|
||||||
notifications.hide('note-update-status');
|
notifications.hide('note-update-status');
|
||||||
|
|
||||||
@@ -227,7 +270,7 @@ export default function NotesEditor({
|
|||||||
notesQuery.refetch();
|
notesQuery.refetch();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [selectedNote, editor, setIsDirty]);
|
}, [selectedNoteId, editor, setIsDirty]);
|
||||||
|
|
||||||
useHotkeys([['mod+s', saveNote]]);
|
useHotkeys([['mod+s', saveNote]]);
|
||||||
|
|
||||||
@@ -243,8 +286,9 @@ export default function NotesEditor({
|
|||||||
<Paper p='xs' shadow='sm' withBorder>
|
<Paper p='xs' shadow='sm' withBorder>
|
||||||
<Group justify='space-between'>
|
<Group justify='space-between'>
|
||||||
<Group justify='left' gap='lg'>
|
<Group justify='left' gap='lg'>
|
||||||
<Text>Note Title Here</Text>
|
<NoteInfoHover note={selectedNote} />
|
||||||
<Text size='sm'>Note description here</Text>
|
<Text fw='bold'>{selectedNote?.title}</Text>
|
||||||
|
<Text size='sm'>{selectedNote?.description}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<Group justify='right' gap='xs'>
|
<Group justify='right' gap='xs'>
|
||||||
@@ -331,7 +375,7 @@ export default function NotesEditor({
|
|||||||
<Tabs
|
<Tabs
|
||||||
orientation='vertical'
|
orientation='vertical'
|
||||||
placement='right'
|
placement='right'
|
||||||
value={selectedNote?.toString()}
|
value={selectedNoteId?.toString()}
|
||||||
>
|
>
|
||||||
<Tabs.List style={{ width: '100%' }}>
|
<Tabs.List style={{ width: '100%' }}>
|
||||||
{notesQuery.data?.map((note: any) => (
|
{notesQuery.data?.map((note: any) => (
|
||||||
@@ -339,7 +383,7 @@ export default function NotesEditor({
|
|||||||
key={note.pk}
|
key={note.pk}
|
||||||
disabled={isDirty}
|
disabled={isDirty}
|
||||||
value={note.pk?.toString()}
|
value={note.pk?.toString()}
|
||||||
onClick={() => setSelectedNote(note.pk)}
|
onClick={() => setSelectedNoteId(note.pk)}
|
||||||
>
|
>
|
||||||
<Group gap='xs' wrap='nowrap' justify='space-between'>
|
<Group gap='xs' wrap='nowrap' justify='space-between'>
|
||||||
<Text size='sm'>{note.title}</Text>
|
<Text size='sm'>{note.title}</Text>
|
||||||
|
|||||||
Reference in New Issue
Block a user