Skip to content

Commit

Permalink
Fetch invitation statistics (#463)
Browse files Browse the repository at this point in the history
  • Loading branch information
UwicyezaG authored and GSinseswa721 committed Sep 16, 2024
1 parent c65e379 commit 4932cce
Show file tree
Hide file tree
Showing 6 changed files with 618 additions and 164 deletions.
34 changes: 33 additions & 1 deletion src/Mutations/invitationMutation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,36 @@ export const UPLOAD_INVITATION_FILE = gql`
sentEmails
}
}
`;
`;

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
}
}
`;
7 changes: 4 additions & 3 deletions src/Mutations/invitationStats.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
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
getAcceptedInvitationsPercentsCount
acceptedInvitationsCount
}
}
`;
`;
225 changes: 159 additions & 66 deletions src/components/InvitationTable.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="w-full overflow-x-auto pb-4 mt-6">
<div className="min-w-[600px] border border-gray-300 pb-2 rounded-lg bg-[#F3F0FE] dark:bg-dark-bg">
{/* Header Row */}
<div className="bg-[#C7B9F9] flex justify-between font-bold p-4 border-b border-gray-300 dark:text-black">
<span className="flex-1 p-4 text-left">Email</span>
<span className="flex-1 p-4 text-left">Role</span>
<span className="flex-1 p-4 text-left">Status</span>
</div>
// 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) => (
<div
key={invitation.id}
className="w-full bg-[#F3F0FE] border-b border-gray-300"
>
{invitation.invitees?.map((invitee, idx) => (
<div
key={invitation.id}
className="flex justify-between p-4 dark:bg-dark-bg"
>
<span className="flex-1 p-4 text-left">{invitee.email}</span>
<span className="flex-1 p-4 text-left">{invitee.role}</span>
<span className="flex-1 p-4 text-left">{invitation.status}</span>
</div>
const handleFilterChange = (e) => {
const value = e.target.value || '';
setGlobalFilter(value);
setFilterInput(value);
};

return (
<div className="">
<div className="flex items-center justify-between pb-6 " />
<div style={{ overflowX: 'auto' }}>
<table className="min-w-full leading-normal" {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr key={headerGroup.id} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th
key={column.id}
className={column.isSorted ? 'sort-asc thead' : ' thead'}
{...column.getHeaderProps(column.getSortByToggleProps())}
>
{column.render('Header')}
</th>
))}
</tr>
))}
</div>
))}
</thead>
<tbody {...getTableBodyProps()}>
{!loading && memoizedData.length === 0 ? (
<tr>
<td
colSpan={columns.length}
className="px-6 py-4 text-sm text-center text-gray-500 dark:text-gray-300"
aria-label="Empty cell" // Added for accessibility
>
&nbsp;{' '}
{/* Non-breaking space to ensure it's not an empty tag */}
</td>
</tr>
) : (
page.map((row) => {
prepareRow(row);
return (
<tr
key={row.id}
className={`border-b dark:border-gray-700 ${
row.index % 2 === 0
? 'bg-light-bg dark:bg-neutral-600'
: 'bg-white dark:bg-dark-bg'
}`}
{...row.getRowProps()}
>
{row.cells.map((cell) => (
<td
key={cell.id}
className="data-cell "
{...cell.getCellProps()}
>
{cell.render('Cell')}
</td>
))}
</tr>
);
})
)}
{loading && (
<tr>
<td
colSpan={columns.length}
className="px-6 py-4 text-sm text-center text-gray-500 dark:text-gray-300 "
>
Loading...
</td>
</tr>
)}
{error && (
<tr>
<td
colSpan={columns.length}
className="px-6 py-4 text-sm text-center text-gray-500 dark:text-gray-300 "
>
Error occurred
</td>
</tr>
)}
{!loading && !error && data.length === 0 && (
<tr>
{' '}
<td colSpan={columns.length || 100} className="text-center p-4">
<div className="flex flex-col items-center justify-center space-y-4">
{' '}
<p className="text-gray-600 dark:text-gray-400 text-lg font-medium">
{' '}
No records available{' '}
</p>
</div>{' '}
</td>{' '}
</tr>
)}
</tbody>
</table>
</div>
<div className="px-6 py-4">
<DataPagination
pageIndex={pageIndex}
pageSize={pageSize}
pageOptions={pageOptions}
canNextPage={canNextPage}
gotoPage={gotoPage}
columnLength={columns.length}
canPreviousPage={canPreviousPage}
pageOptions={pageOptions}
pageSize={pageSize}
setPageSize={setPageSize}
gotoPage={gotoPage}
nextPage={nextPage}
previousPage={previousPage}
columnLength={3}
nextPage={nextPage}
pageCount={pageCount}
pageIndex={pageIndex}
/>
</div>
</div>
);
}

export default InvitationTable;
export default DataTableStats;
1 change: 1 addition & 0 deletions src/components/invitationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ function InviteForm({ onClose }: InviteFormProps) {
setEmail('');
setRole('Role');
setOrgToken('');
onClose();
} catch (e: any) {
toast.error(`Error sending invitation: ${e.message}`);
}
Expand Down
Loading

0 comments on commit 4932cce

Please sign in to comment.