From 47ae4f3b6efb17174c7180c3729ec338ee823e10 Mon Sep 17 00:00:00 2001 From: amiparadis250 Date: Sun, 15 Sep 2024 08:24:36 +0200 Subject: [PATCH] fix tickets-crud that was introduced in issues 242 -Admins and Coordinators will be able to create tickets -Traineess will be able to see tickets assigned to them -Also Admins and Admins can also perform CRUD operations on Tickets --- .eslintrc | 3 +- index.html | 12 +- public/locales/fr/translation.json | 4 +- src/App.tsx | 20 +- src/Mutations/ActionDropdownCell.tsx | 31 ++ src/Mutations/User.tsx | 2 +- src/Mutations/help.mutation.tsx | 4 +- src/components/ActionDropdown.tsx | 79 +++++ src/components/AttendanceStatistics.tsx | 5 +- src/components/Calendar.tsx | 17 +- src/components/ConfirmationModal.tsx | 64 ++++ src/components/CoordinatorCard.tsx | 24 +- src/components/CreateOrganizationModal.tsx | 5 +- src/components/DashHeader.tsx | 17 +- src/components/DataPagination.tsx | 8 +- src/components/DataTable.tsx | 6 +- src/components/Docs/AdminDocs.tsx | 15 +- src/components/Docs/OthersDocs.tsx | 10 +- src/components/Docs/SideBarDocs.tsx | 23 +- src/components/Docs/SigninOrgDocs.tsx | 63 ++-- src/components/Docs/SignupOrgDocs.tsx | 58 ++-- src/components/Docs/TraineeDocs.tsx | 282 +++++++++++------- src/components/EditTicketModal.tsx | 244 +++++++++++++++ src/components/Footer.tsx | 9 +- src/components/Header.tsx | 6 +- src/components/HomePageCard.tsx | 41 +-- src/components/HomePageCardContainer.tsx | 12 +- src/components/InvitationCard.tsx | 4 +- src/components/LoginActivitiesTable.tsx | 14 +- src/components/ManagerCard.tsx | 6 +- src/components/Navbar.tsx | 4 +- src/components/NewTicketModal.tsx | 176 +++++++++++ src/components/Notification.tsx | 2 +- src/components/Organizations.tsx | 22 +- src/components/ProfileTabs.tsx | 44 +-- src/components/TeamCard.tsx | 1 - src/components/TicketActionsCell.tsx | 27 ++ src/components/TicketsActionCell.tsx | 33 ++ src/components/TraineeAttendance.tsx | 14 +- src/components/TraineeHeader.tsx | 5 +- src/components/TraineePerformanceDetails.tsx | 13 +- src/components/TraineePieCharts.tsx | 46 ++- src/components/TraineeTable.tsx | 16 +- src/components/TraineesChart.tsx | 4 +- src/components/TraineesHeader.tsx | 9 +- src/components/ViewComment.tsx | 5 +- src/components/ViewTicketModal.tsx | 79 +++++ src/components/chartGitHub.tsx | 2 +- src/components/ratings/AddNewRatings.tsx | 15 +- src/components/ratings/Dropout.tsx | 2 +- src/components/ratings/ViewWeeklyRatings.tsx | 32 +- .../ratings/hooks/useAddNewRating.tsx | 2 +- src/components/teamDetails.tsx | 18 +- src/components/tests/Navbar.test.tsx | 2 +- src/components/tests/Resume.test.tsx | 11 +- src/components/tickets.columns.ts | 34 +++ src/containers/DashRoutes.tsx | 10 +- src/containers/Routes.tsx | 6 +- src/containers/admin-dashBoard/Cohorts.tsx | 2 +- .../admin-dashBoard/CoordinatorModal.tsx | 10 +- .../admin-dashBoard/CreatePhaseModal.tsx | 8 +- .../admin-dashBoard/CreateProgramModal.tsx | 14 +- .../admin-dashBoard/ManagerRoles.tsx | 3 +- src/containers/admin-dashBoard/Phases.tsx | 14 +- src/containers/admin-dashBoard/Sessions.tsx | 9 +- .../admin-dashBoard/tests/session.test.tsx | 108 ++++--- src/containers/tests/DashRoutes.test.tsx | 6 +- src/hook/ThemeProvider.tsx | 6 +- src/hook/menuProvider.tsx | 7 +- src/hook/ticketsContext.tsx | 8 +- src/index.css | 25 +- src/pages/About.tsx | 13 +- src/pages/AdminDashboard.tsx | 28 +- src/pages/AdminTraineeDashboard.tsx | 20 +- src/pages/Dashboard.tsx | 2 +- src/pages/GradingSystem.tsx | 2 +- src/pages/OrgRegisterSuccessModel.tsx | 5 +- src/pages/Organization/AdminLogin.tsx | 33 +- src/pages/Organization/UserRegister.tsx | 2 +- src/pages/ProfileEdit.tsx | 16 +- src/pages/Ticket.tsx | 258 ---------------- src/pages/Tickets.tsx | 275 +++++++++++++++-- src/pages/TraineeAttendance.tsx | 106 ++++--- src/pages/TraineeDashboard.tsx | 29 +- src/pages/TraineeRatingDashboard.tsx | 2 +- src/pages/UpdatedRatingDashboard.tsx | 17 +- src/pages/gradeSystem/gradeItem.tsx | 5 +- src/pages/invitation.tsx | 30 +- src/pages/ratings/addRatings.tsx | 8 +- .../tests/AdminTraineeDashboard.test.tsx | 2 +- src/pages/tests/Home.test.tsx | 27 +- src/pages/tests/OrgRegister.test.tsx | 2 +- src/pages/tests/Profile.test.tsx | 20 +- src/pages/tests/Settings.test.tsx | 11 +- src/pages/tests/UpdateTraineeRating.test.tsx | 14 +- src/pages/tests/userRegister.test.tsx | 2 +- src/queries/tickets.queries.tsx | 121 ++++++-- tsconfig.json | 8 +- vercel.json | 2 +- webpack.config.js | 2 +- 100 files changed, 1981 insertions(+), 1023 deletions(-) create mode 100644 src/Mutations/ActionDropdownCell.tsx create mode 100644 src/components/ActionDropdown.tsx create mode 100644 src/components/ConfirmationModal.tsx create mode 100644 src/components/EditTicketModal.tsx create mode 100644 src/components/NewTicketModal.tsx create mode 100644 src/components/TicketActionsCell.tsx create mode 100644 src/components/TicketsActionCell.tsx create mode 100644 src/components/ViewTicketModal.tsx create mode 100644 src/components/tickets.columns.ts delete mode 100644 src/pages/Ticket.tsx diff --git a/.eslintrc b/.eslintrc index f3417336a..0d3d40d00 100644 --- a/.eslintrc +++ b/.eslintrc @@ -41,6 +41,7 @@ "no-shadow": 0, "no-unused-expressions": 0, "react/require-default-props": 0, - "import/prefer-default-export": 0 + "import/prefer-default-export": 0, + "react/no-unstable-nested-components": 0 } } diff --git a/index.html b/index.html index a7a76e3fa..c0a6ec1b5 100644 --- a/index.html +++ b/index.html @@ -41,8 +41,10 @@ rel="stylesheet" /> --> - - +
@@ -90,8 +92,10 @@ href="https://fonts.googleapis.com/css2?family=Inria+Serif:ital@1&family=Lexend+Deca:wght@600&family=Open+Sans:wght@300;400;600;700;800&display=swap" rel="stylesheet" /> --> - - +
diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index c281c3064..a5f480815 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -463,6 +463,6 @@ "Provide Quantity range between 1-2": "Fournir une gamme de quantité comprise entre 1-2", "Provide Professional_Skills range between 1-2": "Fournir une gamme de compétences professionnelles comprise entre 1-2", "Sprint Ratings": "Sprint Notations", - "Please wait to be added to a program or cohort":"Veuillez attendre d'être ajouté à un programme ou à une cohorte", - "Enter all the required information":"Entrez toutes les informations requises" + "Please wait to be added to a program or cohort": "Veuillez attendre d'être ajouté à un programme ou à une cohorte", + "Enter all the required information": "Entrez toutes les informations requises" } diff --git a/src/App.tsx b/src/App.tsx index a9b68a067..78c6266ab 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,16 +12,16 @@ function App() { return (
- - - - - } /> - } /> - - - - + + + + + } /> + } /> + + + +
); diff --git a/src/Mutations/ActionDropdownCell.tsx b/src/Mutations/ActionDropdownCell.tsx new file mode 100644 index 000000000..84ff169c9 --- /dev/null +++ b/src/Mutations/ActionDropdownCell.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import ActionDropdown from '../components/ActionDropdown'; + +interface ActionDropdownCellProps { + row: { + original: any; + }; + onView: (ticket: any) => void; + onEdit?: (id: string) => void; + onDelete?: (id: string) => void; + canEditDelete: boolean; +} + +function ActionDropdownCell({ + row, + onView, + onEdit, + onDelete, + canEditDelete, +}: ActionDropdownCellProps) { + return ( + onView(row.original)} + onEdit={canEditDelete ? () => onEdit?.(row.original.id) : undefined} + onDelete={canEditDelete ? () => onDelete?.(row.original.id) : undefined} + canEditDelete={canEditDelete} + /> + ); +} + +export default ActionDropdownCell; diff --git a/src/Mutations/User.tsx b/src/Mutations/User.tsx index 26ebeacbe..aac7b8a48 100644 --- a/src/Mutations/User.tsx +++ b/src/Mutations/User.tsx @@ -131,4 +131,4 @@ export const DROP_TTL_USER = gql` dropTTLUser(email: $email, reason: $reason) } `; -export default GET_PROFILE; +export default GET_PROFILE; \ No newline at end of file diff --git a/src/Mutations/help.mutation.tsx b/src/Mutations/help.mutation.tsx index 453bba4a5..9f075c704 100644 --- a/src/Mutations/help.mutation.tsx +++ b/src/Mutations/help.mutation.tsx @@ -1,8 +1,8 @@ import { gql } from '@apollo/client'; const CREATE_TICKET = gql` - mutation CreateTicket($subject: String!, $message: String!) { - createTicket(subject: $subject, message: $message) { + mutation CreateTicket($subject: String!, $message: String!, $assignee: ID!) { + createTicket(subject: $subject, message: $message, assignee: $assignee) { responseMsg } } diff --git a/src/components/ActionDropdown.tsx b/src/components/ActionDropdown.tsx new file mode 100644 index 000000000..22a32a1a4 --- /dev/null +++ b/src/components/ActionDropdown.tsx @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import { FaEllipsisV, FaEye, FaEdit, FaTrashAlt } from 'react-icons/fa'; + +interface ActionDropdownProps { + onView: () => void; + onEdit?: () => void; // Optional to allow conditional rendering + onDelete?: () => void; // Optional to allow conditional rendering + canEditDelete: boolean; // Prop to control visibility of Edit and Delete +} + +function ActionDropdown({ + onView, + onEdit, + onDelete, + canEditDelete, +}: ActionDropdownProps) { + const [isOpen, setIsOpen] = useState(false); + + const toggleDropdown = () => setIsOpen(!isOpen); + + return ( +
+ + {isOpen && ( +
+ + {canEditDelete && ( + <> + + + + )} +
+ )} +
+ ); +} + +export default ActionDropdown; diff --git a/src/components/AttendanceStatistics.tsx b/src/components/AttendanceStatistics.tsx index 3a73c2197..b56868d28 100644 --- a/src/components/AttendanceStatistics.tsx +++ b/src/components/AttendanceStatistics.tsx @@ -46,10 +46,7 @@ function BarChart() { return (
-
- hello - -
+
hello
); } diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx index da7d6707a..c0a76bf7b 100644 --- a/src/components/Calendar.tsx +++ b/src/components/Calendar.tsx @@ -108,8 +108,9 @@ const Calendar = () => { <> {/* =========================== Start:: RegisterTraineeModel =========================== */}
@@ -155,9 +156,9 @@ const Calendar = () => { value={newEvent.hostName} onChange /* istanbul ignore next */={(e) => /* istanbul ignore next */ setNewEvent({ - ...newEvent, - hostName: e.target.value, - }) + ...newEvent, + hostName: e.target.value, + }) } />
@@ -172,9 +173,9 @@ const Calendar = () => { selected={newEvent.start} onChange /* istanbul ignore next */={(start: any) => /* istanbul ignore next */ setNewEvent({ - ...newEvent, - start, - }) + ...newEvent, + start, + }) } />
diff --git a/src/components/ConfirmationModal.tsx b/src/components/ConfirmationModal.tsx new file mode 100644 index 000000000..fc07f7d09 --- /dev/null +++ b/src/components/ConfirmationModal.tsx @@ -0,0 +1,64 @@ +/* eslint-disable react/function-component-definition */ +import React, { useState } from 'react'; +import { toast } from 'react-toastify'; + +interface ConfirmationModalProps { + isOpen: boolean; + onConfirm: () => void; + onCancel: () => void; + message: string; +} + +const ConfirmationModal: React.FC = ({ + isOpen, + onConfirm, + onCancel, + message, +}) => { + const [loading, setLoading] = useState(false); + + if (!isOpen) return null; + + const handleConfirm = async () => { + setLoading(true); + try { + onConfirm(); + } catch (error) { + toast.error('Failed to confirm action. Please try again.'); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

+ Confirm Action +

+

{message}

+
+ + +
+
+
+ ); +}; + +export default ConfirmationModal; diff --git a/src/components/CoordinatorCard.tsx b/src/components/CoordinatorCard.tsx index 96491e1eb..d1a1bb38b 100644 --- a/src/components/CoordinatorCard.tsx +++ b/src/components/CoordinatorCard.tsx @@ -164,23 +164,21 @@ function ManagerCard() { }); return ( -
- {loading ? ( -
-
-
- ) : ( -
- {teamData && +
+ {loading ? ( +
+
+
+ ) : ( +
+ {teamData && teamData.map((teamProps: any, index: number) => ( - + ))} -
- )} +
+ )}
); } diff --git a/src/components/CreateOrganizationModal.tsx b/src/components/CreateOrganizationModal.tsx index b998d09dc..555ab13e2 100644 --- a/src/components/CreateOrganizationModal.tsx +++ b/src/components/CreateOrganizationModal.tsx @@ -52,8 +52,9 @@ export default function CreateOrganizationModal({ return (
diff --git a/src/components/DashHeader.tsx b/src/components/DashHeader.tsx index c0f12aca3..3aadddf09 100644 --- a/src/components/DashHeader.tsx +++ b/src/components/DashHeader.tsx @@ -171,16 +171,17 @@ function DashHeader() {
- + -

- PULSE -

+

PULSE

- + diff --git a/src/components/DataPagination.tsx b/src/components/DataPagination.tsx index 3b79062fe..abcba6f7b 100644 --- a/src/components/DataPagination.tsx +++ b/src/components/DataPagination.tsx @@ -24,7 +24,7 @@ function DataPagination({ -
+
+ +
+ + + {errors.message && ( +

{errors.message}

+ )} +
+ +
+ + + {errors.status && ( +

{errors.status}

+ )} +
+ +
+ + + {errors.assignee && ( +

{errors.assignee}

+ )} +
+ +
+ + +
+ +
+
+ ); +}; + +export default EditTicketModal; diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 31d0aaf86..1e948d116 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -29,16 +29,15 @@ function Footer({ styles }: any) { } }, []); return ( -
+
-

+

PULSE

diff --git a/src/components/Header.tsx b/src/components/Header.tsx index e79f6a21b..07d9dd9eb 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -15,8 +15,8 @@ const Header = forwardRef(({ open, setOpen, ...props }: any, ref: any) => { /* istanbul ignore next */ const handleClick = () => setOpen(!open); const { user, logout } = useContext(UserContext); - const location = useLocation() - const pathname = location.pathname.split("/")[1] + const location = useLocation(); + const pathname = location.pathname.split('/')[1]; const goTo = orgToken ? '/users/login' : '/login/org'; @@ -75,7 +75,7 @@ const Header = forwardRef(({ open, setOpen, ...props }: any, ref: any) => {
  • { - if (pathname === "docs") return 'text-primary'; + if (pathname === 'docs') return 'text-primary'; return ''; }} to="/docs/getting-started" diff --git a/src/components/HomePageCard.tsx b/src/components/HomePageCard.tsx index edd49cafa..ef3016d90 100644 --- a/src/components/HomePageCard.tsx +++ b/src/components/HomePageCard.tsx @@ -1,31 +1,32 @@ -import React from 'react' +import React from 'react'; function HomePageCard({ style }: any) { return ( <>
    -

    Track Your Program Cycle

    -

    Leverage a robust platform that empowers you to effortlessly track and manage trainees from the recruitment - all the way to their graduation.

    +

    + Track Your Program Cycle +

    +

    + Leverage a robust platform that empowers you to effortlessly track and + manage trainees from the recruitment all the way to their graduation. +

    -
    -

    Say Goodbye to Spreadsheets

    -

    Harness the super powers of a centralized hub, providing you with a reliable and - comprehensive source of information for all your info programs.

    +
    +

    + Say Goodbye to Spreadsheets +

    +

    + Harness the super powers of a centralized hub, providing you with a + reliable and comprehensive source of information for all your info + programs. +

    - ) + ); } -export default HomePageCard \ No newline at end of file +export default HomePageCard; diff --git a/src/components/HomePageCardContainer.tsx b/src/components/HomePageCardContainer.tsx index 346e1f7bf..545442889 100644 --- a/src/components/HomePageCardContainer.tsx +++ b/src/components/HomePageCardContainer.tsx @@ -1,14 +1,12 @@ -import React from 'react' -import HomePageCard from './HomePageCard' - +import React from 'react'; +import HomePageCard from './HomePageCard'; function HomePageCardContainer() { return ( -
    +
    - ) + ); } -export default HomePageCardContainer \ No newline at end of file +export default HomePageCardContainer; diff --git a/src/components/InvitationCard.tsx b/src/components/InvitationCard.tsx index 5a0727b61..d566be95e 100644 --- a/src/components/InvitationCard.tsx +++ b/src/components/InvitationCard.tsx @@ -15,7 +15,7 @@ function InvitationCard({ icon, status, time, staticNumber, percentage }: Invita return (
    -
    +
    {icon}
    @@ -25,7 +25,7 @@ function InvitationCard({ icon, status, time, staticNumber, percentage }: Invita

    {time}

    -
    +
    {staticNumber}
    diff --git a/src/components/LoginActivitiesTable.tsx b/src/components/LoginActivitiesTable.tsx index fae4b9e4f..1177660a8 100644 --- a/src/components/LoginActivitiesTable.tsx +++ b/src/components/LoginActivitiesTable.tsx @@ -127,10 +127,13 @@ const LoginActivitiesTable: React.FC = () => { {displayActivities.map((activity) => ( /* istanbul ignore next */ - - + - {new Date(activity.date).toLocaleString()} {/* Convert UTC date to local time */} + {new Date(activity.date).toLocaleString()}{' '} + {/* Convert UTC date to local time */} {activity.country_name} @@ -145,12 +148,13 @@ const LoginActivitiesTable: React.FC = () => { {activity.IPv4} - {activity.failed > 0 ? "Failed" : "Success"} + + {activity.failed > 0 ? 'Failed' : 'Success'} + ))} - {displayActivities.map((activity) => ( +
    {loading ? (
    @@ -172,7 +170,7 @@ function ManagerCard() {
    {teamData && teamData.map((teamProps: any, index: number) => ( - + ))} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index a01acd80a..c76e9a281 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -34,7 +34,9 @@ const Header = forwardRef(({ open, setOpen, ...props }: any, ref: any) => { }, []); return (
    diff --git a/src/components/NewTicketModal.tsx b/src/components/NewTicketModal.tsx new file mode 100644 index 000000000..377e731c1 --- /dev/null +++ b/src/components/NewTicketModal.tsx @@ -0,0 +1,176 @@ +import React, { useState } from 'react'; +import { useMutation } from '@apollo/client'; +import { toast } from 'react-toastify'; +import CREATE_TICKET from '../Mutations/help.mutation'; + +interface NewTicketModalProps { + isOpen: boolean; + onClose: () => void; + users: any[]; + refetchTickets: () => void; +} + +function NewTicketModal({ + isOpen, + onClose, + users, + refetchTickets, +}: NewTicketModalProps) { + const [subject, setSubject] = useState(''); + const [message, setMessage] = useState(''); + const [selectedUser, setSelectedUser] = useState(''); + const [loading, setLoading] = useState(false); + const [errors, setErrors] = useState<{ + subject?: string; + message?: string; + user?: string; + }>({}); + + const [createTicket] = useMutation(CREATE_TICKET, { + onCompleted: () => { + setLoading(false); + toast.success('Ticket created successfully'); + refetchTickets(); + onClose(); + }, + + onError: (error) => { + setLoading(false); + toast.error(`Error creating ticket: ${error.message}`); + }, + }); + + const validateForm = () => { + const newErrors: { subject?: string; message?: string; user?: string } = {}; + if (!subject.trim()) newErrors.subject = 'Subject is required'; + if (!message.trim()) newErrors.message = 'Message is required'; + if (!selectedUser) newErrors.user = 'User must be selected'; + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async () => { + if (!validateForm()) return; + + setLoading(true); + try { + await createTicket({ + variables: { + subject, + message, + assignee: selectedUser, + }, + }); + } catch (error) { + // @ts-ignore + toast.error(`Error submitting ticket: ${error.message}`); + } + }; + + if (!isOpen) return null; + + return ( +
    +
    +

    + Create New Ticket +

    + {loading && ( +
    +
    +
    + )} +
    { + e.preventDefault(); + handleSubmit(); + }} + > +
    + + setSubject(e.target.value)} + className={`block w-full px-4 py-2 mt-1 text-gray-900 bg-gray-200 border ${ + errors.subject ? 'border-red-500' : 'border-gray-300' + } rounded-md shadow-sm dark:bg-gray-700 dark:text-gray-100 dark:border-gray-600`} + /> + {errors.subject && ( +

    {errors.subject}

    + )} +
    +
    + +