added notes to the frontend

This commit is contained in:
QkoSad
2022-12-11 11:28:40 +02:00
parent 475f924381
commit 88f04e2734
11 changed files with 538 additions and 16 deletions
+6 -4
View File
@@ -1,10 +1,12 @@
import { configureStore } from "@reduxjs/toolkit";
import authSlice from "../features/auth/authSlice";
import ticketSlice from "../features/tickets/ticketSlice";
import authReducer from "../features/auth/authSlice";
import ticketReducer from "../features/tickets/ticketSlice";
import noteReducer from "../features/notes/noteSlice";
export const store = configureStore({
reducer: {
auth: authSlice,
tickets: ticketSlice,
auth: authReducer,
tickets: ticketReducer,
notes: noteReducer,
},
});
+23
View File
@@ -0,0 +1,23 @@
import { useSelector } from "react-redux";
function NoteItem({ note }) {
const { user } = useSelector((state) => state.auth);
return (
<div
className="note"
style={{
backgroundColor: note.isStaff ? "rgba(0,0,0,0.7)" : "#fff",
color: note.isStaff ? "#fff" : "#000",
}}
>
<h4>Note from {note.isStaff ? <span>Staff</span>:<span>{user.name}</span>}</h4>
<p>{note.text}</p>
<div className="note=date">
{new Date(note.createdAt).toLocaleString('en-US')}
</div>
</div>
);
}
export default NoteItem
@@ -0,0 +1,35 @@
import axios from "axios";
const API_URL = "/api/tickets/";
const createNote = async (noteText, ticketId, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.post(
API_URL + ticketId + "/notes",
{ text: noteText },
config
);
return response.data;
};
const getNotes = async (ticketId, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.get(API_URL + ticketId + "/notes", config);
return response.data;
};
const noteService = {
getNotes,
createNote
};
export default noteService;
+88
View File
@@ -0,0 +1,88 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import noteService from "./noteService";
const initialState = {
notes: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
export const noteSlice = createSlice({
name: "note",
initialState,
reducers: {
rest: (stata) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(getNotes.pending, (state) => {
state.isLoading = true;
})
.addCase(getNotes.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.notes = action.payload;
})
.addCase(getNotes.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(createNote.pending, (state) => {
state.isLoading = true;
})
.addCase(createNote.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.notes.push(action.payload);
})
.addCase(createNote.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
export const createNote = createAsyncThunk(
"notes/create",
async ({ noteText, ticketId }, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await noteService.createNote(noteText, ticketId, token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const getNotes = createAsyncThunk(
"notes/getAll",
async (ticketId, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await noteService.getNotes(ticketId, token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const { reset } = noteSlice.actions;
export default noteSlice.reducer;
+81 -10
View File
@@ -1,30 +1,62 @@
import { useEffect } from "react";
import { FaPlus } from "react-icons/fa";
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { closeTicket, getTicket, reset } from "../features/tickets/ticketSlice";
import { closeTicket, getTicket } from "../features/tickets/ticketSlice";
import { useNavigate, useParams } from "react-router-dom";
import Spinner from "../components/Spinner";
import { BackButton } from "../components/BackButton";
import { toast } from "react-toastify";
import { getNotes, createNote } from "../features/notes/noteSlice";
import NoteItem from "../components/NoteItem";
import Modal from "react-modal";
const customStyles = {
content: {
width: "600px",
top: "50%",
left: "50%",
right: "auto",
bottom: "auto",
marginRight: "-50%",
transform: "translate(-50%,-50%)",
position: "relative",
},
};
Modal.setAppElement("#root");
function Ticket() {
const [modalIsOpen, setModalIsOpen] = useState(false);
const [noteText, setNoteText] = useState("");
const { ticket, isLoading, isSuccess, isError, message } = useSelector(
(state) => state.tickets
);
const { notes, isLoading: notesIsLoading } = useSelector(
(state) => state.notes
);
const { ticketId } = useParams();
const dispatch = useDispatch();
const navigate = useNavigate();
const onTicketClose =()=>{
dispatch(closeTicket(ticketId))
toast.success('Ticket closed')
navigate('/tickets')
}
const onNoteSubmit = (e) => {
e.preventDefault();
dispatch(createNote({ noteText, ticketId }));
closeModal();
};
const openModal = () => setModalIsOpen(true);
const closeModal = () => setModalIsOpen(false);
const onTicketClose = () => {
dispatch(closeTicket(ticketId));
toast.success("Ticket closed");
navigate("/tickets");
};
useEffect(() => {
if (isError) toast.error(message);
dispatch(getTicket(ticketId));
dispatch(getNotes(ticketId));
}, [isError, message, ticketId]);
if (isLoading) return <Spinner />;
if (isLoading || notesIsLoading) return <Spinner />;
if (isError) return <h3>Something went wrong</h3>;
return (
@@ -48,7 +80,46 @@ function Ticket() {
</div>
</header>
{ticket.status !== "closed" && (
<button className="btn btn-block btn-danger"onClick={onTicketClose}>Close Ticket</button>
<button onClick={openModal} className="btn">
<FaPlus />
Add Note
</button>
)}
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Add Note"
>
<h2>Add Note</h2>
<button className="btn-close" onClick={closeModal}>
X
</button>
<form onSubmit={onNoteSubmit}>
<div className="form-group">
<textarea
value={noteText}
onChange={(e) => setNoteText(e.target.value)}
name="noteText"
id="noteText"
className="form-control"
placeholder="Note text"
></textarea>
</div>
<div className="form-group">
<button className="btn" type="Submit">
Submit
</button>
</div>
</form>
</Modal>
{notes.map((note) => (
<NoteItem key={note._id} note={note} />
))}
{ticket.status !== "closed" && (
<button className="btn btn-block btn-danger" onClick={onTicketClose}>
Close Ticket
</button>
)}
</div>
);