diff --git a/TSX/Widget/NoteWindow/NoteTable.tsx b/TSX/Widget/NoteWindow/NoteTable.tsx index c7a9018..2a5119b 100644 --- a/TSX/Widget/NoteWindow/NoteTable.tsx +++ b/TSX/Widget/NoteWindow/NoteTable.tsx @@ -25,7 +25,7 @@ import React from 'react'; import moment from 'moment'; import { OpenXDA } from '@gpa-gemstone/application-typings'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { Modal } from '@gpa-gemstone/react-interactive'; +import { Modal, Warning } from '@gpa-gemstone/react-interactive'; import { ToolTip } from '@gpa-gemstone/react-forms'; import { Column, Table } from '@gpa-gemstone/react-table'; import NoteOptions from './NoteOptions'; @@ -38,17 +38,21 @@ interface IProps { Ascending: boolean, AllowCreate: boolean, AllowUpdate: boolean, + AllowDelete: boolean, NewNote: OpenXDA.Types.Note, SetNewNote: (note: OpenXDA.Types.Note) => void, EditNote: OpenXDA.Types.Note, SetEditNote: (note: OpenXDA.Types.Note) => void, ShowEdit: boolean, + DeleteNote: OpenXDA.Types.Note | null, Hover: 'none' | 'add' | 'clear', SetHover: (hover: 'none' | 'add' | 'clear') => void, OnSort: (data: { colField?: keyof OpenXDA.Types.Note }) => void, OnAdd: (note: OpenXDA.Types.Note) => void, OnEdit: (note: OpenXDA.Types.Note) => void, - OnSaveEdit: (confirm: boolean) => void + OnSaveEdit: (confirm: boolean) => void, + OnDelete: (note: OpenXDA.Types.Note) => void, + OnConfirmDelete: (confirm: boolean) => void } const NoteTable = (props: IProps) => { @@ -166,16 +170,23 @@ const NoteTable = (props: IProps) => { : <> } - {props.AllowUpdate ? + {props.AllowUpdate || props.AllowDelete ? Key='buttons' HeaderStyle={{ width: 'auto' }} RowStyle={{ width: 'auto' }} - Content={(row) => - - } + Content={(row) => <> + {props.AllowUpdate ? + + : null} + {props.AllowDelete ? + + : null} + } >   @@ -200,6 +211,13 @@ const NoteTable = (props: IProps) => { NoteApplications={props.NoteApplications} /> + ); diff --git a/TSX/Widget/NoteWindow/NoteWindow.tsx b/TSX/Widget/NoteWindow/NoteWindow.tsx index 5f27d3d..fd83d96 100644 --- a/TSX/Widget/NoteWindow/NoteWindow.tsx +++ b/TSX/Widget/NoteWindow/NoteWindow.tsx @@ -25,9 +25,10 @@ import React from 'react'; import moment from 'moment'; import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { ReadWriteControllerFunctions_Gemstone } from '@gpa-gemstone/common-pages'; +import { Gemstone, ReadWriteControllerFunctions_Gemstone } from '@gpa-gemstone/common-pages'; import { Alert } from '@gpa-gemstone/react-interactive'; import { MultiCheckBoxSelect, Select } from '@gpa-gemstone/react-forms'; +import { Paging } from '@gpa-gemstone/react-table'; import { EventWidget } from '../../global'; import NoteTable from './NoteTable'; @@ -76,14 +77,19 @@ const NoteWidget: EventWidget.IWidget = { const [noteStatus, setNoteStatus] = React.useState('uninitiated'); const [sortField, setSortField] = React.useState('Timestamp'); const [ascending, setAscending] = React.useState(true); + const [activePage, setActivePage] = React.useState(0); + const [pageCount, setPageCount] = React.useState(0); + const [pageInfoStatus, setPageInfoStatus] = React.useState('uninitiated'); const [refreshToken, setRefreshToken] = React.useState(0); const [newNote, setNewNote] = React.useState(createBlankNote(defaultNoteType, defaultNoteApp, -1, [])); const [editNote, setEditNote] = React.useState(createBlankNote(defaultNoteType, defaultNoteApp, -1, [])); const [showEdit, setShowEdit] = React.useState(false); + const [deleteNote, setDeleteNote] = React.useState(null); const [hover, setHover] = React.useState<'none' | 'add' | 'clear'>('none'); const canCreate = props.WidgetAuthorization.Notes.Create; const canUpdate = props.WidgetAuthorization.Notes.Update; + const canDelete = props.WidgetAuthorization.Notes.Delete; const controllers = React.useMemo(() => ({ Event: new ReadWriteControllerFunctions_Gemstone(`${props.HomePath}api/OpenXDA/Note/Event`), @@ -95,9 +101,11 @@ const NoteWidget: EventWidget.IWidget = { const activeRecordName = noteType.Name as NoteRecordName; const activeReferenceID = getReferenceID(activeRecordName, ids); const activeTags = React.useMemo(() => noteTags.filter((tag) => selectedTags.indexOf(tag.ID) >= 0), [noteTags, selectedTags]); - const filteredNotes = React.useMemo(() => - selectedTags.length === 0 ? [] : notes.filter((note) => selectedTags.indexOf(note.NoteTagID) >= 0), - [notes, selectedTags]); + const noteFilters = React.useMemo[]>(() => selectedTags.length === 0 ? [] : [{ + FieldName: 'NoteTagID', + SearchParameter: selectedTags.map((tagID) => tagID), + Operator: 'IN' + }], [selectedTags]); const metadataLoading = statusIsLoading(idStatus, noteTypeStatus, noteTagStatus, noteAppStatus); const metadataError = statusIsError(idStatus, noteTypeStatus, noteTagStatus, noteAppStatus); @@ -187,20 +195,50 @@ const NoteWidget: EventWidget.IWidget = { setNewNote((note) => ensureNoteDefaults(note, noteType, noteApp, activeReferenceID, activeTags)); }, [noteType, noteApp, activeReferenceID, activeTags]); + React.useEffect(() => { + setActivePage(0); + }, [activeRecordName, activeReferenceID]); + + React.useEffect(() => { + if (metadataLoading || metadataError) { + setPageCount(0); + setPageInfoStatus('idle'); + return; + } + + if (!isSupportedNoteType(noteType.Name) || activeReferenceID <= 0 || selectedTags.length === 0) { + setPageCount(0); + setPageInfoStatus('idle'); + return; + } + + setPageInfoStatus('loading'); + const handle = controllers[activeRecordName].GetPageInfo(noteFilters, activeReferenceID); + + handle.done((data) => { + setPageCount(data.PageCount); + setActivePage((current) => data.PageCount === 0 ? 0 : Math.min(current, data.PageCount - 1)); + setPageInfoStatus('idle'); + }).fail(() => setPageInfoStatus('error')); + + return () => { if (handle?.abort != null) handle.abort(); }; + }, [activeRecordName, activeReferenceID, controllers, metadataError, metadataLoading, noteFilters, noteType.Name, refreshToken, selectedTags.length]); + React.useEffect(() => { if (metadataLoading || metadataError) { setNotes([]); + setNoteStatus('idle'); return; } - if (!isSupportedNoteType(noteType.Name) || activeReferenceID <= 0) { + if (!isSupportedNoteType(noteType.Name) || activeReferenceID <= 0 || selectedTags.length === 0) { setNotes([]); setNoteStatus('idle'); return; } setNoteStatus('loading'); - const handle = getNotes(controllers[activeRecordName], sortField, ascending, activeReferenceID); + const handle = getNotes(controllers[activeRecordName], activePage, sortField, ascending, noteFilters, activeReferenceID); handle.done((data) => { setNotes(data); @@ -208,9 +246,10 @@ const NoteWidget: EventWidget.IWidget = { }).fail(() => setNoteStatus('error')); return () => { if (handle?.abort != null) handle.abort(); }; - }, [activeRecordName, activeReferenceID, ascending, controllers, metadataError, metadataLoading, noteType.Name, props.EventID, refreshToken, sortField]); + }, [activePage, activeRecordName, activeReferenceID, ascending, controllers, metadataError, metadataLoading, noteFilters, noteType.Name, props.EventID, refreshToken, selectedTags.length, sortField]); const handleTagChange = (_: any, changed: { Value: number | string, Label: string | JSX.Element, Selected: boolean }[]): void => { + setActivePage(0); setSelectedTags((current) => { const updated = current.filter((id) => changed.findIndex((option) => parseInt(option.Value.toString(), 10) === id) < 0); updated.push(...changed @@ -269,6 +308,26 @@ const NoteWidget: EventWidget.IWidget = { }).fail(() => setNoteStatus('error')); }; + const handleDelete = (record: OpenXDA.Types.Note): void => { + if (!canDelete) + return; + + setDeleteNote(record); + }; + + const handleConfirmDelete = (confirm: boolean): void => { + const record = deleteNote; + setDeleteNote(null); + + if (!confirm || !canDelete || record == null) + return; + + setNoteStatus('loading'); + removeNote(controllers[activeRecordName], record).done(() => { + setRefreshToken((token) => token + 1); + }).fail(() => setNoteStatus('error')); + }; + const createActionNote = (record: OpenXDA.Types.Note): OpenXDA.Types.Note => { return { ...record, @@ -308,40 +367,52 @@ const NoteWidget: EventWidget.IWidget = { /> - {metadataError || noteStatus === 'error' ? + {metadataError || noteStatus === 'error' || pageInfoStatus === 'error' ? An error occurred while fetching note data. - : null} - {metadataLoading || noteStatus === 'loading' ? + : null} + {metadataLoading || noteStatus === 'loading' || pageInfoStatus === 'loading' ?
: selectedTags.length === 0 ? -
-

At least 1 Type needs to be selected.

-
- : +
+

At least 1 Type needs to be selected.

+
+ : + <> +
+
+ setActivePage(page - 1)} /> + +
+
+ } @@ -382,8 +453,8 @@ const getReferenceID = (noteType: NoteRecordName, ids: INoteIDs): number => { return ids.LocationID; }; -const getNotes = (controller: NoteController, sortField: keyof OpenXDA.Types.Note, ascending: boolean, referenceTableID: number): JQuery.jqXHR => { - return controller.GetAll(sortField, ascending, [], referenceTableID); +const getNotes = (controller: NoteController, page: number, sortField: keyof OpenXDA.Types.Note, ascending: boolean, filters: Gemstone.Types.ISearchFilter[], referenceTableID: number): JQuery.jqXHR => { + return controller.SearchPage(page, sortField, ascending, filters, referenceTableID); }; const addNote = (controller: NoteController, record: OpenXDA.Types.Note): JQuery.jqXHR => { @@ -394,6 +465,10 @@ const updateNote = (controller: NoteController, record: OpenXDA.Types.Note): JQu return controller.Update(record); }; +const removeNote = (controller: NoteController, record: OpenXDA.Types.Note): JQuery.jqXHR => { + return controller.Delete(record); +}; + const getNoteTypes = (homePath: string): JQuery.jqXHR => { return $.ajax({ type: 'GET',