commit 3a226033dbd82b3b351ad5afecc74f6ab05d367f
Author: Fantič <58391275+FantaFantic@users.noreply.github.com>
Date:   Thu May 11 08:49:30 2023 +0200

    re #10569: NotesView: Create note, update note, triggerRefresh

diff --git a/src/api/notesservice.ts b/src/api/notesservice.ts
index f8908ab..a890e8a 100644
--- a/src/api/notesservice.ts
+++ b/src/api/notesservice.ts
@@ -1,4 +1,5 @@
 import { SortOptions } from "../types/general"
+import { Note } from "../types/note"
 import { axiosInstance } from "./api"
 
 
@@ -8,6 +9,20 @@ export const getItemNotesRequest = async (itemId : string) => {
     )
 }
 
+export const updateNoteRequest = async (note : Note) => {
+    return await axiosInstance.put(`/note`, note);
+}
+
+export const createNoteRequest = async (note : Note) => {
+    return await axiosInstance.post(`/note`, note);
+}
+
+export const deleteNoteRequest = async (noteId : string) => {
+    return await axiosInstance.delete(
+        `/note/${noteId}`
+    )
+}
+
 export const getAllNotesRequest = async (myComments : boolean, generalComments : boolean, sortOptions: { items: SortOptions, date: SortOptions }) => {
     let url = `/notes/?my_notes=${myComments}&general_notes=${generalComments}`;
 
diff --git a/src/components/notes/NoteView.tsx b/src/components/notes/NoteView.tsx
index 1b41032..deb1148 100644
--- a/src/components/notes/NoteView.tsx
+++ b/src/components/notes/NoteView.tsx
@@ -6,7 +6,7 @@ import { useState } from "react";
 import React from "react";
 import { ConfirmDialog } from "../general/Dialogs";
 
-const NoteView = (props: { note: Note, handleReply: any, handleDelete: any, handleEdit: any, setConfirmDialog: any }) => {
+const NoteView = (props: { note: Note, handleReply: any, handleDelete: any, handleEdit: any, setConfirmDialog: any, navigation: any }) => {
     const note = props.note;
 
     const [isEdited, setIsEdited] = useState(false);
@@ -19,10 +19,13 @@ const NoteView = (props: { note: Note, handleReply: any, handleDelete: any, hand
         ).replace(/,/g, "").replace(/ /g, "-").replace(/-(?!.*-)/, " ");
     }
 
-    const getUsernameInitials = (username: string): string => {
-        const words = username.split(" ");
-        const initials = words.map(word => word.charAt(0).toUpperCase());
-        return initials.join("");
+    const getUsernameInitials = (username: string ): string => {
+        if (username) {
+            const words = username.split(" ");
+            const initials = words.map(word => word.charAt(0).toUpperCase());
+            return initials.join("");
+        }
+        return "UK"; // Unknown
     }
 
     const toggleEdited = () => {
@@ -64,8 +67,7 @@ const NoteView = (props: { note: Note, handleReply: any, handleDelete: any, hand
             setIsEdited(false);
         }
         else {
-            props.handleEdit(note.uuid, text);
-            // todo
+            props.handleEdit({...note, note: text});
             setIsEdited(false);
         }
     }
@@ -74,9 +76,10 @@ const NoteView = (props: { note: Note, handleReply: any, handleDelete: any, hand
         <HStack marginTop="5%">
             <Box width="10" height="10" marginTop="2.5%">
                 <Avatar bg={note.noteColor} size="sm" source={{
-                    uri: AVATAR_URL + "/" + note.avatarUrl                                     
-                    // TODO bude zde nebo v atributu ta připona?
-                    + ".png"                }}>
+                    uri: AVATAR_URL + "/" + note.avatarUrl
+                        // TODO bude zde nebo v atributu ta připona?
+                        + ".png"
+                }}>
                     {getUsernameInitials(note.username)}
                 </Avatar>
             </Box>
@@ -102,7 +105,7 @@ const NoteView = (props: { note: Note, handleReply: any, handleDelete: any, hand
 
                     {note.items && note.items.length > 0 &&
                         <Flex marginLeft="1.5%" direction="column" alignItems="flex-start" justify="flex-start" >
-                            <Button width={"16"} variant="outline" size="sm">
+                            <Button width={"16"} variant="outline" size="sm" onPress={() => props.navigation.navigate("Item", { itemId: note.items[0] })}>
                                 {note.items[0]}
                             </Button>
                         </Flex>
diff --git a/src/components/notes/NotesListView.tsx b/src/components/notes/NotesListView.tsx
index aced4b8..4ca98f7 100644
--- a/src/components/notes/NotesListView.tsx
+++ b/src/components/notes/NotesListView.tsx
@@ -2,7 +2,7 @@ import { Center, VStack, Box, Text, HStack, ScrollView } from "native-base";
 import { Note } from "../../types/note";
 import NoteView from "./NoteView";
 
-const NotesListView = (props: { notes: Note[], handleReply : any, handleDelete : any, handleEdit : any, setConfirmDialog: any}) => {
+const NotesListView = (props: { notes: Note[], handleReply : any, handleDelete : any, handleEdit : any, setConfirmDialog: any, navigation: any}) => {
     const notes = props.notes;
     return (
         <ScrollView>
@@ -10,7 +10,7 @@ const NotesListView = (props: { notes: Note[], handleReply : any, handleDelete :
                 notes && notes.length > 0 ? (
                     <VStack>
                         {notes.map((note, index) => (
-                            <NoteView key={index} note={note} handleReply={props.handleReply} handleDelete={props.handleDelete} handleEdit={props.handleEdit} setConfirmDialog={props.setConfirmDialog}/>
+                            <NoteView key={index} note={note} handleReply={props.handleReply} handleDelete={props.handleDelete} handleEdit={props.handleEdit} setConfirmDialog={props.setConfirmDialog} navigation={props.navigation}/>
                         ))}
                     </VStack>
                 ) : (
diff --git a/src/pages/NotesViewPage.tsx b/src/pages/NotesViewPage.tsx
index 629a844..e336ed3 100644
--- a/src/pages/NotesViewPage.tsx
+++ b/src/pages/NotesViewPage.tsx
@@ -12,14 +12,17 @@ import { ConfirmDialog } from "../components/general/Dialogs"
 import { useDispatch, useSelector } from "react-redux"
 import { AppDispatch, RootState } from "../stores/store"
 import LoadingBox from "../components/loading/LoadingBox"
-import { getAllNotes } from "../stores/actions/notesThunks"
+import { createNote, deleteNote, getAllNotes, updateNote } from "../stores/actions/notesThunks"
 import { login } from "../stores/actions/userThunks"
 const NotesViewPage = ({ route, navigation }: DrawerScreenProps<RootDrawerParamList, 'Notes'>) => {
 
-  const { notes, notesLoading, requestPending } = useSelector((state: RootState) => state.noteViewState)
+  const { notes, notesLoading, requestPending, triggerRefresh } = useSelector((state: RootState) => state.noteViewState)
+  
   const dispatch = useDispatch<AppDispatch>();
 
   const [isSortOpen, setIsSortOpen] = useState(false);
+  
+  const [newComment, setNewComment] = useState<string>("");
 
   const [myCommentsCheck, setMyCommentsCheck] = useState(false);
   const [generalCommentsCheck, setGeneralCommentsCheck] = useState(true);
@@ -76,11 +79,14 @@ const NotesViewPage = ({ route, navigation }: DrawerScreenProps<RootDrawerParamL
     }
   };
 
-  // initial: load notes
+  // trigger refresh on triggerRefresh increment
   useEffect(() => {
     log.debug("NotesViewPage", "useEffect", "getNotes");
     getNotes();
-  }, [])
+    
+    setNewComment("");
+    setReplyingTo(null);
+  }, [triggerRefresh])
 
   // general comments check changed
   useEffect(() => {
@@ -100,22 +106,37 @@ const NotesViewPage = ({ route, navigation }: DrawerScreenProps<RootDrawerParamL
   }
 
   const handleReply = (messageId: string, userName: string) => {
-    console.log("Handling reply: " + messageId);
     setReplyingTo({ messageId, userName })
   }
 
-  const handleEdit = (messageId: string, newMessage: string) => {
-    console.log("Handling edit:" + messageId + ", " + newMessage);
+  const handleEdit = (note : Note) => {
+    if(!requestPending)
+      dispatch(updateNote(note));
   }
 
   const handleDelete = (messageId: string) => {
-    console.log("Handling delete:" + messageId);
+    if(!requestPending)
+      dispatch(deleteNote({noteId: messageId}))
   }
 
-  console.log("notes loading: " + notesLoading);
+  const handleCreateComment = () => {
+    if(!requestPending){
+      // creating new comment
+      if(replyingTo != null){
+        dispatch(createNote( {
+          newNote: {note: newComment, reply_to: replyingTo.messageId} as Note
+        }))
+      }
+      else{
+        dispatch(createNote({
+          newNote: {note: newComment} as Note
+        }))
+      }
+    } 
+  }
 
   return (
-    <Box width="90%" marginLeft="5%">
+    <Box width="92%" marginLeft="5%">
       <VStack>
         <HStack marginTop="5%">
           <Box width="30%" justifyContent={"center"}>
@@ -168,7 +189,7 @@ const NotesViewPage = ({ route, navigation }: DrawerScreenProps<RootDrawerParamL
           {
             notesLoading ? (<LoadingBox text="Loading notes"/>)
               :
-              (<NotesListView notes={notes} handleReply={handleReply} handleDelete={handleDelete} handleEdit={handleEdit} setConfirmDialog={setConfirmDialog} />)
+              (<NotesListView notes={notes} handleReply={handleReply} handleDelete={handleDelete} handleEdit={handleEdit} setConfirmDialog={setConfirmDialog} navigation={navigation} />)
           }
         </Box>
         {replyingTo != null &&
@@ -179,11 +200,11 @@ const NotesViewPage = ({ route, navigation }: DrawerScreenProps<RootDrawerParamL
           </Flex>
         }
         <HStack h={replyingTo != null ? "20%" : "15%"} marginTop={replyingTo != null ? "0%" : "5%"}>
-          <TextArea placeholder="Add comment" width="90%" autoCompleteType={undefined} ></TextArea>
+          <TextArea placeholder="Add comment" width="90%" fontSize={14} value={newComment} onChangeText={setNewComment} autoCompleteType={undefined} ></TextArea>
           <VStack>
             <Box h={replyingTo != null ? "30%" : "45%"}></Box>
             <Box marginLeft="2">
-              <Button startIcon={<MessageIcon color="#FFF" />}></Button>
+              <Button onPress={handleCreateComment} startIcon={<MessageIcon color="#FFF" /> }></Button>
             </Box>
           </VStack>
         </HStack>
diff --git a/src/stores/actions/itemThunks.ts b/src/stores/actions/itemThunks.ts
index e043b85..37a31ec 100644
--- a/src/stores/actions/itemThunks.ts
+++ b/src/stores/actions/itemThunks.ts
@@ -120,6 +120,7 @@ export const getItemNotes = createAsyncThunk(
                             createdTime: (note as any).created, 
                             updatedTime: (note as any).updated,
                             noteColor: (note as any).note_color,
+                            reply_to: (note as any).reply_to
                         })
                     }
 
diff --git a/src/stores/actions/notesThunks.ts b/src/stores/actions/notesThunks.ts
index 2675110..8122a36 100644
--- a/src/stores/actions/notesThunks.ts
+++ b/src/stores/actions/notesThunks.ts
@@ -1,11 +1,78 @@
 import { createAsyncThunk } from "@reduxjs/toolkit"
-import { getAllNotesRequest } from "../../api/notesservice"
+import { createNoteRequest, deleteNoteRequest, getAllNotesRequest, updateNoteRequest } from "../../api/notesservice"
 import { SortOptions } from "../../types/general";
+import { Note } from "../../types/note";
+import { useDispatch } from "react-redux";
+import { AppDispatch } from "../store";
+
+export const deleteNote = createAsyncThunk(
+    "notes/deleteNote",
+    async ({ noteId }: { noteId: string }) => {
+        try {
+
+            const response = await deleteNoteRequest(noteId);
+
+            console.log(response);
+
+            if (response.status === 200) {
+                return { deletedNoteId: noteId };
+            }
+            else {
+                console.log("Error");
+                return Promise.reject(response.data ? response.data : "Error")
+            }
+        } catch (err: any) {
+            console.log(err);
+            return Promise.reject(err.response.data)
+        }
+    }
+)
+
+export const createNote = createAsyncThunk(
+    "notes/createNote",
+    async ({ newNote }: { newNote: Note}) => {
+        try {
+
+            const response = await createNoteRequest(newNote);
+            console.log(response);
+            if (response.status === 201) {
+                return { createdNote: newNote };
+            }
+            else {
+                console.log("Error");
+                return Promise.reject(response.data ? response.data : "Error")
+            }
+        } catch (err: any) {
+            console.log(err);
+            return Promise.reject(err.response.data)
+        }
+    }
+)
+
+export const updateNote = createAsyncThunk(
+    "notes/updateNote",
+    async (note: Note) => {
+        try {
+            const response = await updateNoteRequest(note);
+            if (response.status === 201) {
+                return { updateNote: note };
+            }
+            else {
+                console.log("Error");
+                return Promise.reject(response.data ? response.data : "Error")
+            }
+        } catch (err: any) {
+            console.log(err);
+            return Promise.reject(err.response.data)
+        }
+    }
+)
+
 
 
 export const getAllNotes = createAsyncThunk(
     "notes/getAllNotes",
-    async ({ myComments, generalComments, sortOptions }: { myComments : boolean, generalComments : boolean, sortOptions: { items: SortOptions, date: SortOptions }}) => {
+    async ({ myComments, generalComments, sortOptions }: { myComments: boolean, generalComments: boolean, sortOptions: { items: SortOptions, date: SortOptions } }) => {
         try {
             console.log("GET notes/getNotes/");
 
@@ -14,10 +81,10 @@ export const getAllNotes = createAsyncThunk(
             console.log(response);
 
             if (response.status === 200) {
-                if(response.data.length > 0){
+                if (response.data.length > 0) {
 
                     let notes = [];
-                    for(let i = 0; i < response.data.length; i++){
+                    for (let i = 0; i < response.data.length; i++) {
                         let note = response.data[i];
                         notes.push({
                             uuid: (note as any).uuid,
@@ -26,9 +93,10 @@ export const getAllNotes = createAsyncThunk(
                             note: (note as any).note,
                             avatarUrl: (note as any).avatar,
                             items: (note as any).items,
-                            createdTime: (note as any).created, 
+                            createdTime: (note as any).created,
                             updatedTime: (note as any).updated,
                             noteColor: (note as any).note_color,
+                            reply_to: (note as any).reply_to
                         })
                     }
 
@@ -36,7 +104,7 @@ export const getAllNotes = createAsyncThunk(
                         notes,
                     }
                 }
-                else{
+                else {
                     // no notes
                     return {
                         notes: [],
diff --git a/src/stores/reducers/notesSlice.ts b/src/stores/reducers/notesSlice.ts
index 0399236..3c1edb8 100644
--- a/src/stores/reducers/notesSlice.ts
+++ b/src/stores/reducers/notesSlice.ts
@@ -1,5 +1,5 @@
-import { PayloadAction, createSlice } from "@reduxjs/toolkit"
-import { getAllNotes } from "../actions/notesThunks"
+import { AsyncThunkAction, PayloadAction, createSlice } from "@reduxjs/toolkit"
+import { deleteNote, getAllNotes, createNote, updateNote } from "../actions/notesThunks"
 import { Certainty, ItemViewState, Item } from "../../types/item";
 import { Note, NotesViewState } from "../../types/note";
 
@@ -9,6 +9,7 @@ const initialState: NotesViewState = {
     notes: [],
     notesLoading: true, // for reading notes
     requestPending: false, // for updating or deleting note
+    triggerRefresh: 0,
     lastError: ""
 }
 
@@ -18,19 +19,69 @@ export const noteSlice = createSlice({
     reducers: {
     },
     extraReducers: (builder) => {
-            // getAllNotes
-            builder.addCase(getAllNotes.fulfilled, (state, action) => {
-                state.notesLoading = false;
-                state.notes = action.payload.notes;
-            })
-            builder.addCase(getAllNotes.pending, (state, action) => {
-                state.notesLoading = true;
-            })
-            builder.addCase(getAllNotes.rejected, (state, action) => {
-                state.notesLoading = false;
-                state.lastError = action.error.message
-            })
+        // getAllNotes
+        builder.addCase(getAllNotes.fulfilled, (state, action) => {
+            state.notesLoading = false;
+            state.lastError = "";
+            state.notes = action.payload.notes;
+        })
+        builder.addCase(getAllNotes.pending, (state, action) => {
+            state.notesLoading = true;
+        })
+        builder.addCase(getAllNotes.rejected, (state, action) => {
+            state.notesLoading = false;
+            state.lastError = action.error.message
+        })
+
+        // deleteNote
+        builder.addCase(deleteNote.pending, (state, action) => {
+            state.requestPending = true;
+        })
+        builder.addCase(deleteNote.fulfilled, (state, action) => {
+            state.lastError = "";
+            state.triggerRefresh++;
+            state.requestPending = false;
+        })
+        builder.addCase(deleteNote.rejected, (state, action) => {
+            state.requestPending = false;
+            state.lastError = action.error.message
+        })
+
+        // createNote
+        builder.addCase(createNote.pending, (state, action) => {
+            state.requestPending = true;
+        })
+        builder.addCase(createNote.fulfilled, (state, action) => {
+            state.notes = [action.payload.createdNote ,...state.notes]
+            state.lastError = "";
+            state.triggerRefresh++;
+            state.requestPending = false;
+        })
+        builder.addCase(createNote.rejected, (state, action) => {
+            state.requestPending = false;
+            state.lastError = action.error.message
+        })
+
+        // createNote
+        builder.addCase(updateNote.pending, (state, action) => {
+            state.requestPending = true;
+        })
+        builder.addCase(updateNote.fulfilled, (state, action) => {
+            state.requestPending = false;
+            state.notes = state.notes.map((note) => {
+                if (note.uuid === action.payload.updateNote.uuid) {
+                    return action.payload.updateNote;
+                } else {
+                    return note;
+                }
+            });
+            state.lastError = "";
+        });
+        builder.addCase(updateNote.rejected, (state, action) => {
+            state.requestPending = false;
+            state.lastError = action.error.message
+        })
     }
 })
 
-export default noteSlice.reducer
\ No newline at end of file
+export default noteSlice.reducer
diff --git a/src/types/note.ts b/src/types/note.ts
index f47d528..6a16193 100644
--- a/src/types/note.ts
+++ b/src/types/note.ts
@@ -8,6 +8,7 @@ export interface Note {
     createdTime: Date 
     updatedTime: Date
     noteColor: string
+    reply_to: string // parent id
 }
 
 
@@ -15,6 +16,7 @@ export type NotesViewState = {
     notes: Note[] 
     notesLoading: boolean
     requestPending: boolean
+    triggerRefresh: number
     lastError?: string
 }
 
