diff --git a/src/Mutations/invitationMutation.tsx b/src/Mutations/invitationMutation.tsx index a70a2464e..7f797dc1b 100644 --- a/src/Mutations/invitationMutation.tsx +++ b/src/Mutations/invitationMutation.tsx @@ -27,4 +27,36 @@ export const UPLOAD_INVITATION_FILE = gql` sentEmails } } -`; \ No newline at end of file +`; + +export const DELETE_INVITATION = gql` + mutation DeleteInvitation($invitationId: ID!) { + deleteInvitation(invitationId: $invitationId) { + message + } + } +`; + +export const UPDATE_INVITATION = gql` + mutation UpdateInvitation( + $invitationId: ID! + $orgToken: String! + $newEmail: String + $newRole: String + ) { + updateInvitation( + invitationId: $invitationId + orgToken: $orgToken + newEmail: $newEmail + newRole: $newRole + ) { + id + invitees { + email + role + } + inviterId + orgToken + } + } +`; diff --git a/src/Mutations/invitationStats.tsx b/src/Mutations/invitationStats.tsx index a6b188659..4f0f9d3f0 100644 --- a/src/Mutations/invitationStats.tsx +++ b/src/Mutations/invitationStats.tsx @@ -1,7 +1,8 @@ import { gql } from '@apollo/client'; + export const GET_INVITATIONS_STATISTICS_QUERY = gql` - query GetInvitationStatistics($orgToken: String!){ - getInvitationStatistics(orgToken: $orgToken){ + query GetInvitationStatistics($orgToken: String!) { + getInvitationStatistics(orgToken: $orgToken) { totalInvitations pendingInvitationsCount getPendingInvitationsPercentsCount @@ -9,4 +10,4 @@ export const GET_INVITATIONS_STATISTICS_QUERY = gql` acceptedInvitationsCount } } -`; \ No newline at end of file +`; diff --git a/src/components/InvitationTable.tsx b/src/components/InvitationTable.tsx index 62dcc46e9..bb1245aa6 100644 --- a/src/components/InvitationTable.tsx +++ b/src/components/InvitationTable.tsx @@ -1,85 +1,178 @@ -import React from 'react'; +// @ts-nocheck +import React, { useState, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + useGlobalFilter, + usePagination, + useSortBy, + useTable, +} from 'react-table'; import DataPagination from './DataPagination'; -interface Invitee { - email: string; - role: string; +interface TableData { + data: any[]; + columns: any; + error: string | null; + loading?: boolean; + className?: string; } -interface Invitation { - invitees: Invitee[]; - status: string; - id: string; -} +function DataTableStats({ data, columns, error, loading }: TableData) { + const [filterInput, setFilterInput] = useState(''); + const { t } = useTranslation(); -interface InvitationTableProps { - invitations: Invitation[]; - pageIndex: number; - pageSize: number; - canNextPage: boolean; - canPreviousPage: boolean; - pageOptions: any[]; - setPageSize: (size: number) => void; - gotoPage: (page: number) => void; - nextPage: () => void; - previousPage: () => void; -} + // Memoize columns and data to prevent unnecessary re-renders + const memoizedColumns = useMemo(() => [...columns], [columns]); + const memoizedData = useMemo(() => [...data], [data]); -function InvitationTable({ - invitations, - pageIndex, - pageSize, - canNextPage, - canPreviousPage, - pageOptions, - setPageSize, - gotoPage, - nextPage, - previousPage, -}: InvitationTableProps) { - return ( -
-
- {/* Header Row */} -
- Email - Role - Status -
+ // Table instance + const tableInstance = useTable( + { + data: memoizedData, + columns: memoizedColumns, + initialState: { pageSize: 3, globalFilter: filterInput }, + }, + useGlobalFilter, + useSortBy, + usePagination, + ); + + const { + getTableProps, + setGlobalFilter, + getTableBodyProps, + page, + nextPage, + previousPage, + canPreviousPage, + canNextPage, + gotoPage, + pageCount, + setPageSize, + pageOptions, + headerGroups, + prepareRow, + state: { pageIndex, pageSize }, + } = tableInstance; - {/* Data Rows */} - {invitations?.map((invitation) => ( -
- {invitation.invitees?.map((invitee, idx) => ( -
- {invitee.email} - {invitee.role} - {invitation.status} -
+ const handleFilterChange = (e) => { + const value = e.target.value || ''; + setGlobalFilter(value); + setFilterInput(value); + }; + + return ( +
+
+
+ + + {headerGroups.map((headerGroup) => ( + + {headerGroup.headers.map((column) => ( + + ))} + ))} - - ))} + + + {!loading && memoizedData.length === 0 ? ( + + + + ) : ( + page.map((row) => { + prepareRow(row); + return ( + + {row.cells.map((cell) => ( + + ))} + + ); + }) + )} + {loading && ( + + + + )} + {error && ( + + + + )} + {!loading && !error && data.length === 0 && ( + + {' '} + {' '} + + )} + +
+ {column.render('Header')} +
+  {' '} + {/* Non-breaking space to ensure it's not an empty tag */} +
+ {cell.render('Cell')} +
+ Loading... +
+ Error occurred +
+
+ {' '} +

+ {' '} + No records available{' '} +

+
{' '} +
+
+
); } -export default InvitationTable; +export default DataTableStats; diff --git a/src/components/invitationModal.tsx b/src/components/invitationModal.tsx index 872941532..dcefade08 100644 --- a/src/components/invitationModal.tsx +++ b/src/components/invitationModal.tsx @@ -102,6 +102,7 @@ function InviteForm({ onClose }: InviteFormProps) { setEmail(''); setRole('Role'); setOrgToken(''); + onClose(); } catch (e: any) { toast.error(`Error sending invitation: ${e.message}`); } diff --git a/src/pages/invitation.tsx b/src/pages/invitation.tsx index 1aeddeea3..e72698ab5 100644 --- a/src/pages/invitation.tsx +++ b/src/pages/invitation.tsx @@ -1,18 +1,26 @@ +/* eslint-disable */ +/* istanbul ignore file */ + import React, { useState, useEffect } from 'react'; -import { useQuery, gql, useLazyQuery } from '@apollo/client'; +import { useQuery, gql, useLazyQuery, useMutation } from '@apollo/client'; import { IoIosAddCircleOutline, IoIosSearch } from 'react-icons/io'; import { FaCheck, FaFilter } from 'react-icons/fa'; import { LuHourglass } from 'react-icons/lu'; import { BsPersonFillX } from 'react-icons/bs'; import InvitationCard from '../components/InvitationCard'; -import InvitationTable from '../components/InvitationTable'; +import DataTableStats from '../components/InvitationTable'; import InvitationModal from './invitationModalComponet'; import { GET_INVITATIONS_STATISTICS_QUERY } from '../Mutations/invitationStats'; import { toast } from 'react-toastify'; +import { useTranslation } from 'react-i18next'; +import { Icon } from '@iconify/react'; +import { DELETE_INVITATION } from '../Mutations/invitationMutation'; +import { UPDATE_INVITATION } from '../Mutations/invitationMutation'; +import Button from '../components/Buttons'; const GET_ALL_INVITATIONS = gql` - query AllInvitations($limit: Int, $offset: Int) { - getAllInvitations(limit: $limit, offset: $offset) { + query AllInvitations { + getAllInvitations { invitations { invitees { email @@ -27,8 +35,8 @@ const GET_ALL_INVITATIONS = gql` `; const GET_INVITATIONS = gql` - query GetInvitations($query: String!, $limit: Int, $offset: Int) { - getInvitations(query: $query, limit: $limit, offset: $offset) { + query GetInvitations($query: String!) { + getInvitations(query: $query) { invitations { invitees { email @@ -59,49 +67,66 @@ function Invitation() { const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [totalInvitations, setTotalInvitations] = useState(0); - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(5); const [isModalOpen, setIsModalOpen] = useState(false); const organizationToken = localStorage.getItem('orgToken'); - - const { loading: isLoading, data: queryData, refetch: refreshData } = useQuery( - GET_INVITATIONS_STATISTICS_QUERY, - { - variables: { - orgToken: organizationToken - }, - skip: !organizationToken, - fetchPolicy: 'network-only', - onError: (error) => { - toast.error("testtes111"); - }, - }, - ); - useEffect(() => { - if (queryData) { - refreshData(); - setInvitationStats(queryData.getInvitationStatistics); - } - }, [queryData, refreshData]); - if (!organizationToken) { - return

Organization token not found. Please log in.

; - } + const { t }: any = useTranslation(); + const [selectedRow, setSelectedRow] = useState(null); + const [removeInviteeModel, setRemoveInviteeModel] = useState(false); + const [updateInviteeModel, setUpdateInviteeModel] = useState(false); + const [deleteInvitation, setDeleteInvitation] = useState(''); + const [email, setEmail] = useState(''); + const [role, setRole] = useState(''); + const [selectedInvitationId, setSelectedInvitationId] = useState(''); + const [buttonLoading, setButtonLoading] = useState(false); + const { + loading: isLoading, + data: queryData, + refetch: refreshData, + } = useQuery(GET_INVITATIONS_STATISTICS_QUERY, { + variables: { + orgToken: organizationToken, + }, + skip: !organizationToken, + fetchPolicy: 'network-only', + onError: (error) => { + toast.error('testtes111'); + }, + }); + useEffect(() => { + if (queryData) { + refreshData(); + setInvitationStats(queryData.getInvitationStatistics); + } + }, [queryData, refreshData]); + if (!organizationToken) { + return

Organization token not found. Please log in.

; + } + const toggleOptions = (row: string) => { + setSelectedRow(selectedRow === row ? null : row); + }; const { data, loading: queryLoading, error: queryError, refetch, - } = useQuery(GET_ALL_INVITATIONS, { - variables: { limit: pageSize, offset: pageIndex * pageSize }, - }); + } = useQuery(GET_ALL_INVITATIONS, {}); const [ fetchInvitations, { data: searchData, loading: searchLoading, error: searchError }, - ] = useLazyQuery(GET_INVITATIONS, { - variables: { limit: pageSize, offset: pageIndex * pageSize }, - }); + ] = useLazyQuery(GET_INVITATIONS); + + /* istanbul ignore next */ + const removeInviteeMod = () => { + const newState = !removeInviteeModel; + setRemoveInviteeModel(newState); + }; + + const updateInviteeMod = () => { + const newState = !updateInviteeModel; + setUpdateInviteeModel(newState); + }; useEffect(() => { if (queryLoading || searchLoading) { @@ -118,11 +143,13 @@ function Invitation() { searchData && Array.isArray(searchData.getInvitations.invitations) ) { + refetch(); setInvitations(searchData.getInvitations.invitations); } else if (data && data.getAllInvitations) { setInvitations(data.getAllInvitations.invitations); setTotalInvitations(data.getAllInvitations.totalInvitations); } + refetch(); }, [data, searchData, queryLoading, searchLoading, queryError, searchError]); const handleSearch = () => { @@ -135,68 +162,230 @@ function Invitation() { setError(null); setLoading(false); - fetchInvitations({ - variables: { - query: searchQuery, - limit: pageSize, - offset: pageIndex * pageSize, - }, - }); - }; - - const gotoPage = (pageNumber: number) => { - setPageIndex(pageNumber); - }; - - const nextPage = () => { - if (pageIndex < Math.floor(totalInvitations / pageSize)) { - setPageIndex(pageIndex + 1); - } + fetchInvitations(); }; - const previousPage = () => { - if (pageIndex > 0) { - setPageIndex(pageIndex - 1); + // Defining invitation table + let content; + const capitalizeStrings = (str: string): string => { + if (!str) return ''; + if (str === 'ttl') { + return 'TTL'; } + return str.charAt(0).toUpperCase() + str.slice(1); }; + const columns = [ + { Header: t('email'), accessor: 'email' }, + { Header: t('role'), accessor: 'role' }, + { + Header: t('Status'), + accessor: 'status', - const canNextPage = pageIndex < Math.floor(totalInvitations / pageSize); - const canPreviousPage = pageIndex > 0; - const pageOptions = Array.from( - { length: Math.ceil(totalInvitations / pageSize) }, - (_, i) => i + 1, - ); + Cell: ({ row }: any) => { + return ( +
0 ? ' flex' : ' hidden') + } + > + {row.original.Status} +
+ ); + }, + }, - // Defining invitation table - let content; + { + Header: t('action'), + accessor: '', + Cell: ({ row }: any) => ( +
+
+ toggleOptions(row.id)} + /> + {selectedRow === row.id && ( +
+ <> +
+
+
{ + updateInviteeMod(); + setSelectedInvitationId(row.original.id); + toggleOptions(row.original.email); + }} + > + +
+ Update{' '} + <> +
+ Update invitation + +
+
+
+
+
{ + removeInviteeMod(); + setDeleteInvitation(row.original.id); + toggleOptions(row.original.email); + }} + > + +
+ Delete + <> +
+ Delete invitation + +
+
+
+ {row.original.Status === 'pending' && ( +
+
+ +
+ Resend{' '} + <> +
+ Resend invitation + +
+
+
+ )} +
+ +
+ )} +
+
+ ), + }, + ]; + 8; + const datum: any = []; + if (invitations && invitations.length > 0) { + invitations.forEach((invitation) => { + invitation.invitees?.forEach((data: any) => { + let entry: any = {}; + entry.email = data.email; + entry.role = capitalizeStrings(data.role); + entry.Status = capitalizeStrings(invitation.status); + entry.id = invitation.id; + datum.push(entry); + }); + }); + } if (loading || searchLoading) { - content =

Loading...

; + content = ( + 0 ? datum : []} + columns={columns} + loading={loading} + error={error} + /> + ); } else if (error || searchError) { - content =

Error occurred "{error || searchError?.message}"

; - } else if (invitations.length === 0) { - content =

No invitation found.

; + content = ( + 0 ? datum : []} + columns={columns} + loading={loading} + error={error} + /> + ); } else { content = ( <> - 0 ? datum : []} + columns={columns} + loading={loading} + error={error} /> ); } + const [DeleteInvitation] = useMutation(DELETE_INVITATION, { + variables: { + invitationId: deleteInvitation, + }, + onCompleted: (data) => { + setTimeout(() => { + setButtonLoading(false); + toast.success(data.deleteInvitation.message); + refetch(); + removeInviteeMod(); + }, 1000); + }, + onError: (err) => { + setTimeout(() => { + setButtonLoading(false); + toast.error(err.message); + }, 500); + }, + }); + + const [UpdateInvitation] = useMutation(UPDATE_INVITATION, { + variables: { + invitationId: selectedInvitationId, + orgToken: organizationToken, + newEmail: email || undefined, + newRole: role || undefined, + }, + onCompleted: () => { + setTimeout(() => { + setButtonLoading(false); + toast.success('Invitation updated successfully!'); + refetch(); + updateInviteeMod(); + }, 1000); + }, + onError: (err) => { + setTimeout(() => { + setButtonLoading(false); + toast.error(`Failed to update invitation`); + }, 500); + }, + }); + const handleOpenModal = () => setIsModalOpen(true); - const handleCloseModal = () => setIsModalOpen(false); + const handleCloseModal = () => { + setIsModalOpen(false); + refetch(); + }; return (
@@ -238,10 +427,9 @@ function Invitation() { time="Last 7 days" staticNumber={invitationStats?.acceptedInvitationsCount || 0} percentage={ - invitationStats?.getAcceptedInvitationsPercentsCount?.toFixed( - 1, - ) + '%' || '0' - } + invitationStats?.getAcceptedInvitationsPercentsCount?.toFixed(1) + + '%' || '0' + } /> - + {/* Search Section */} -
+

Search for Invitation Status

@@ -294,7 +481,7 @@ function Invitation() { {/* Search form */}
-
+
+
+
+

+ {t('Delete Invitation')} +

+
+
+
+
+
+

+ {t('Are you sure you want to Delete this invitation?')} +

+
+ +
+ + +
+
+
+
+
+ + {/* =========================== Start:: UpdateInvitee Model =============================== */} + +
+
+
+

+ {t('Update Invitation')} +

+
+
+
+
+ {/* Email Input Field */} +
+ + setEmail(e.target.value)} // Controlled input for email + /> +
+ + {/* Role Input Field */} +
+ + setRole(e.target.value)} // Controlled input for role + /> +
+ +
+ + +
+
+
+
+
); } export default Invitation; - diff --git a/src/pages/invitationModalComponet.tsx b/src/pages/invitationModalComponet.tsx index 649ad8fa3..1737a349f 100644 --- a/src/pages/invitationModalComponet.tsx +++ b/src/pages/invitationModalComponet.tsx @@ -12,7 +12,7 @@ function InvitationModal({ isOpen, onClose }: InvitationModalProps) { if (!isOpen) return null; return ( -
+