From ee638fb666723a0da18b3bfdd2a195f947f80c96 Mon Sep 17 00:00:00 2001 From: Sibunnayak Date: Wed, 20 Mar 2024 10:53:09 +0530 Subject: [PATCH] SupportTicket UI and API integration Updated --- src/_nav.js | 20 + src/routes.js | 25 + src/views/CustomerSupport/CloseRequestView.js | 207 +++++++++ src/views/CustomerSupport/MessageList.js | 32 ++ src/views/CustomerSupport/SupportReply.js | 346 ++++++++++++++ src/views/CustomerSupport/SupportRequest.js | 428 ++++++++++++++++++ .../CustomerSupport/SupportRequestClosed.js | 379 ++++++++++++++++ 7 files changed, 1437 insertions(+) create mode 100644 src/views/CustomerSupport/CloseRequestView.js create mode 100644 src/views/CustomerSupport/MessageList.js create mode 100644 src/views/CustomerSupport/SupportReply.js create mode 100644 src/views/CustomerSupport/SupportRequest.js create mode 100644 src/views/CustomerSupport/SupportRequestClosed.js diff --git a/src/_nav.js b/src/_nav.js index 4b49ef5..6eddd7e 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -22,6 +22,7 @@ import { cilTennisBall, cilText, cilUser, + cilAlarm, } from "@coreui/icons"; import { CNavGroup, CNavItem, CNavTitle, CTabContent } from "@coreui/react"; @@ -229,6 +230,25 @@ const _nav = [ ], }, //Affiliate end + { + component: CNavGroup, + name: "Customer Service", + icon: , + items: [ + { + component: CNavItem, + name: "Customer Support", + icon: , + to: "/support/request", + }, + { + component: CNavItem, + name: "Contact Requests", + icon: , + to: "/contact/request", + }, + ], + }, ]; export default _nav; diff --git a/src/routes.js b/src/routes.js index 56e9c3b..c518abd 100644 --- a/src/routes.js +++ b/src/routes.js @@ -110,6 +110,10 @@ import EditCoupon from "./views/Affiliate/EditCoupon"; import PayAffiliate from "./views/Affiliate/PayAffiliate"; import AffiliateHistory from "./views/Affiliate/AffiliateHistory"; import CouponHistory from "./views/Affiliate/CouponHistory"; +import SupportRequest from "./views/CustomerSupport/SupportRequest"; +import SupportReply from "./views/CustomerSupport/SupportReply"; +import SupportRequestClosed from "./views/CustomerSupport/SupportRequestClosed"; +import CloseRequestView from "./views/CustomerSupport/CloseRequestView"; const routes = [ { path: "/", exact: true, name: "Home" }, @@ -238,7 +242,28 @@ const routes = [ name: "AddContact Request", element: AddContactRequest, }, + //Support Requests + { + path: "/support/request", + name: "CustomerSupport Requests", + element: SupportRequest, + }, + { + path: "/support/request/closed", + name: "Closed CustomerSupport Requests", + element: SupportRequestClosed, + }, + { + path: "/support/request/closed/:ticketID", + name: "Closed CustomerSupport Request view", + element: CloseRequestView, + }, + { + path: "/support/request/reply/:ticketID", + name: "CustomerSupport Reply", + element: SupportReply, + }, // Content ---- > { diff --git a/src/views/CustomerSupport/CloseRequestView.js b/src/views/CustomerSupport/CloseRequestView.js new file mode 100644 index 0000000..078346a --- /dev/null +++ b/src/views/CustomerSupport/CloseRequestView.js @@ -0,0 +1,207 @@ +import React, { useEffect, useState } from "react"; +// import Button from "@material-ui/core/Button"; +import { Link, useNavigate } from "react-router-dom"; +import swal from "sweetalert"; +import axios from "axios"; +import { isAutheticated } from "src/auth"; +import { Button, ImageList, InputLabel, ImageListItem } from "@mui/material"; +import MessageList from "./MessageList"; +import { useParams } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; + +const CloseRequestView = () => { + // Assuming you have the following ticket details + const { ticketID } = useParams(); + const token = isAutheticated(); + const navigate = useNavigate(); + + const [loading, setLoading] = useState(false); + // error****************************please check + const [ticketDetails, setticketDetails] = useState(null); + + // const dispatch = useDispatch(); + // const tickets = useSelector((state) => state.ticket.ticket); + // useEffect(() => { + // dispatch(getTicketItem()); + // }, [dispatch]); + + // console.log(tickets); + // geting data from backend************ + + // const dispatch = useDispatch(); + // const tickets = useSelector((state) => state.ticket.ticket); + // useEffect(() => { + // dispatch(getTicketItem()); + // }, [dispatch]); + + // console.log(tickets); + // useEffect(() => { + // const foundTicket = tickets.find((ticket) => ticket.ticketId === ticketID); + // // console.log(foundTicket); + // setticketDetails(foundTicket); + // }, [ticketID]); + // console.log(ticketID); + // ***************** + // geting data from backend************ + const getSupportTicketDetails = async () => { + // console.log(ticketID); + axios + .get(`/api/support/getOne/${ticketID}`, { + headers: { + "Access-Control-Allow-Origin": "*", + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + // console.log(res?.data); + // setticketDetails(res.data?.support); + // setTicket(res.data?.support); + console.log(res.data?.support); + getuserName(res.data?.support); + // setLoading(false); + }) + .catch((error) => { + swal({ + title: error, + text: "please login to access the resource or refresh the page ", + icon: "error", + button: "Retry", + dangerMode: true, + }); + // setLoading(false); + }); + }; + // ************************************ + const getuserName = async (ticket) => { + try { + const userId = ticket.addedBy; + let resp = await axios.get(`/api/v1/admin/user/${userId}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + console.log(resp?.data?.user?.name); + const userName = resp?.data?.user?.name; + const userMailId = resp?.data?.user?.email; + // console.log(userName); + // Update support ticket with user name + const ticketWithUserName = { + ...ticket, + userName: userName, + userMailId: userMailId, + }; + setticketDetails(ticketWithUserName); + } catch (error) { + // Handle errors + } + }; + + React.useEffect(() => { + getSupportTicketDetails(); + }, []); + // ***************** + + return ( +
+
+
+
+
+ View Request +
+
+

+
+
+ +
+
+
+
+
+
+
+
+
+ + Ticket ID: {ticketDetails?.ticketId} + + + User ID: {ticketDetails?.addedBy} + + + User Name: {ticketDetails?.userName} + + + User MailId: {ticketDetails?.userMailId} + + + Date and Time: {ticketDetails?.createdOn} + + + Subject: {ticketDetails?.subject} + + + Description:{" "} + {ticketDetails?.description + ? ticketDetails?.description + : "No Description"} + +
+
+ {ticketDetails?.image?.length > 0 && ( +
+ + Image: + + + {ticketDetails?.image?.map((item) => ( + + {item.title + + ))} + +
+ )} +
+
+ {ticketDetails && ( + + )} +
+ + {/* *************** */} +
+
+
+
+
+ ); +}; + +export default CloseRequestView; diff --git a/src/views/CustomerSupport/MessageList.js b/src/views/CustomerSupport/MessageList.js new file mode 100644 index 0000000..251473b --- /dev/null +++ b/src/views/CustomerSupport/MessageList.js @@ -0,0 +1,32 @@ + +import React from 'react'; +import { Paper } from '@material-ui/core'; + +const MessageList = ({ messages }) => { + return ( + <> + {messages.map((msg, index) => ( +
+ +
+ {msg.user === 'user' ? 'User' : 'Admin'} +
+
+ {msg.replyDate} +
+
{msg.message}
+
+
+ ))} + + ); +}; + +export default MessageList; + diff --git a/src/views/CustomerSupport/SupportReply.js b/src/views/CustomerSupport/SupportReply.js new file mode 100644 index 0000000..1712a37 --- /dev/null +++ b/src/views/CustomerSupport/SupportReply.js @@ -0,0 +1,346 @@ +import React, { useEffect, useState } from "react"; +// import Button from "@material-ui/core/Button"; +import { Link, useNavigate, useParams } from "react-router-dom"; +import swal from "sweetalert"; +import axios from "axios"; +import { isAutheticated } from "src/auth"; +import { Button, ImageList, InputLabel, ImageListItem } from "@mui/material"; +import MessageList from "./MessageList"; +import { useDispatch, useSelector } from "react-redux"; + +import toast from "react-hot-toast"; +const SupportReply = () => { + const token = isAutheticated(); + const navigate = useNavigate(); + const [data, setData] = useState({ + user: "admin", + replyDate: "", + message: "", + }); + + const [loading, setLoading] = useState(false); + + const handleChange = (e) => { + setData((prev) => ({ ...prev, [e.target.id]: e.target.value })); + }; + const options = { + weekday: "short", // Abbreviated weekday (e.g., "Tue") + day: "numeric", // Numeric day (e.g., "5") + month: "short", // Abbreviated month (e.g., "Mar") + year: "numeric", // Numeric year (e.g., "2024") + hour: "numeric", // Numeric hour (e.g., "10") + minute: "2-digit", // Two-digit minute (e.g., "27") + hour12: true, // Use 12-hour clock (true/false) + }; + + const { ticketID } = useParams(); + + + const [ticketDetails, setticketDetails] = useState(null); + const [fetchData, setFetchData] = useState(false); + + const getSupportTicketDetails = async () => { + // console.log(ticketID); + axios + .get(`/api/support/getOne/${ticketID}`, { + headers: { + "Access-Control-Allow-Origin": "*", + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + // console.log(res?.data); + // setticketDetails(res.data?.support); + // setTicket(res.data?.support); + console.log(res.data?.support); + getuserName(res.data?.support); + // setLoading(false); + }) + .catch((error) => { + swal({ + title: error, + text: "please login to access the resource or refresh the page ", + icon: "error", + button: "Retry", + dangerMode: true, + }); + // setLoading(false); + }); + }; + // ************************************ + // updating message in backend************ + const handleSubmit = () => { + if (data.message.trim() === "") { + swal({ + title: "Warning", + text: "Fill all mandatory fields", + icon: "error", + button: "Close", + dangerMode: true, + }); + return; + } + setLoading(true); + const formData = { + message: data.message, + replyDate: new Date().toLocaleString("en-US", options), + user: "admin", + }; + axios + .patch( + `/api/support/update/${ticketID}`, + { + message: formData, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) + .then((res) => { + toast.success("Message Sent successfully!"); + setLoading(false); + setData({ message: "" }); + setFetchData(true); + navigate("/support/request", { replace: true }); + }) + .catch((error) => { + swal({ + title: "Error", + text: "Something went wrong", + icon: "error", + button: "Close", + }); + }); + }; + // ************************************ + const getuserName = async (ticket) => { + try { + const userId = ticket.addedBy; + let resp = await axios.get(`/api/v1/admin/user/${userId}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + console.log(resp?.data?.user?.name); + const userName = resp?.data?.user?.name; + const userMailId = resp?.data?.user?.email; + // console.log(userName); + // Update support ticket with user name + const ticketWithUserName = { + ...ticket, + userName: userName, + userMailId: userMailId, + }; + setticketDetails(ticketWithUserName); + } catch (error) { + // Handle errors + } + }; + + React.useEffect(() => { + getSupportTicketDetails(); + setFetchData(false); + }, [fetchData]); + + // ************************************ + return ( +
+
+
+
+
+ Support Chat +
+
+

+
+
+ {/* */} + {/* */} +
+
+
+
+
+
+
+
+
+ + Ticket ID: {ticketDetails?.ticketId} + + + User ID: {ticketDetails?.addedBy} + + + User Name: {ticketDetails?.userName} + + + User MailId: {ticketDetails?.userMailId} + + + Date and Time: {ticketDetails?.createdOn} + + + Subject: {ticketDetails?.subject} + + + Description:{" "} + {ticketDetails?.description + ? ticketDetails?.description + : "No Description"} + +
+
+ {ticketDetails?.image?.length > 0 && ( +
+ + Image: + + + {ticketDetails?.image?.map((item) => ( + + {item.title + + ))} + +
+ )} +
+
+ {ticketDetails && ( + + )} +
+ +
+ + + + {data.message ? ( + <> + + {500 - data.message.length} characters left + + + ) : ( + <> + )} +
+
+ + {/* */} + + {/* */} +
+
+
+
+
+
+ ); +}; + +export default SupportReply; diff --git a/src/views/CustomerSupport/SupportRequest.js b/src/views/CustomerSupport/SupportRequest.js new file mode 100644 index 0000000..93ccdbd --- /dev/null +++ b/src/views/CustomerSupport/SupportRequest.js @@ -0,0 +1,428 @@ +import React, { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import Button from "@material-ui/core/Button"; +import { useNavigate } from "react-router-dom"; +import axios from "axios"; +import { isAutheticated } from "src/auth"; +import { useDispatch, useSelector } from "react-redux"; + +import { CircularProgress, TextField } from "@material-ui/core"; +import { cilSearch } from "@coreui/icons"; +import CIcon from "@coreui/icons-react"; +import Fuse from "fuse.js"; +const SupportRequest = () => { + const token = isAutheticated(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(true); + const [SupportRequestsData, setSupportRequestsData] = useState([]); + + const [currentPage, setCurrentPage] = useState(1); + const [itemPerPage, setItemPerPage] = useState(10); + const [showData, setShowData] = useState(SupportRequestsData); + const [searchTerm, setSearchTerm] = useState(""); + + useEffect(() => { + setTimeout(() => { + if (searchTerm !== "") { + let searchedResult = []; + searchedResult = SupportRequestsData.filter((item) => + item.ticketId.toString().includes(searchTerm) + ); + + setShowData(searchedResult); + } + else{ + getSupportTicketsData(); + } + }, 100); + }, [searchTerm]); + + const handleShowEntries = (e) => { + setCurrentPage(1); + setItemPerPage(e.target.value); + }; + const getSupportTicketsData = async () => { + axios + .get(`/api/support/getAll/`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + + setSupportRequestsData( + res.data?.support.filter((ticket) => ticket.status === "Open") + ); + // setLoading(false); + }) + .catch((error) => { + swal({ + title: error, + text: "please login to access the resource or refresh the page ", + icon: "error", + button: "Retry", + dangerMode: true, + }); + setLoading(false); + }); + }; + React.useEffect(() => { + setLoading(true); + getSupportTicketsData(); + }, [token]); + const statusupdate = (ticketId) => { + axios + .patch( + `/api/support/update/${ticketId}`, + { + status: "Close", + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) + .then((res) => { + swal({ + title: "Success", + text: "Ticket Closed Successfully", + icon: "success", + button: "Close", + }); + getSupportTicketsData(); + }) + .catch((error) => { + swal({ + title: "Error", + text: "Something went wrong", + icon: "error", + button: "Close", + }); + }); + }; + + useEffect(() => { + // const loadData = () => { + const indexOfLastPost = currentPage * itemPerPage; + const indexOfFirstPost = indexOfLastPost - itemPerPage; + setShowData(SupportRequestsData.slice(indexOfFirstPost, indexOfLastPost)); + setLoading(false); + // }; + // loadData(); + }, [currentPage, itemPerPage, SupportRequestsData]); + + return ( +
+
+
+
+
+
+
+ Customer Support Requests +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+ {/* ********* */} +
+ setSearchTerm(e.target.value)} + /> + +
+ {/* ********** */} +
+ +
+ + + + + + + + + + + + + {!loading && showData.length === 0 && ( + + + + )} + {loading ? ( + + + + ) : ( + showData.map((ticket, i) => { + return ( + + + + + + + + + );}) + )} + +
Ticket IDSubjectCreated OnLast ReplyStatus + Action +
+
No Open Tickets Available
+
+ {/* Loading... */} + +
{ticket.ticketId}{ticket.subject}{ticket.createdOn}{ticket.lastreply}{ticket.status} + + +
+
+ +
+
+
+ Showing {currentPage * itemPerPage - itemPerPage + 1} to{" "} + {Math.min( + currentPage * itemPerPage, + SupportRequestsData.length + )}{" "} + of {SupportRequestsData.length} entries +
+
+ +
+
+
    +
  • + setCurrentPage((prev) => prev - 1)} + > + Previous + +
  • + + {!(currentPage - 1 < 1) && ( +
  • + + setCurrentPage((prev) => prev - 1) + } + > + {currentPage - 1} + +
  • + )} + +
  • + + {currentPage} + +
  • + + {!( + (currentPage + 1) * itemPerPage - itemPerPage > + SupportRequestsData.length - 1 + ) && ( +
  • + { + setCurrentPage((prev) => prev + 1); + }} + > + {currentPage + 1} + +
  • + )} + +
  • + SupportRequestsData.length - 1 + ) + ? "paginate_button page-item next" + : "paginate_button page-item next disabled" + } + > + setCurrentPage((prev) => prev + 1)} + > + Next + +
  • +
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default SupportRequest; diff --git a/src/views/CustomerSupport/SupportRequestClosed.js b/src/views/CustomerSupport/SupportRequestClosed.js new file mode 100644 index 0000000..6453642 --- /dev/null +++ b/src/views/CustomerSupport/SupportRequestClosed.js @@ -0,0 +1,379 @@ +import React, { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import Button from "@material-ui/core/Button"; +import { useNavigate } from "react-router-dom"; +import axios from "axios"; +import { isAutheticated } from "src/auth"; +import { useDispatch, useSelector } from "react-redux"; +import { CircularProgress, TextField } from "@material-ui/core"; +import { cilSearch } from "@coreui/icons"; +import CIcon from "@coreui/icons-react"; +import Fuse from "fuse.js"; +const SupportRequestClosed = () => { + const token = isAutheticated(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(true); + const [status, setStatus] = useState(""); + const [SupportRequestsData, setSupportRequestsData] = useState([]); + + const [currentPage, setCurrentPage] = useState(1); + const [itemPerPage, setItemPerPage] = useState(10); + const [showData, setShowData] = useState(SupportRequestsData); + const [searchTerm, setSearchTerm] = useState(""); + + useEffect(() => { + setTimeout(() => { + if (searchTerm !== "") { + let searchedResult = []; + searchedResult = SupportRequestsData.filter((item) => + item.ticketId.toString().includes(searchTerm) + ); + + setShowData(searchedResult); + } else { + getSupportTicketsData(); + } + }, 100); + }, [searchTerm]); + + const handleShowEntries = (e) => { + setCurrentPage(1); + setItemPerPage(e.target.value); + }; + const [ticketsData, setticketsData] = React.useState([]); + const getSupportTicketsData = async () => { + axios + .get(`/api/support/getAll/`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + setSupportRequestsData( + res.data?.support.filter((ticket) => ticket.status === "Close") + ); + setLoading(false); + }) + .catch((error) => { + swal({ + title: error, + text: "please login to access the resource or refresh the page ", + icon: "error", + button: "Retry", + dangerMode: true, + }); + setLoading(false); + }); + }; + React.useEffect(() => { + setLoading(true); + getSupportTicketsData(); + }, [token]); + + // console.log(showData); + + //********************************* */ + useEffect(() => { + const loadData = () => { + const indexOfLastPost = currentPage * itemPerPage; + const indexOfFirstPost = indexOfLastPost - itemPerPage; + setShowData(SupportRequestsData.slice(indexOfFirstPost, indexOfLastPost)); + }; + loadData(); + }, [currentPage, itemPerPage, SupportRequestsData]); + + return ( +
+
+
+
+
+
+
+ Closed Customer Support Requests +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+ {/* ********* */} +
+ setSearchTerm(e.target.value)} + /> + handleSearch(searchTerm)} + /> +
+ {/* ********** */} +
+ +
+ + + + + + + + + + {/* */} + + + + + {!loading && showData.length === 0 && ( + + + + )} + {loading ? ( + + + + ) : ( + showData.map((ticket, i) => { + return ( + + + + + + + + + ); + }) + )} + +
Ticket IDSubjectCreated OnLast ReplyStatus + Action + Action
+
No Close Tickets Available
+
+ {/* Loading... */} + +
+ {ticket.ticketId} + {ticket.subject} + {ticket.createdOn} + + {ticket.lastreply} + {ticket.status} + + + +
+
+ +
+
+
+ Showing {currentPage * itemPerPage - itemPerPage + 1} to{" "} + {Math.min( + currentPage * itemPerPage, + SupportRequestsData.length + )}{" "} + of {SupportRequestsData.length} entries +
+
+ +
+
+
    +
  • + setCurrentPage((prev) => prev - 1)} + > + Previous + +
  • + + {!(currentPage - 1 < 1) && ( +
  • + + setCurrentPage((prev) => prev - 1) + } + > + {currentPage - 1} + +
  • + )} + +
  • + + {currentPage} + +
  • + + {!( + (currentPage + 1) * itemPerPage - itemPerPage > + SupportRequestsData.length - 1 + ) && ( +
  • + { + setCurrentPage((prev) => prev + 1); + }} + > + {currentPage + 1} + +
  • + )} + +
  • + SupportRequestsData.length - 1 + ) + ? "paginate_button page-item next" + : "paginate_button page-item next disabled" + } + > + setCurrentPage((prev) => prev + 1)} + > + Next + +
  • +
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default SupportRequestClosed;