From 252c4f12a46b0772fe565814cbce045fe5c09392 Mon Sep 17 00:00:00 2001 From: aweeshathavishanka Date: Sun, 13 Oct 2024 20:08:23 +0530 Subject: [PATCH 01/17] NIC and Birthday Change --- frontend/src/Pages/Customer/LoginForm.jsx | 18 ++++---- .../src/Pages/farmer/FarmerRegistration.jsx | 42 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/frontend/src/Pages/Customer/LoginForm.jsx b/frontend/src/Pages/Customer/LoginForm.jsx index 877d8b5a..c467fbf2 100644 --- a/frontend/src/Pages/Customer/LoginForm.jsx +++ b/frontend/src/Pages/Customer/LoginForm.jsx @@ -29,14 +29,14 @@ const Login = () => { }, [error]) return ( -
-
+
+
Logo -
+

Welcome Back...

@@ -47,7 +47,7 @@ const Login = () => { setEmail(e.target.value)} required @@ -64,7 +64,7 @@ const Login = () => { setPassword(e.target.value)} required @@ -74,12 +74,12 @@ const Login = () => { + + {/* Need an account section */} +
+

+ Don't have an account?{' '} + + Sign up here + + . +

+
+
+
+ ) +} + +export default LoginForm diff --git a/frontend/src/Components/Help/SignupForm.jsx b/frontend/src/Components/Help/SignupForm.jsx new file mode 100644 index 00000000..4663ff98 --- /dev/null +++ b/frontend/src/Components/Help/SignupForm.jsx @@ -0,0 +1,127 @@ +import { Link } from 'react-router-dom' +import { useState } from 'react' + +const SignupForm = () => { + const [formData, setFormData] = useState({ + firstName: '', + lastName: '', + email: '', + password: '', + }) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value, + })) + } + + const handleSubmit = (e) => { + e.preventDefault() + // Handle form submission logic here + console.log('Form submitted:', formData) + } + + return ( +
+
+

+ Create Customer Care Manager Account +

+

+ By creating a Farmcart account, you agree to our{' '} + + Terms of Service + {' '} + and{' '} + + Privacy Policy + + . +

+
+
+ {/* First Name */} +
+ + +
+ {/* Last Name */} +
+ + +
+
+ {/* Email */} +
+ + +
+ {/* Password */} +
+ + +
+ +
+ {/* Already have an account section */} +
+

+ Already have an account?{' '} + + Log in here + + . +

+
+
+
+ ) +} + +export default SignupForm diff --git a/frontend/src/Pages/Help/CCManager/Login.jsx b/frontend/src/Pages/Help/CCManager/Login.jsx new file mode 100644 index 00000000..f149cc71 --- /dev/null +++ b/frontend/src/Pages/Help/CCManager/Login.jsx @@ -0,0 +1,25 @@ +import signupImg from '../../../../public/loginimg.png' +import LoginForm from '../../../Components/Help/LoginForm' + +const LogIn = () => { + return ( +
+
+
+
+ +
+
+
+ SignUp Img +
+
+ +
+
+
+
+ ) +} + +export default LogIn diff --git a/frontend/src/Pages/Help/CCManager/Signup.jsx b/frontend/src/Pages/Help/CCManager/Signup.jsx new file mode 100644 index 00000000..05efd449 --- /dev/null +++ b/frontend/src/Pages/Help/CCManager/Signup.jsx @@ -0,0 +1,25 @@ +import SignupForm from '../../../Components/Help/SignupForm' +import signupImg from '../../../../public/ccmanagersignup.png' + +const Signup = () => { + return ( +
+
+
+
+ +
+
+
+ +
+
+ SignUp Img +
+
+
+
+ ) +} + +export default Signup From e59358a5723d3738436c013db1b06fb4d7b99f9e Mon Sep 17 00:00:00 2001 From: aweeshathavishanka Date: Mon, 14 Oct 2024 19:21:01 +0530 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=98=8D=20CCM=20Authentication=20is?= =?UTF-8?q?=20Completed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 2 +- frontend/src/App.jsx | 4 + frontend/src/Components/Help/OTPForm.jsx | 61 +++++++++++++ frontend/src/Components/Help/SignupForm.jsx | 38 ++++++++- .../src/Pages/Help/CCManager/Dashboard.jsx | 5 ++ frontend/src/Pages/Help/CCManager/Login.jsx | 2 +- .../src/Pages/Help/CCManager/OtpEntry.jsx | 85 +++++++++++++++++++ frontend/src/Pages/Help/CCManager/Signup.jsx | 4 +- 8 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 frontend/src/Components/Help/OTPForm.jsx create mode 100644 frontend/src/Pages/Help/CCManager/Dashboard.jsx create mode 100644 frontend/src/Pages/Help/CCManager/OtpEntry.jsx diff --git a/frontend/package.json b/frontend/package.json index 54ae12b9..69ce11cc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -40,7 +40,7 @@ "react-step-progress-bar": "^1.0.3", "react-table": "^7.8.0", "react-to-print": "^2.15.1", - "react-toastify": "^10.0.5", + "react-toastify": "^10.0.6", "slick-carousel": "^1.8.1", "sweetalert2": "^11.12.4" }, diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index bf7a47f9..62659866 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -112,6 +112,8 @@ import SupportTicket from './Pages/Help/SupportTicket' import Feedback from './Pages/Help/Feedback' import Signup from './Pages/Help/CCManager/Signup' import LogIn from './Pages/Help/CCManager/Login' +import OtpEntry from './Pages/Help/CCManager/OtpEntry' +import Dashboard from './Pages/Help/CCManager/Dashboard' // Define all routes in a single Router const router = createBrowserRouter( @@ -308,6 +310,8 @@ const router = createBrowserRouter( } /> } /> } /> + } /> + } /> } /> diff --git a/frontend/src/Components/Help/OTPForm.jsx b/frontend/src/Components/Help/OTPForm.jsx new file mode 100644 index 00000000..ecf3a10d --- /dev/null +++ b/frontend/src/Components/Help/OTPForm.jsx @@ -0,0 +1,61 @@ +import { useState } from 'react' +import { Link } from 'react-router-dom' + +const OtpForm = () => { + const [otp, setOtp] = useState('') + + const handleChange = (e) => { + setOtp(e.target.value) + } + + const handleSubmit = (e) => { + e.preventDefault() + // Handle OTP submission logic here, e.g., verify OTP + console.log('OTP submitted:', otp) + } + + return ( +
+

Enter OTP

+

+ Please enter the OTP sent to your registered email. +

+
+
+ + +
+ +
+
+

+ Didn’t receive an OTP?{' '} + + Resend OTP + + . +

+
+
+ ) +} + +export default OtpForm diff --git a/frontend/src/Components/Help/SignupForm.jsx b/frontend/src/Components/Help/SignupForm.jsx index 4663ff98..34102b49 100644 --- a/frontend/src/Components/Help/SignupForm.jsx +++ b/frontend/src/Components/Help/SignupForm.jsx @@ -1,5 +1,8 @@ -import { Link } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import { useState } from 'react' +import axios from 'axios' +import { toast, ToastContainer } from 'react-toastify' +import 'react-toastify/dist/ReactToastify.css' const SignupForm = () => { const [formData, setFormData] = useState({ @@ -9,6 +12,8 @@ const SignupForm = () => { password: '', }) + const navigate = useNavigate() + const handleChange = (e) => { const { name, value } = e.target setFormData((prevData) => ({ @@ -17,14 +22,39 @@ const SignupForm = () => { })) } - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault() - // Handle form submission logic here - console.log('Form submitted:', formData) + try { + const response = await axios.post( + `${import.meta.env.VITE_API_URL}/help/auth/signup`, + formData + ) + + console.log('Signup successful:', response.data) + toast.success( + 'Signup successful! Please check your email for the OTP.', + { + position: 'top-right', + } + ) + + // Redirect to OTP entry page after a short delay + navigate('/help/verify') + } catch (error) { + console.error('Signup error:', error.response?.data) + toast.error( + error.response?.data.message || + 'Signup failed! Please try again.', + { + position: 'top-right', + } + ) + } } return (
+

Create Customer Care Manager Account diff --git a/frontend/src/Pages/Help/CCManager/Dashboard.jsx b/frontend/src/Pages/Help/CCManager/Dashboard.jsx new file mode 100644 index 00000000..917e6603 --- /dev/null +++ b/frontend/src/Pages/Help/CCManager/Dashboard.jsx @@ -0,0 +1,5 @@ +const Dashboard = () => { + return
Dashboard
+} + +export default Dashboard diff --git a/frontend/src/Pages/Help/CCManager/Login.jsx b/frontend/src/Pages/Help/CCManager/Login.jsx index f149cc71..5b4fdbf0 100644 --- a/frontend/src/Pages/Help/CCManager/Login.jsx +++ b/frontend/src/Pages/Help/CCManager/Login.jsx @@ -8,7 +8,7 @@ const LogIn = () => {

-
+
SignUp Img diff --git a/frontend/src/Pages/Help/CCManager/OtpEntry.jsx b/frontend/src/Pages/Help/CCManager/OtpEntry.jsx new file mode 100644 index 00000000..12ee31fb --- /dev/null +++ b/frontend/src/Pages/Help/CCManager/OtpEntry.jsx @@ -0,0 +1,85 @@ +import { useState } from 'react' +import { Link, useNavigate } from 'react-router-dom' + +const OtpEntry = () => { + const [otp, setOtp] = useState('') + const navigate = useNavigate() // To programmatically navigate after OTP verification + const VERIFY_OTP_URL = `${import.meta.env.VITE_API_URL}/help/auth/verify-email` + + const handleChange = (e) => { + setOtp(e.target.value) + } + + const handleSubmit = async (e) => { + e.preventDefault() + + try { + const response = await fetch(VERIFY_OTP_URL, { + // Updated here + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ code: otp }), + }) + + const data = await response.json() + + if (response.ok) { + // Navigate to the dashboard if OTP verification is successful + navigate('/help/dashboard') + } else { + // Handle errors, e.g., show an error message + alert(data.message || 'Failed to verify OTP. Please try again.') + } + } catch (error) { + console.error('Error verifying OTP:', error) + alert('An error occurred. Please try again.') + } + } + + return ( +
+

Enter OTP

+

+ Please enter the OTP sent to your registered email. +

+
+
+ + +
+ +
+
+

+ Didn't receive an OTP?{' '} + + Resend OTP + + . +

+
+
+ ) +} + +export default OtpEntry diff --git a/frontend/src/Pages/Help/CCManager/Signup.jsx b/frontend/src/Pages/Help/CCManager/Signup.jsx index 05efd449..22ba9915 100644 --- a/frontend/src/Pages/Help/CCManager/Signup.jsx +++ b/frontend/src/Pages/Help/CCManager/Signup.jsx @@ -5,10 +5,10 @@ const Signup = () => { return (
-
+
-
+
From 0c13a8d87f5fb5c8409e44916337b4cc9e730d85 Mon Sep 17 00:00:00 2001 From: aweeshathavishanka Date: Mon, 14 Oct 2024 21:50:30 +0530 Subject: [PATCH 09/17] Dashboard Complete Step one --- .../src/Components/Help/HelpHeroSection.jsx | 4 +- .../Help/dashboard/CCMDashboardNavBar.jsx | 65 +++++++++++++++++++ .../Components/Help/dashboard/Overview.jsx | 11 ++++ .../Help/dashboard/OverviewCards.jsx | 30 +++++++++ .../dashboard/SupportTicketsViewTable.jsx | 5 ++ .../src/Pages/Help/CCManager/Dashboard.jsx | 14 +++- 6 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 frontend/src/Components/Help/dashboard/CCMDashboardNavBar.jsx create mode 100644 frontend/src/Components/Help/dashboard/Overview.jsx create mode 100644 frontend/src/Components/Help/dashboard/OverviewCards.jsx create mode 100644 frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx diff --git a/frontend/src/Components/Help/HelpHeroSection.jsx b/frontend/src/Components/Help/HelpHeroSection.jsx index f434b1c6..89adf4d5 100644 --- a/frontend/src/Components/Help/HelpHeroSection.jsx +++ b/frontend/src/Components/Help/HelpHeroSection.jsx @@ -43,7 +43,7 @@ const HelpHeroSection = () => {
Learn more { { + return ( +
+ +
+ ) +} + +export default CCMDashboardNavBar diff --git a/frontend/src/Components/Help/dashboard/Overview.jsx b/frontend/src/Components/Help/dashboard/Overview.jsx new file mode 100644 index 00000000..8fd5b818 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/Overview.jsx @@ -0,0 +1,11 @@ +import OverviewCards from './OverviewCards' + +const Overview = () => { + return ( +
+ +
+ ) +} + +export default Overview diff --git a/frontend/src/Components/Help/dashboard/OverviewCards.jsx b/frontend/src/Components/Help/dashboard/OverviewCards.jsx new file mode 100644 index 00000000..04db5b41 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/OverviewCards.jsx @@ -0,0 +1,30 @@ +const OverviewCards = () => { + return ( +
+
+
+
+

Total Tickets

+

+ 100 +

+
+
+

Resolve Tickets

+

+ 100 +

+
+
+

Close Tickets

+

+ 100 +

+
+
+
+
+ ) +} + +export default OverviewCards diff --git a/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx b/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx new file mode 100644 index 00000000..3c809351 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx @@ -0,0 +1,5 @@ +const SupportTicketsViewTable = () => { + return
SupportTicketsViewTable
+} + +export default SupportTicketsViewTable diff --git a/frontend/src/Pages/Help/CCManager/Dashboard.jsx b/frontend/src/Pages/Help/CCManager/Dashboard.jsx index 917e6603..b2765eed 100644 --- a/frontend/src/Pages/Help/CCManager/Dashboard.jsx +++ b/frontend/src/Pages/Help/CCManager/Dashboard.jsx @@ -1,5 +1,17 @@ +import CCMDashboardNavBar from '../../../Components/Help/dashboard/CCMDashboardNavBar' +import Overview from '../../../Components/Help/dashboard/Overview' + const Dashboard = () => { - return
Dashboard
+ return ( +
+ +
+
+ +
+
+
+ ) } export default Dashboard From 5c7fc1291a76f5c3f74e659876081f70e33b337f Mon Sep 17 00:00:00 2001 From: aweeshathavishanka Date: Mon, 14 Oct 2024 23:01:11 +0530 Subject: [PATCH 10/17] =?UTF-8?q?=F0=9F=98=8DSupport=20ticket=20and=20Feed?= =?UTF-8?q?back=20fetching=20Complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 1 + .../Help/dashboard/DownloadReceipt.jsx | 114 ++++++++++ .../Help/dashboard/FeedbackTableView.jsx | 122 +++++++++++ .../Components/Help/dashboard/Overview.jsx | 8 + .../Help/dashboard/SupportTicketPage.jsx | 11 + .../dashboard/SupportTicketsViewTable.jsx | 194 +++++++++++++++++- 6 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 frontend/src/Components/Help/dashboard/DownloadReceipt.jsx create mode 100644 frontend/src/Components/Help/dashboard/FeedbackTableView.jsx create mode 100644 frontend/src/Components/Help/dashboard/SupportTicketPage.jsx diff --git a/frontend/package.json b/frontend/package.json index 69ce11cc..4d1d71d6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "@iconify/react": "^5.0.2", "@intercom/messenger-js-sdk": "^0.0.14", "@nextui-org/react": "^2.4.6", + "@react-pdf/renderer": "^4.0.0", "@stripe/react-stripe-js": "^2.8.0", "@stripe/stripe-js": "^4.4.0", "antd": "^5.21.3", diff --git a/frontend/src/Components/Help/dashboard/DownloadReceipt.jsx b/frontend/src/Components/Help/dashboard/DownloadReceipt.jsx new file mode 100644 index 00000000..a8e9e442 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/DownloadReceipt.jsx @@ -0,0 +1,114 @@ +// DownloadReceipt.jsx + +import React from 'react' +import jsPDFInvoiceTemplate, { OutputType } from 'jspdf-invoice-template' +import farmcartLogo from '../../assets/logo.png' + +const DownloadReceipt = ({ order }) => { + const user = JSON.parse(localStorage.getItem('user')) + + const generatePDF = () => { + // Check if order is defined + if (!order) { + console.error('Order is undefined') + return + } + + const props = { + outputType: OutputType.Save, + returnJsPDFDocObject: true, + fileName: `Invoice_${order._id}`, + orientationLandscape: false, + compress: true, + logo: { + src: farmcartLogo, + type: 'PNG', + width: 72, + height: 12, + margin: { + top: 0, + left: 0, + }, + }, + stamp: { + inAllPages: true, + src: 'https://cdn.me-qr.com/qr/127374536.png?v=1727191883', + type: 'PNG', + width: 20, + height: 20, + margin: { + top: 0, + left: 0, + }, + }, + business: { + name: 'FarmCart Lanka (PVT.) LTD', + address: 'No.78, Malabe, Colombo', + phone: '(+94) 011 34 56 837', + email: 'contact@farmcart.com', + website: 'www.farmcart.com', + }, + contact: { + label: 'Invoice issued for:', + name: `${user.firstname} ${user.lastname}`, + address: `${user.defaultAddress.streetAddress}, ${user.defaultAddress.city}, ${user.defaultAddress.city}, ${user.defaultAddress.zipCode}`, + phone: order.shippingAddress?.phone, // Optional chaining to avoid errors + }, + invoice: { + label: 'Invoice #: ', + num: order._id.slice(-12), + invDate: `Payment Date: ${new Date(order.createdAt).toLocaleDateString()}`, + invGenDate: `Invoice Date: ${new Date().toLocaleDateString()}`, + headerBorder: true, + tableBodyBorder: true, + header: [ + { title: '#', style: { width: 10 } }, + { title: 'Title', style: { width: 30 } }, + { title: 'Price' }, + { title: 'Quantity' }, + { title: 'Unit' }, + { title: 'Total' }, + ], + table: order.orderItems.map((item, index) => [ + index + 1, + item.name, + item.price.toFixed(2), + item.quantity, + 'pcs', // Adjust unit as needed + (item.price * item.quantity).toFixed(2), + ]), + additionalRows: [ + { + col1: 'Total:', + col2: `Rs.${order.totalPrice.toFixed(2)}`, + style: { fontSize: 14 }, + }, + ], + invDescLabel: 'Invoice Note', + invDesc: 'Thank you for your order!', + }, + footer: { + text: 'The invoice is created on a computer and is valid without the signature and stamp.', + }, + pageEnable: true, + pageLabel: 'Page ', + } + + // Create PDF and get the created PDF object + const pdfCreated = jsPDFInvoiceTemplate(props) + + // Save the created PDF + pdfCreated.jsPDFDocObject.save() + } + + return ( + + ) +} + +export default DownloadReceipt diff --git a/frontend/src/Components/Help/dashboard/FeedbackTableView.jsx b/frontend/src/Components/Help/dashboard/FeedbackTableView.jsx new file mode 100644 index 00000000..3b64175e --- /dev/null +++ b/frontend/src/Components/Help/dashboard/FeedbackTableView.jsx @@ -0,0 +1,122 @@ +import { useEffect, useState } from 'react' +import axios from 'axios' + +const FeedbackTableView = () => { + const [feedbacks, setFeedbacks] = useState([]) + const [searchTerm, setSearchTerm] = useState('') + + useEffect(() => { + const fetchFeedbacks = async () => { + try { + const response = await axios.get( + `${import.meta.env.VITE_API_URL}/help/feedback` + ) // Adjusted endpoint + setFeedbacks(response.data) + } catch (error) { + console.error('Error fetching feedbacks:', error) + } + } + + fetchFeedbacks() + }, []) + + const filteredFeedbacks = feedbacks.filter( + (feedback) => + feedback.name?.toLowerCase().includes(searchTerm.toLowerCase()) || + feedback.email?.toLowerCase().includes(searchTerm.toLowerCase()) + ) + + return ( +
+

Feedback Table

+ setSearchTerm(e.target.value)} + className="w-full p-2 mb-4 border" + /> + + + + + + + + + + + + + + + + {filteredFeedbacks.length > 0 ? ( + filteredFeedbacks.map((feedback) => ( + + + + + + + + + + + + )) + ) : ( + + + + )} + +
+ Name + + Email + + Overall Experience + + Response Time Satisfaction + + Solution Satisfaction + + Issue Resolved + + Additional Comments + + Recommend + + Date +
+ {feedback.name || 'N/A'} + + {feedback.email || 'N/A'} + + {feedback.overallExperience} + + {feedback.responseTimeSatisfaction} + + {feedback.solutionSatisfaction} + + {feedback.issueResolved} + + {feedback.additionalComments || 'N/A'} + + {feedback.recommend} + + {new Date( + feedback.createdAt + ).toLocaleDateString()} +
+ No feedback available +
+
+ ) +} + +export default FeedbackTableView diff --git a/frontend/src/Components/Help/dashboard/Overview.jsx b/frontend/src/Components/Help/dashboard/Overview.jsx index 8fd5b818..8169e54f 100644 --- a/frontend/src/Components/Help/dashboard/Overview.jsx +++ b/frontend/src/Components/Help/dashboard/Overview.jsx @@ -1,9 +1,17 @@ +import FeedbackTableView from './FeedbackTableView' import OverviewCards from './OverviewCards' +import SupportTicketsViewTable from './SupportTicketsViewTable' const Overview = () => { return (
+
+ +
+
+ +
) } diff --git a/frontend/src/Components/Help/dashboard/SupportTicketPage.jsx b/frontend/src/Components/Help/dashboard/SupportTicketPage.jsx new file mode 100644 index 00000000..b093b198 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/SupportTicketPage.jsx @@ -0,0 +1,11 @@ +import SupportTicketsViewTable from './SupportTicketsViewTable.jsx' + +const SupportTicketsPage = () => { + return ( +
+ +
+ ) +} + +export default SupportTicketsPage diff --git a/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx b/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx index 3c809351..2cedd3c1 100644 --- a/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx +++ b/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx @@ -1,5 +1,197 @@ +import { useEffect, useState } from 'react' +import axios from 'axios' +import jsPDFInvoiceTemplate, { OutputType } from 'jspdf-invoice-template' + +import 'jspdf-autotable' +import farmcartLogo from '../../../../public/farmcart.svg' + +// Get the API URL from environment variables +const API_URL = import.meta.env.VITE_API_URL + const SupportTicketsViewTable = () => { - return
SupportTicketsViewTable
+ const [tickets, setTickets] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [searchTerm, setSearchTerm] = useState('') + + useEffect(() => { + const fetchTickets = async () => { + try { + const response = await axios.get( + `${API_URL}/help/support-tickets/` + ) + setTickets(response.data) + } catch (err) { + setError(err.message) + } finally { + setLoading(false) + } + } + + fetchTickets() + }, []) + + const handleSearchChange = (e) => { + setSearchTerm(e.target.value) + } + + const generatePDF = (order) => { + const user = JSON.parse(localStorage.getItem('user')) + const props = { + outputType: OutputType.Save, + returnJsPDFDocObject: true, + fileName: `Invoice_${order._id}`, + orientationLandscape: false, + compress: true, + logo: { + src: farmcartLogo, + type: 'PNG', + width: 72, + height: 12, + margin: { + top: 0, + left: 0, + }, + }, + stamp: { + inAllPages: true, + src: 'https://cdn.me-qr.com/qr/127374536.png?v=1727191883', + type: 'PNG', + width: 20, + height: 20, + margin: { + top: 0, + left: 0, + }, + }, + business: { + name: 'FarmCart Lanka (PVT.) LTD', + address: 'No.78, Malabe, Colombo', + phone: '(+94) 011 34 56 837', + email: 'contact@farmcart.com', + website: 'www.farmcart.com', + }, + contact: { + label: 'Invoice issued for:', + name: `${user.firstname} ${user.lastname}`, + address: `${user.defaultAddress.streetAddress}, ${user.defaultAddress.city}, ${user.defaultAddress.zipCode}`, + phone: order.shippingAddress.phone, + }, + invoice: { + label: 'Invoice #: ', + num: order._id.slice(-12), + invDate: `Payment Date: ${new Date(order.createdAt).toLocaleDateString()}`, + invGenDate: `Invoice Date: ${new Date().toLocaleDateString()}`, + headerBorder: true, + tableBodyBorder: true, + header: [ + { title: '#', style: { width: 10 } }, + { title: 'Title', style: { width: 30 } }, + { title: 'Price' }, + { title: 'Quantity' }, + { title: 'Unit' }, + { title: 'Total' }, + ], + table: order.orderItems.map((item, index) => [ + index + 1, + item.name, + item.price.toFixed(2), + item.quantity, + 'pcs', // Adjust unit as needed + (item.price * item.quantity).toFixed(2), + ]), + additionalRows: [ + { + col1: 'Total:', + col2: `Rs.${order.totalPrice.toFixed(2)}`, + style: { fontSize: 14 }, + }, + ], + invDescLabel: 'Invoice Note', + invDesc: 'Thank you for your order!', + }, + footer: { + text: 'The invoice is created on a computer and is valid without the signature and stamp.', + }, + pageEnable: true, + pageLabel: 'Page ', + } + + // Create PDF and get the created PDF object + const pdfCreated = jsPDFInvoiceTemplate(props) + + // Save the created PDF + pdfCreated.jsPDFDocObject.save() + } + + if (loading) return
Loading...
+ if (error) return
Error: {error}
+ + const filteredTickets = tickets.filter((ticket) => + ticket.name.toLowerCase().includes(searchTerm.toLowerCase()) + ) + + return ( +
+
+ +
+ + + + + + + + + + + + + + + + {filteredTickets.map((ticket) => ( + + + + + + + + + + + + ))} + +
NameEmailPhoneSubjectPriority LevelCategoryDescriptionCreated AtActions
{ticket.name}{ticket.email}{ticket.phone} + {ticket.subject} + + {ticket.priorityLevel} + + {ticket.category} + + {ticket.description} + + {new Date(ticket.createdAt).toLocaleString()} + + {/* Assuming each ticket has an associated order object */} + +
+
+ ) } export default SupportTicketsViewTable From b6471d20b9e008bf2db0ac7d1b17caebb64e4a01 Mon Sep 17 00:00:00 2001 From: aweeshathavishanka Date: Tue, 15 Oct 2024 00:59:59 +0530 Subject: [PATCH 11/17] Support ticket Chart Update --- frontend/src/App.jsx | 6 + .../Help/dashboard/DownloadReceipt.jsx | 114 ---------- .../Help/dashboard/FeedbackCharts.jsx | 92 ++++++++ .../Help/dashboard/FeedbackOverviewTable.jsx | 103 +++++++++ .../Help/dashboard/FeedbackTableView.jsx | 122 ----------- .../Components/Help/dashboard/Overview.jsx | 45 +++- .../Help/dashboard/OverviewCards.jsx | 30 --- .../dashboard/SupportTicketOverviewTable.jsx | 110 ++++++++++ .../Help/dashboard/SupportTicketPage.jsx | 11 - .../dashboard/SupportTicketsViewTable.jsx | 197 ------------------ .../Help/dashboard/charts/PieChart.jsx | 74 +++++++ .../Pages/Help/CCManager/SupportTicket.jsx | 5 + 12 files changed, 427 insertions(+), 482 deletions(-) delete mode 100644 frontend/src/Components/Help/dashboard/DownloadReceipt.jsx create mode 100644 frontend/src/Components/Help/dashboard/FeedbackCharts.jsx create mode 100644 frontend/src/Components/Help/dashboard/FeedbackOverviewTable.jsx delete mode 100644 frontend/src/Components/Help/dashboard/FeedbackTableView.jsx delete mode 100644 frontend/src/Components/Help/dashboard/OverviewCards.jsx create mode 100644 frontend/src/Components/Help/dashboard/SupportTicketOverviewTable.jsx delete mode 100644 frontend/src/Components/Help/dashboard/SupportTicketPage.jsx delete mode 100644 frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx create mode 100644 frontend/src/Components/Help/dashboard/charts/PieChart.jsx create mode 100644 frontend/src/Pages/Help/CCManager/SupportTicket.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 62659866..911fb7c6 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -115,6 +115,8 @@ import LogIn from './Pages/Help/CCManager/Login' import OtpEntry from './Pages/Help/CCManager/OtpEntry' import Dashboard from './Pages/Help/CCManager/Dashboard' +import SupportTicketDashboardPage from './Pages/Help/CCManager/SupportTicket' + // Define all routes in a single Router const router = createBrowserRouter( createRoutesFromElements( @@ -312,6 +314,10 @@ const router = createBrowserRouter( } /> } /> } /> + } + /> } /> diff --git a/frontend/src/Components/Help/dashboard/DownloadReceipt.jsx b/frontend/src/Components/Help/dashboard/DownloadReceipt.jsx deleted file mode 100644 index a8e9e442..00000000 --- a/frontend/src/Components/Help/dashboard/DownloadReceipt.jsx +++ /dev/null @@ -1,114 +0,0 @@ -// DownloadReceipt.jsx - -import React from 'react' -import jsPDFInvoiceTemplate, { OutputType } from 'jspdf-invoice-template' -import farmcartLogo from '../../assets/logo.png' - -const DownloadReceipt = ({ order }) => { - const user = JSON.parse(localStorage.getItem('user')) - - const generatePDF = () => { - // Check if order is defined - if (!order) { - console.error('Order is undefined') - return - } - - const props = { - outputType: OutputType.Save, - returnJsPDFDocObject: true, - fileName: `Invoice_${order._id}`, - orientationLandscape: false, - compress: true, - logo: { - src: farmcartLogo, - type: 'PNG', - width: 72, - height: 12, - margin: { - top: 0, - left: 0, - }, - }, - stamp: { - inAllPages: true, - src: 'https://cdn.me-qr.com/qr/127374536.png?v=1727191883', - type: 'PNG', - width: 20, - height: 20, - margin: { - top: 0, - left: 0, - }, - }, - business: { - name: 'FarmCart Lanka (PVT.) LTD', - address: 'No.78, Malabe, Colombo', - phone: '(+94) 011 34 56 837', - email: 'contact@farmcart.com', - website: 'www.farmcart.com', - }, - contact: { - label: 'Invoice issued for:', - name: `${user.firstname} ${user.lastname}`, - address: `${user.defaultAddress.streetAddress}, ${user.defaultAddress.city}, ${user.defaultAddress.city}, ${user.defaultAddress.zipCode}`, - phone: order.shippingAddress?.phone, // Optional chaining to avoid errors - }, - invoice: { - label: 'Invoice #: ', - num: order._id.slice(-12), - invDate: `Payment Date: ${new Date(order.createdAt).toLocaleDateString()}`, - invGenDate: `Invoice Date: ${new Date().toLocaleDateString()}`, - headerBorder: true, - tableBodyBorder: true, - header: [ - { title: '#', style: { width: 10 } }, - { title: 'Title', style: { width: 30 } }, - { title: 'Price' }, - { title: 'Quantity' }, - { title: 'Unit' }, - { title: 'Total' }, - ], - table: order.orderItems.map((item, index) => [ - index + 1, - item.name, - item.price.toFixed(2), - item.quantity, - 'pcs', // Adjust unit as needed - (item.price * item.quantity).toFixed(2), - ]), - additionalRows: [ - { - col1: 'Total:', - col2: `Rs.${order.totalPrice.toFixed(2)}`, - style: { fontSize: 14 }, - }, - ], - invDescLabel: 'Invoice Note', - invDesc: 'Thank you for your order!', - }, - footer: { - text: 'The invoice is created on a computer and is valid without the signature and stamp.', - }, - pageEnable: true, - pageLabel: 'Page ', - } - - // Create PDF and get the created PDF object - const pdfCreated = jsPDFInvoiceTemplate(props) - - // Save the created PDF - pdfCreated.jsPDFDocObject.save() - } - - return ( - - ) -} - -export default DownloadReceipt diff --git a/frontend/src/Components/Help/dashboard/FeedbackCharts.jsx b/frontend/src/Components/Help/dashboard/FeedbackCharts.jsx new file mode 100644 index 00000000..d59de7f4 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/FeedbackCharts.jsx @@ -0,0 +1,92 @@ +import PieChart from './charts/PieChart' +import { useEffect, useState } from 'react' +import axios from 'axios' + +const FeedbackCharts = () => { + const [feedbacks, setFeedbacks] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + useEffect(() => { + const fetchFeedbacks = async () => { + try { + const response = await axios.get( + `${import.meta.env.VITE_API_URL}/help/feedback` + ) + setFeedbacks(response.data) + } catch (err) { + setError(err.message) + } finally { + setLoading(false) + } + } + + fetchFeedbacks() + }, []) + + const getCounts = (feedbacks, field) => { + const counts = Array(5).fill(0) + feedbacks.forEach((feedback) => { + if (feedback[field]) { + counts[feedback[field] - 1]++ // Increment count for the corresponding rating + } + }) + return counts + } + const overallExperienceCounts = getCounts(feedbacks, 'overallExperience') + const responseTimeCounts = getCounts(feedbacks, 'responseTimeSatisfaction') + const solutionSatisfactionCounts = getCounts( + feedbacks, + 'solutionSatisfaction' + ) + + if (loading) return

Loading...

+ if (error) return

Error: {error}

+ return ( +
+ {/* Pie Charts */} +
+
+ +
+
+ +
+
+ +
+
+
+ ) +} + +export default FeedbackCharts diff --git a/frontend/src/Components/Help/dashboard/FeedbackOverviewTable.jsx b/frontend/src/Components/Help/dashboard/FeedbackOverviewTable.jsx new file mode 100644 index 00000000..6d0227e5 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/FeedbackOverviewTable.jsx @@ -0,0 +1,103 @@ +import { useEffect, useState } from 'react' +import axios from 'axios' + +const FeedbackOverviewTable = () => { + const [feedbacks, setFeedbacks] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchFeedbacks = async () => { + try { + const response = await axios.get( + `${import.meta.env.VITE_API_URL}/help/feedback` + ) // Use the environment variable here + setFeedbacks(response.data) + } catch (err) { + setError(err.message) + } finally { + setLoading(false) + } + } + + fetchFeedbacks() + }, []) + + if (loading) return

Loading...

+ if (error) return

Error: {error}

+ + return ( +
+ + + + + + + + + + + + + + + + {feedbacks.map((feedback) => ( + + + + + + + + + + + + ))} + +
+ Name + + Email + + Overall Experience + + Response Time Satisfaction + + Solution Satisfaction + + Issue Resolved + + Additional Comments + + Recommend + + Date +
+ {feedback.name || 'N/A'} + + {feedback.email || 'N/A'} + + {feedback.overallExperience} + + {feedback.responseTimeSatisfaction} + + {feedback.solutionSatisfaction} + + {feedback.issueResolved} + + {feedback.additionalComments || 'N/A'} + + {feedback.recommend} + + {new Date( + feedback.createdAt + ).toLocaleDateString()} +
+
+ ) +} + +export default FeedbackOverviewTable diff --git a/frontend/src/Components/Help/dashboard/FeedbackTableView.jsx b/frontend/src/Components/Help/dashboard/FeedbackTableView.jsx deleted file mode 100644 index 3b64175e..00000000 --- a/frontend/src/Components/Help/dashboard/FeedbackTableView.jsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useEffect, useState } from 'react' -import axios from 'axios' - -const FeedbackTableView = () => { - const [feedbacks, setFeedbacks] = useState([]) - const [searchTerm, setSearchTerm] = useState('') - - useEffect(() => { - const fetchFeedbacks = async () => { - try { - const response = await axios.get( - `${import.meta.env.VITE_API_URL}/help/feedback` - ) // Adjusted endpoint - setFeedbacks(response.data) - } catch (error) { - console.error('Error fetching feedbacks:', error) - } - } - - fetchFeedbacks() - }, []) - - const filteredFeedbacks = feedbacks.filter( - (feedback) => - feedback.name?.toLowerCase().includes(searchTerm.toLowerCase()) || - feedback.email?.toLowerCase().includes(searchTerm.toLowerCase()) - ) - - return ( -
-

Feedback Table

- setSearchTerm(e.target.value)} - className="w-full p-2 mb-4 border" - /> - - - - - - - - - - - - - - - - {filteredFeedbacks.length > 0 ? ( - filteredFeedbacks.map((feedback) => ( - - - - - - - - - - - - )) - ) : ( - - - - )} - -
- Name - - Email - - Overall Experience - - Response Time Satisfaction - - Solution Satisfaction - - Issue Resolved - - Additional Comments - - Recommend - - Date -
- {feedback.name || 'N/A'} - - {feedback.email || 'N/A'} - - {feedback.overallExperience} - - {feedback.responseTimeSatisfaction} - - {feedback.solutionSatisfaction} - - {feedback.issueResolved} - - {feedback.additionalComments || 'N/A'} - - {feedback.recommend} - - {new Date( - feedback.createdAt - ).toLocaleDateString()} -
- No feedback available -
-
- ) -} - -export default FeedbackTableView diff --git a/frontend/src/Components/Help/dashboard/Overview.jsx b/frontend/src/Components/Help/dashboard/Overview.jsx index 8169e54f..8061564a 100644 --- a/frontend/src/Components/Help/dashboard/Overview.jsx +++ b/frontend/src/Components/Help/dashboard/Overview.jsx @@ -1,16 +1,45 @@ -import FeedbackTableView from './FeedbackTableView' -import OverviewCards from './OverviewCards' -import SupportTicketsViewTable from './SupportTicketsViewTable' +import { Link } from 'react-router-dom' +import FeedbackOverviewTable from './FeedbackOverviewTable' +import SupportTicketOverviewTable from './SupportTicketOverviewTable' +import FeedbackCharts from './FeedbackCharts' const Overview = () => { return (
- -
- +
+

+ Feedback Analytics +

+
-
- + {/* Support Ticket and Feedback Overview */} +
+
+

Feedbacks

+ +
+ +
+
+
+
+

+ Support Tickets +

+ +
+ +
+
+
) diff --git a/frontend/src/Components/Help/dashboard/OverviewCards.jsx b/frontend/src/Components/Help/dashboard/OverviewCards.jsx deleted file mode 100644 index 04db5b41..00000000 --- a/frontend/src/Components/Help/dashboard/OverviewCards.jsx +++ /dev/null @@ -1,30 +0,0 @@ -const OverviewCards = () => { - return ( -
-
-
-
-

Total Tickets

-

- 100 -

-
-
-

Resolve Tickets

-

- 100 -

-
-
-

Close Tickets

-

- 100 -

-
-
-
-
- ) -} - -export default OverviewCards diff --git a/frontend/src/Components/Help/dashboard/SupportTicketOverviewTable.jsx b/frontend/src/Components/Help/dashboard/SupportTicketOverviewTable.jsx new file mode 100644 index 00000000..7e0e4777 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/SupportTicketOverviewTable.jsx @@ -0,0 +1,110 @@ +import { useEffect, useState } from 'react' +import axios from 'axios' + +const SupportTicketOverviewTable = () => { + const [tickets, setTickets] = useState([]) // Initialize tickets as an empty array + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchTickets = async () => { + try { + const response = await axios.get( + `${import.meta.env.VITE_API_URL}/help/support-tickets` + ) + console.log('Fetched Tickets:', response.data) // Log fetched tickets + setTickets(response.data) + } catch (err) { + console.error('Error fetching tickets:', err) // Log error details + setError(err.message) + } finally { + setLoading(false) + } + } + + fetchTickets() + }, []) + + if (loading) return

Loading...

+ if (error) return

Error: {error}

+ + return ( +
+ + + + + + + + + + + + + + + {Array.isArray(tickets) && tickets.length > 0 ? ( + tickets.map((ticket) => ( + + + + + + + + + + + )) + ) : ( + + + + )} + +
+ Name + + Email + + Phone + + Subject + + Priority Level + + Category + + Description + + Date +
+ {ticket.name || 'N/A'} + + {ticket.email || 'N/A'} + + {ticket.phone || 'N/A'} + + {ticket.subject || 'N/A'} + + {ticket.priorityLevel} + + {ticket.category} + + {ticket.description || 'N/A'} + + {new Date( + ticket.createdAt + ).toLocaleDateString()} +
+ No tickets found +
+
+ ) +} + +export default SupportTicketOverviewTable diff --git a/frontend/src/Components/Help/dashboard/SupportTicketPage.jsx b/frontend/src/Components/Help/dashboard/SupportTicketPage.jsx deleted file mode 100644 index b093b198..00000000 --- a/frontend/src/Components/Help/dashboard/SupportTicketPage.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import SupportTicketsViewTable from './SupportTicketsViewTable.jsx' - -const SupportTicketsPage = () => { - return ( -
- -
- ) -} - -export default SupportTicketsPage diff --git a/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx b/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx deleted file mode 100644 index 2cedd3c1..00000000 --- a/frontend/src/Components/Help/dashboard/SupportTicketsViewTable.jsx +++ /dev/null @@ -1,197 +0,0 @@ -import { useEffect, useState } from 'react' -import axios from 'axios' -import jsPDFInvoiceTemplate, { OutputType } from 'jspdf-invoice-template' - -import 'jspdf-autotable' -import farmcartLogo from '../../../../public/farmcart.svg' - -// Get the API URL from environment variables -const API_URL = import.meta.env.VITE_API_URL - -const SupportTicketsViewTable = () => { - const [tickets, setTickets] = useState([]) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const [searchTerm, setSearchTerm] = useState('') - - useEffect(() => { - const fetchTickets = async () => { - try { - const response = await axios.get( - `${API_URL}/help/support-tickets/` - ) - setTickets(response.data) - } catch (err) { - setError(err.message) - } finally { - setLoading(false) - } - } - - fetchTickets() - }, []) - - const handleSearchChange = (e) => { - setSearchTerm(e.target.value) - } - - const generatePDF = (order) => { - const user = JSON.parse(localStorage.getItem('user')) - const props = { - outputType: OutputType.Save, - returnJsPDFDocObject: true, - fileName: `Invoice_${order._id}`, - orientationLandscape: false, - compress: true, - logo: { - src: farmcartLogo, - type: 'PNG', - width: 72, - height: 12, - margin: { - top: 0, - left: 0, - }, - }, - stamp: { - inAllPages: true, - src: 'https://cdn.me-qr.com/qr/127374536.png?v=1727191883', - type: 'PNG', - width: 20, - height: 20, - margin: { - top: 0, - left: 0, - }, - }, - business: { - name: 'FarmCart Lanka (PVT.) LTD', - address: 'No.78, Malabe, Colombo', - phone: '(+94) 011 34 56 837', - email: 'contact@farmcart.com', - website: 'www.farmcart.com', - }, - contact: { - label: 'Invoice issued for:', - name: `${user.firstname} ${user.lastname}`, - address: `${user.defaultAddress.streetAddress}, ${user.defaultAddress.city}, ${user.defaultAddress.zipCode}`, - phone: order.shippingAddress.phone, - }, - invoice: { - label: 'Invoice #: ', - num: order._id.slice(-12), - invDate: `Payment Date: ${new Date(order.createdAt).toLocaleDateString()}`, - invGenDate: `Invoice Date: ${new Date().toLocaleDateString()}`, - headerBorder: true, - tableBodyBorder: true, - header: [ - { title: '#', style: { width: 10 } }, - { title: 'Title', style: { width: 30 } }, - { title: 'Price' }, - { title: 'Quantity' }, - { title: 'Unit' }, - { title: 'Total' }, - ], - table: order.orderItems.map((item, index) => [ - index + 1, - item.name, - item.price.toFixed(2), - item.quantity, - 'pcs', // Adjust unit as needed - (item.price * item.quantity).toFixed(2), - ]), - additionalRows: [ - { - col1: 'Total:', - col2: `Rs.${order.totalPrice.toFixed(2)}`, - style: { fontSize: 14 }, - }, - ], - invDescLabel: 'Invoice Note', - invDesc: 'Thank you for your order!', - }, - footer: { - text: 'The invoice is created on a computer and is valid without the signature and stamp.', - }, - pageEnable: true, - pageLabel: 'Page ', - } - - // Create PDF and get the created PDF object - const pdfCreated = jsPDFInvoiceTemplate(props) - - // Save the created PDF - pdfCreated.jsPDFDocObject.save() - } - - if (loading) return
Loading...
- if (error) return
Error: {error}
- - const filteredTickets = tickets.filter((ticket) => - ticket.name.toLowerCase().includes(searchTerm.toLowerCase()) - ) - - return ( -
-
- -
- - - - - - - - - - - - - - - - {filteredTickets.map((ticket) => ( - - - - - - - - - - - - ))} - -
NameEmailPhoneSubjectPriority LevelCategoryDescriptionCreated AtActions
{ticket.name}{ticket.email}{ticket.phone} - {ticket.subject} - - {ticket.priorityLevel} - - {ticket.category} - - {ticket.description} - - {new Date(ticket.createdAt).toLocaleString()} - - {/* Assuming each ticket has an associated order object */} - -
-
- ) -} - -export default SupportTicketsViewTable diff --git a/frontend/src/Components/Help/dashboard/charts/PieChart.jsx b/frontend/src/Components/Help/dashboard/charts/PieChart.jsx new file mode 100644 index 00000000..c227b5af --- /dev/null +++ b/frontend/src/Components/Help/dashboard/charts/PieChart.jsx @@ -0,0 +1,74 @@ +import { useEffect, useRef } from 'react' +import { Chart } from 'chart.js' +import PropTypes from 'prop-types' + +const PieChart = ({ data, labels, title, width = 300, height = 300 }) => { + const chartRef = useRef(null) // Create a ref to store the chart instance + + useEffect(() => { + // Destroy the previous chart instance if it exists + if (chartRef.current) { + chartRef.current.destroy() + } + + const ctx = document.getElementById(title).getContext('2d') + chartRef.current = new Chart(ctx, { + type: 'pie', + data: { + labels: labels, + datasets: [ + { + data: data, + backgroundColor: [ + '#FF6384', + '#36A2EB', + '#FFCE56', + '#4BC0C0', + '#9966FF', + ], + }, + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'top', + }, + title: { + display: true, + text: title, + }, + }, + }, + }) + + // Cleanup function to destroy the chart instance on unmount + return () => { + if (chartRef.current) { + chartRef.current.destroy() + } + } + }, [data, labels, title]) + + return ( + + ) +} + +// PropTypes validation +PieChart.propTypes = { + data: PropTypes.arrayOf(PropTypes.number).isRequired, + labels: PropTypes.arrayOf(PropTypes.string).isRequired, + title: PropTypes.string.isRequired, + width: PropTypes.number, + height: PropTypes.number, +} + +export default PieChart diff --git a/frontend/src/Pages/Help/CCManager/SupportTicket.jsx b/frontend/src/Pages/Help/CCManager/SupportTicket.jsx new file mode 100644 index 00000000..87099c54 --- /dev/null +++ b/frontend/src/Pages/Help/CCManager/SupportTicket.jsx @@ -0,0 +1,5 @@ +const SupportTicketDashboardPage = () => { + return
+} + +export default SupportTicketDashboardPage From 7275a4d3c32416d4c466af4aca59a69b158c6602 Mon Sep 17 00:00:00 2001 From: aweeshathavishanka Date: Tue, 15 Oct 2024 01:21:06 +0530 Subject: [PATCH 12/17] Support Ticket Basic Overview Complete --- .../Help/dashboard/SupportTicketCharts.jsx | 56 +++++++++ .../dashboard/SupportTicketFullViewTable.jsx | 106 ++++++++++++++++++ .../charts/TicketsByCategoryChart.jsx | 59 ++++++++++ .../dashboard/charts/TicketsByDateChart.jsx | 60 ++++++++++ .../charts/TicketsByPriorityChart.jsx | 61 ++++++++++ .../Pages/Help/CCManager/SupportTicket.jsx | 12 +- 6 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 frontend/src/Components/Help/dashboard/SupportTicketCharts.jsx create mode 100644 frontend/src/Components/Help/dashboard/SupportTicketFullViewTable.jsx create mode 100644 frontend/src/Components/Help/dashboard/charts/TicketsByCategoryChart.jsx create mode 100644 frontend/src/Components/Help/dashboard/charts/TicketsByDateChart.jsx create mode 100644 frontend/src/Components/Help/dashboard/charts/TicketsByPriorityChart.jsx diff --git a/frontend/src/Components/Help/dashboard/SupportTicketCharts.jsx b/frontend/src/Components/Help/dashboard/SupportTicketCharts.jsx new file mode 100644 index 00000000..7786c3f9 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/SupportTicketCharts.jsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from 'react' +import TicketsByDateChart from './charts/TicketsByDateChart' +import TicketsByPriorityChart from './charts/TicketsByPriorityChart' +import TicketsByCategoryChart from './charts/TicketsByCategoryChart' + +const SupportTicketCharts = () => { + const [tickets, setTickets] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchTickets = async () => { + try { + const response = await fetch( + `${import.meta.env.VITE_API_URL}/help/support-tickets` + ) + if (!response.ok) { + throw new Error('Failed to fetch tickets') + } + const data = await response.json() + setTickets(data) + } catch (err) { + setError(err.message) + } finally { + setLoading(false) + } + } + + fetchTickets() + }, []) + + if (loading) { + return

Loading...

+ } + + if (error) { + return

Error: {error}

+ } + return ( +
+
+
+ +
+
+ +
+
+ +
+
+
+ ) +} + +export default SupportTicketCharts diff --git a/frontend/src/Components/Help/dashboard/SupportTicketFullViewTable.jsx b/frontend/src/Components/Help/dashboard/SupportTicketFullViewTable.jsx new file mode 100644 index 00000000..0573357d --- /dev/null +++ b/frontend/src/Components/Help/dashboard/SupportTicketFullViewTable.jsx @@ -0,0 +1,106 @@ +import { useEffect, useState } from 'react' + +const SupportTicketFullViewTable = () => { + const [tickets, setTickets] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchTickets = async () => { + try { + const response = await fetch( + `${import.meta.env.VITE_API_URL}/help/support-tickets` + ) + if (!response.ok) { + throw new Error('Failed to fetch tickets') + } + const data = await response.json() + setTickets(data) + } catch (err) { + setError(err.message) + } finally { + setLoading(false) + } + } + + fetchTickets() + }, []) + + if (loading) { + return

Loading...

+ } + + if (error) { + return

Error: {error}

+ } + + return ( +
+ + + + + + + + + + + + + + + {tickets.map((ticket, index) => ( + + + + + + + + + + + ))} + +
+ Name + + Email + + Phone + + Subject + + Priority Level + + Category + + Description + + Created At +
+ {ticket.name} + + {ticket.email} + + {ticket.phone} + + {ticket.subject} + + {ticket.priorityLevel} + + {ticket.category} + + {ticket.description} + + {new Date(ticket.createdAt).toLocaleString()} +
+
+ ) +} + +export default SupportTicketFullViewTable diff --git a/frontend/src/Components/Help/dashboard/charts/TicketsByCategoryChart.jsx b/frontend/src/Components/Help/dashboard/charts/TicketsByCategoryChart.jsx new file mode 100644 index 00000000..ee9083c7 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/charts/TicketsByCategoryChart.jsx @@ -0,0 +1,59 @@ +import PropTypes from 'prop-types' +import { Bar } from 'react-chartjs-2' +import { useEffect, useState } from 'react' + +const TicketsByCategoryChart = ({ tickets }) => { + const [data, setData] = useState({ + labels: [], + datasets: [], + }) + + useEffect(() => { + const ticketCountByCategory = {} + + tickets.forEach((ticket) => { + const category = ticket.category + ticketCountByCategory[category] = + (ticketCountByCategory[category] || 0) + 1 + }) + + setData({ + labels: Object.keys(ticketCountByCategory), + datasets: [ + { + label: 'Tickets by Category', + data: Object.values(ticketCountByCategory), + backgroundColor: 'rgba(54, 162, 235, 0.6)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 1, + }, + ], + }) + }, [tickets]) + + // Check if data is valid before rendering + if (!data.labels.length) { + return ( +

+ No ticket data available. +

+ ) + } + + return ( +
+

Tickets by Category

+ +
+ ) +} + +TicketsByCategoryChart.propTypes = { + tickets: PropTypes.arrayOf( + PropTypes.shape({ + category: PropTypes.string.isRequired, + }) + ).isRequired, +} + +export default TicketsByCategoryChart diff --git a/frontend/src/Components/Help/dashboard/charts/TicketsByDateChart.jsx b/frontend/src/Components/Help/dashboard/charts/TicketsByDateChart.jsx new file mode 100644 index 00000000..eceb120c --- /dev/null +++ b/frontend/src/Components/Help/dashboard/charts/TicketsByDateChart.jsx @@ -0,0 +1,60 @@ +import PropTypes from 'prop-types' +import { Bar } from 'react-chartjs-2' +import { useEffect, useState } from 'react' + +const TicketsByDateChart = ({ tickets }) => { + const [data, setData] = useState({ + labels: [], + datasets: [], + }) + + useEffect(() => { + const ticketCountByDate = {} + + tickets.forEach((ticket) => { + const date = new Date(ticket.createdAt).toLocaleDateString() + ticketCountByDate[date] = (ticketCountByDate[date] || 0) + 1 + }) + + setData({ + labels: Object.keys(ticketCountByDate), + datasets: [ + { + label: 'Tickets Created', + data: Object.values(ticketCountByDate), + backgroundColor: 'rgba(75, 192, 192, 0.6)', + borderColor: 'rgba(75, 192, 192, 1)', + borderWidth: 1, + }, + ], + }) + }, [tickets]) + + // Check if data is valid before rendering + if (!data.labels.length) { + return ( +

+ No ticket data available. +

+ ) + } + + return ( +
+

+ Tickets Created Over Time +

+ +
+ ) +} + +TicketsByDateChart.propTypes = { + tickets: PropTypes.arrayOf( + PropTypes.shape({ + createdAt: PropTypes.string.isRequired, + }) + ).isRequired, +} + +export default TicketsByDateChart diff --git a/frontend/src/Components/Help/dashboard/charts/TicketsByPriorityChart.jsx b/frontend/src/Components/Help/dashboard/charts/TicketsByPriorityChart.jsx new file mode 100644 index 00000000..abfaee33 --- /dev/null +++ b/frontend/src/Components/Help/dashboard/charts/TicketsByPriorityChart.jsx @@ -0,0 +1,61 @@ +import PropTypes from 'prop-types' +import { Bar } from 'react-chartjs-2' +import { useEffect, useState } from 'react' + +const TicketsByPriorityChart = ({ tickets }) => { + const [data, setData] = useState({ + labels: [], + datasets: [], + }) + + useEffect(() => { + const ticketCountByPriority = {} + + tickets.forEach((ticket) => { + const priority = ticket.priorityLevel + ticketCountByPriority[priority] = + (ticketCountByPriority[priority] || 0) + 1 + }) + + setData({ + labels: Object.keys(ticketCountByPriority), + datasets: [ + { + label: 'Tickets by Priority', + data: Object.values(ticketCountByPriority), + backgroundColor: 'rgba(255, 99, 132, 0.6)', + borderColor: 'rgba(255, 99, 132, 1)', + borderWidth: 1, + }, + ], + }) + }, [tickets]) + + // Check if data is valid before rendering + if (!data.labels.length) { + return ( +

+ No ticket data available. +

+ ) + } + + return ( +
+

+ Tickets by Priority Level +

+ +
+ ) +} + +TicketsByPriorityChart.propTypes = { + tickets: PropTypes.arrayOf( + PropTypes.shape({ + priorityLevel: PropTypes.string.isRequired, + }) + ).isRequired, +} + +export default TicketsByPriorityChart diff --git a/frontend/src/Pages/Help/CCManager/SupportTicket.jsx b/frontend/src/Pages/Help/CCManager/SupportTicket.jsx index 87099c54..aec530de 100644 --- a/frontend/src/Pages/Help/CCManager/SupportTicket.jsx +++ b/frontend/src/Pages/Help/CCManager/SupportTicket.jsx @@ -1,5 +1,15 @@ +import SupportTicketCharts from '../../../Components/Help/dashboard/SupportTicketCharts' +import SupportTicketFullViewTable from '../../../Components/Help/dashboard/SupportTicketFullViewTable' + const SupportTicketDashboardPage = () => { - return
+ return ( +
+
+ +
+ +
+ ) } export default SupportTicketDashboardPage From 9dc9ab0782a12af4c20e30ab3e0c050bdab7aba0 Mon Sep 17 00:00:00 2001 From: aweeshathavishanka Date: Tue, 15 Oct 2024 01:58:20 +0530 Subject: [PATCH 13/17] Support Ticket Complete --- frontend/src/App.jsx | 5 + .../dashboard/SupportTicketFullViewTable.jsx | 257 +++++++++++++----- .../Help/CCManager/FeedbackDashboard.jsx | 5 + .../Pages/Help/CCManager/SupportTicket.jsx | 7 +- 4 files changed, 211 insertions(+), 63 deletions(-) create mode 100644 frontend/src/Pages/Help/CCManager/FeedbackDashboard.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 911fb7c6..2460f336 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -116,6 +116,7 @@ import OtpEntry from './Pages/Help/CCManager/OtpEntry' import Dashboard from './Pages/Help/CCManager/Dashboard' import SupportTicketDashboardPage from './Pages/Help/CCManager/SupportTicket' +import FeedbackDashboard from './Pages/Help/CCManager/FeedbackDashboard' // Define all routes in a single Router const router = createBrowserRouter( @@ -314,6 +315,10 @@ const router = createBrowserRouter( } /> } /> } /> + } + /> } diff --git a/frontend/src/Components/Help/dashboard/SupportTicketFullViewTable.jsx b/frontend/src/Components/Help/dashboard/SupportTicketFullViewTable.jsx index 0573357d..bc08573e 100644 --- a/frontend/src/Components/Help/dashboard/SupportTicketFullViewTable.jsx +++ b/frontend/src/Components/Help/dashboard/SupportTicketFullViewTable.jsx @@ -1,9 +1,15 @@ import { useEffect, useState } from 'react' +import html2canvas from 'html2canvas' +import jsPDF from 'jspdf' +import logo from '../../../../public/logoIcon.png' // Make sure this path is correct const SupportTicketFullViewTable = () => { const [tickets, setTickets] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + const [currentPage, setCurrentPage] = useState(1) + const [ticketsPerPage] = useState(5) // Show 5 tickets per page + const [searchTerm, setSearchTerm] = useState('') useEffect(() => { const fetchTickets = async () => { @@ -26,6 +32,71 @@ const SupportTicketFullViewTable = () => { fetchTickets() }, []) + // Handle Pagination + const indexOfLastTicket = currentPage * ticketsPerPage + const indexOfFirstTicket = indexOfLastTicket - ticketsPerPage + const currentTickets = tickets.slice(indexOfFirstTicket, indexOfLastTicket) + const totalPages = Math.ceil(tickets.length / ticketsPerPage) + + const paginate = (pageNumber) => setCurrentPage(pageNumber) + + // Handle Search + const filteredTickets = currentTickets.filter( + (ticket) => + ticket.name.toLowerCase().includes(searchTerm.toLowerCase()) || + ticket.email.toLowerCase().includes(searchTerm.toLowerCase()) || + ticket.subject.toLowerCase().includes(searchTerm.toLowerCase()) + ) + + // Generate PDF Report + const generateReport = () => { + const input = document.getElementById('table-to-pdf') + + html2canvas(input).then((canvas) => { + const imgData = canvas.toDataURL('image/png') + const pdf = new jsPDF() + + // Load the logo and add it to the PDF + const logoImage = new Image() + logoImage.src = logo // Use the imported logo + + logoImage.onload = () => { + pdf.addImage(logoImage, 'PNG', 10, 10, 50, 20) // Adjust position and size as needed + + const imgWidth = 190 + const pageHeight = pdf.internal.pageSize.height + const imgHeight = (canvas.height * imgWidth) / canvas.width + let heightLeft = imgHeight + let position = 30 // Adjust to position below logo + + pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight) + heightLeft -= pageHeight + + while (heightLeft >= 0) { + position = heightLeft - imgHeight + pdf.addPage() + pdf.addImage(logoImage, 'PNG', 10, 10, 50, 50) // Add logo to each page + pdf.addImage( + imgData, + 'PNG', + 10, + position, + imgWidth, + imgHeight + ) + heightLeft -= pageHeight + } + + pdf.save('support_ticket_report.pdf') + } + + logoImage.onerror = () => { + console.error('Failed to load logo image.') + } + }) + } + + // Loading and Error States if (loading) { return

Loading...

} @@ -35,70 +106,132 @@ const SupportTicketFullViewTable = () => { } return ( -
- - - - - - - - - - - - - - - {tickets.map((ticket, index) => ( - - - - - - - - - +
+ {/* Search Input */} +
+ setSearchTerm(e.target.value)} + /> + +
+ + {/* Tickets Table */} +
+
- Name - - Email - - Phone - - Subject - - Priority Level - - Category - - Description - - Created At -
- {ticket.name} - - {ticket.email} - - {ticket.phone} - - {ticket.subject} - - {ticket.priorityLevel} - - {ticket.category} - - {ticket.description} - - {new Date(ticket.createdAt).toLocaleString()} -
+ + + {[ + 'Name', + 'Email', + 'Phone', + 'Subject', + 'Priority Level', + 'Category', + 'Description', + 'Created At', + ].map((header) => ( + + ))} + + + {filteredTickets.map((ticket, index) => ( + + + + + + + + + + + ))} + +
+ {header} +
+ {ticket.name} + + {ticket.email} + + {ticket.phone} + + {ticket.subject} + + {ticket.priorityLevel} + + {ticket.category} + + {ticket.description} + + {new Date( + ticket.createdAt + ).toLocaleString()} +
+
+ + {/* Pagination Section */} +
) } diff --git a/frontend/src/Pages/Help/CCManager/FeedbackDashboard.jsx b/frontend/src/Pages/Help/CCManager/FeedbackDashboard.jsx new file mode 100644 index 00000000..d419bb9c --- /dev/null +++ b/frontend/src/Pages/Help/CCManager/FeedbackDashboard.jsx @@ -0,0 +1,5 @@ +const FeedbackDashboard = () => { + return
FeedbackDashboard
+} + +export default FeedbackDashboard diff --git a/frontend/src/Pages/Help/CCManager/SupportTicket.jsx b/frontend/src/Pages/Help/CCManager/SupportTicket.jsx index aec530de..41f70a9b 100644 --- a/frontend/src/Pages/Help/CCManager/SupportTicket.jsx +++ b/frontend/src/Pages/Help/CCManager/SupportTicket.jsx @@ -1,13 +1,18 @@ import SupportTicketCharts from '../../../Components/Help/dashboard/SupportTicketCharts' import SupportTicketFullViewTable from '../../../Components/Help/dashboard/SupportTicketFullViewTable' +import CCMDashboardNavBar from '../../../Components/Help/dashboard/CCMDashboardNavBar' const SupportTicketDashboardPage = () => { return (
+ +
+ +
+

All Support Tickets

-
) } From 347452f85779044fa7bc043b8d23340ebd476694 Mon Sep 17 00:00:00 2001 From: aweeshathavishanka Date: Tue, 15 Oct 2024 02:07:04 +0530 Subject: [PATCH 14/17] Feedback DashboardComplete --- .../Help/dashboard/FeedbackDataViewTable.jsx | 245 ++++++++++++++++++ .../Help/CCManager/FeedbackDashboard.jsx | 12 +- 2 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 frontend/src/Components/Help/dashboard/FeedbackDataViewTable.jsx diff --git a/frontend/src/Components/Help/dashboard/FeedbackDataViewTable.jsx b/frontend/src/Components/Help/dashboard/FeedbackDataViewTable.jsx new file mode 100644 index 00000000..ce4e4ddc --- /dev/null +++ b/frontend/src/Components/Help/dashboard/FeedbackDataViewTable.jsx @@ -0,0 +1,245 @@ +import { useEffect, useState } from 'react' +import html2canvas from 'html2canvas' +import jsPDF from 'jspdf' +import logo from '../../../../public/logoIcon.png' // Ensure this path is correct + +const FeedbackDataViewTable = () => { + const [feedbacks, setFeedbacks] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [currentPage, setCurrentPage] = useState(1) + const [feedbacksPerPage] = useState(5) // Show 5 feedbacks per page + const [searchTerm, setSearchTerm] = useState('') + + useEffect(() => { + const fetchFeedbacks = async () => { + try { + const response = await fetch( + `${import.meta.env.VITE_API_URL}/help/feedback` + ) + if (!response.ok) { + throw new Error('Failed to fetch feedbacks') + } + const data = await response.json() + setFeedbacks(data) + } catch (err) { + setError(err.message) + } finally { + setLoading(false) + } + } + + fetchFeedbacks() + }, []) + + // Handle Pagination + const indexOfLastFeedback = currentPage * feedbacksPerPage + const indexOfFirstFeedback = indexOfLastFeedback - feedbacksPerPage + const currentFeedbacks = feedbacks.slice( + indexOfFirstFeedback, + indexOfLastFeedback + ) + const totalPages = Math.ceil(feedbacks.length / feedbacksPerPage) + + const paginate = (pageNumber) => setCurrentPage(pageNumber) + + // Handle Search + const filteredFeedbacks = currentFeedbacks.filter( + (feedback) => + feedback.name?.toLowerCase().includes(searchTerm.toLowerCase()) || + feedback.email?.toLowerCase().includes(searchTerm.toLowerCase()) + ) + + // Generate PDF Report + const generateReport = () => { + const input = document.getElementById('table-to-pdf') + + html2canvas(input).then((canvas) => { + const imgData = canvas.toDataURL('image/png') + const pdf = new jsPDF() + + // Load the logo and add it to the PDF + const logoImage = new Image() + logoImage.src = logo // Use the imported logo + + logoImage.onload = () => { + pdf.addImage(logoImage, 'PNG', 10, 10, 50, 20) // Adjust position and size as needed + + const imgWidth = 190 + const pageHeight = pdf.internal.pageSize.height + const imgHeight = (canvas.height * imgWidth) / canvas.width + let heightLeft = imgHeight + let position = 30 // Adjust to position below logo + + pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight) + heightLeft -= pageHeight + + while (heightLeft >= 0) { + position = heightLeft - imgHeight + pdf.addPage() + pdf.addImage(logoImage, 'PNG', 10, 10, 50, 20) // Add logo to each page + pdf.addImage( + imgData, + 'PNG', + 10, + position, + imgWidth, + imgHeight + ) + heightLeft -= pageHeight + } + + pdf.save('feedback_report.pdf') + } + + logoImage.onerror = () => { + console.error('Failed to load logo image.') + } + }) + } + + // Loading and Error States + if (loading) { + return

Loading...

+ } + + if (error) { + return

Error: {error}

+ } + + return ( +
+ {/* Search Input */} +
+ setSearchTerm(e.target.value)} + /> + +
+ + {/* Feedbacks Table */} +
+ + + + {[ + 'Name', + 'Email', + 'Overall Experience', + 'Response Time Satisfaction', + 'Solution Satisfaction', + 'Issue Resolved', + 'Additional Comments', + 'Recommend', + 'Created At', + ].map((header) => ( + + ))} + + + + {filteredFeedbacks.map((feedback, index) => ( + + + + + + + + + + + + ))} + +
+ {header} +
+ {feedback.name} + + {feedback.email} + + {feedback.overallExperience} + + {feedback.responseTimeSatisfaction} + + {feedback.solutionSatisfaction} + + {feedback.issueResolved} + + {feedback.additionalComments} + + {feedback.recommend} + + {new Date( + feedback.createdAt + ).toLocaleString()} +
+
+ + {/* Pagination Section */} + +
+ ) +} + +export default FeedbackDataViewTable diff --git a/frontend/src/Pages/Help/CCManager/FeedbackDashboard.jsx b/frontend/src/Pages/Help/CCManager/FeedbackDashboard.jsx index d419bb9c..1735d8eb 100644 --- a/frontend/src/Pages/Help/CCManager/FeedbackDashboard.jsx +++ b/frontend/src/Pages/Help/CCManager/FeedbackDashboard.jsx @@ -1,5 +1,15 @@ +import FeedbackCharts from '../../../Components/Help/dashboard/FeedbackCharts' +import FeedbackDataViewTable from '../../../Components/Help/dashboard/FeedbackDataViewTable' + const FeedbackDashboard = () => { - return
FeedbackDashboard
+ return ( +
+ +
+ +
+
+ ) } export default FeedbackDashboard From b9ab6815047a9f4df78441411faed33ca045ab1f Mon Sep 17 00:00:00 2001 From: aweeshathavishanka Date: Tue, 15 Oct 2024 02:28:44 +0530 Subject: [PATCH 15/17] UserProfile Not Completed --- backend/controllers/Help/ccmController.js | 70 +++++++++++++++++++ backend/routes/Help/ccmRoutes.js | 27 +++++++ backend/server.js | 2 + frontend/src/App.jsx | 5 ++ .../Help/dashboard/CCMDashboardNavBar.jsx | 10 +-- .../dashboard/CustomerCareManagerProfile.jsx | 68 ++++++++++++++++++ .../src/Pages/Help/CCManager/CCMProfile.jsx | 11 +++ .../Help/CCManager/FeedbackDashboard.jsx | 12 ++-- 8 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 backend/controllers/Help/ccmController.js create mode 100644 backend/routes/Help/ccmRoutes.js create mode 100644 frontend/src/Components/Help/dashboard/CustomerCareManagerProfile.jsx create mode 100644 frontend/src/Pages/Help/CCManager/CCMProfile.jsx diff --git a/backend/controllers/Help/ccmController.js b/backend/controllers/Help/ccmController.js new file mode 100644 index 00000000..76e04ffd --- /dev/null +++ b/backend/controllers/Help/ccmController.js @@ -0,0 +1,70 @@ +import CCMUser from '../../models/Help/ccmUser.model.js' + +// Create a new Customer Care Manager +export const createCCM = async (req, res) => { + try { + const { email, password, firstName, lastName } = req.body + const newUser = await CCMUser.create({ + email, + password, + firstName, + lastName, + }) + res.status(201).json(newUser) + } catch (error) { + res.status(400).json({ message: error.message }) + } +} + +// Get all Customer Care Managers +export const getAllCCMs = async (req, res) => { + try { + const ccmUsers = await CCMUser.find() + res.status(200).json(ccmUsers) + } catch (error) { + res.status(500).json({ message: error.message }) + } +} + +// Get a specific Customer Care Manager by ID +export const getCCMById = async (req, res) => { + try { + const ccmUser = await CCMUser.findById(req.params.id) + if (!ccmUser) { + return res.status(404).json({ message: 'User not found' }) + } + res.status(200).json(ccmUser) + } catch (error) { + res.status(500).json({ message: error.message }) + } +} + +// Update a Customer Care Manager +export const updateCCM = async (req, res) => { + try { + const ccmUser = await CCMUser.findByIdAndUpdate( + req.params.id, + req.body, + { new: true } + ) + if (!ccmUser) { + return res.status(404).json({ message: 'User not found' }) + } + res.status(200).json(ccmUser) + } catch (error) { + res.status(400).json({ message: error.message }) + } +} + +// Delete a Customer Care Manager +export const deleteCCM = async (req, res) => { + try { + const ccmUser = await CCMUser.findByIdAndDelete(req.params.id) + if (!ccmUser) { + return res.status(404).json({ message: 'User not found' }) + } + res.status(204).send() // No content + } catch (error) { + res.status(500).json({ message: error.message }) + } +} diff --git a/backend/routes/Help/ccmRoutes.js b/backend/routes/Help/ccmRoutes.js new file mode 100644 index 00000000..6f97abc8 --- /dev/null +++ b/backend/routes/Help/ccmRoutes.js @@ -0,0 +1,27 @@ +import express from 'express' +import { + createCCM, + getAllCCMs, + getCCMById, + updateCCM, + deleteCCM, +} from '../controllers/ccmController.js' + +const router = express.Router() + +// Create a new Customer Care Manager +router.post('/', createCCM) + +// Get all Customer Care Managers +router.get('/', getAllCCMs) + +// Get a specific Customer Care Manager by ID +router.get('/:id', getCCMById) + +// Update a Customer Care Manager +router.put('/:id', updateCCM) + +// Delete a Customer Care Manager +router.delete('/:id', deleteCCM) + +export default router diff --git a/backend/server.js b/backend/server.js index 734b3087..ec032805 100644 --- a/backend/server.js +++ b/backend/server.js @@ -36,6 +36,7 @@ import { //Help Routes import routes from './routes/Help/index.js' import authRoutes from './routes/Help/authCCM.route.js' +import ccmRoutes from './routes/Help/ccmRoutes.js' // Production-only delivery task scheduling if (process.env.SERVER_ENV === 'production') { @@ -104,6 +105,7 @@ app.use('/api/news', newsRoutes) // News routes //Help Routes app.use('/api/help', routes) app.use('/api/help/auth', authRoutes) +app.use('/api/ccm', ccmRoutes) // Middleware to handle errors and send appropriate responses app.use(notFound) // Handle 404 Not Found diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2460f336..63c5a0a2 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -117,6 +117,7 @@ import Dashboard from './Pages/Help/CCManager/Dashboard' import SupportTicketDashboardPage from './Pages/Help/CCManager/SupportTicket' import FeedbackDashboard from './Pages/Help/CCManager/FeedbackDashboard' +import CCMProfile from './Pages/Help/CCManager/CCMProfile' // Define all routes in a single Router const router = createBrowserRouter( @@ -315,6 +316,10 @@ const router = createBrowserRouter( } /> } /> } /> + } + /> } diff --git a/frontend/src/Components/Help/dashboard/CCMDashboardNavBar.jsx b/frontend/src/Components/Help/dashboard/CCMDashboardNavBar.jsx index af12aab9..33893641 100644 --- a/frontend/src/Components/Help/dashboard/CCMDashboardNavBar.jsx +++ b/frontend/src/Components/Help/dashboard/CCMDashboardNavBar.jsx @@ -20,19 +20,19 @@ const NavLinks = [ }, { title: 'Knowledge Base', - to: '/help/dashboard/knowledge-base', + to: 'https://medium.com/@aweesha', }, { - title: 'Analytics', - to: '/help/dashboard/analytics', + title: 'CCM Profile', + to: '/help/dashboard/profile', }, ] const CCMDashboardNavBar = () => { return (
-