Skip to content

Commit

Permalink
feat(Queries):Admin query management
Browse files Browse the repository at this point in the history
  • Loading branch information
YvetteNyibuka committed Jul 26, 2024
1 parent 09f1b6f commit 00fa2e2
Show file tree
Hide file tree
Showing 17 changed files with 577 additions and 56 deletions.
8 changes: 3 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@hookform/resolvers": "^3.6.0",
"@react-oauth/google": "^0.12.1",
"@reduxjs/toolkit": "^2.2.5",
"@types/react-copy-to-clipboard": "^5.0.7",
"axios": "^1.7.2",
"chart.js": "^4.4.3",
"class-variance-authority": "^0.7.0",
Expand Down
8 changes: 8 additions & 0 deletions src/@types/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DynamicData } from './DynamicData';

export interface QueryState {
isLoading: boolean;
singleQuery: DynamicData;
error: string | null;
querries: DynamicData;
}
5 changes: 3 additions & 2 deletions src/components/DashboardSideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { fetchUserProfile } from '../redux/features/userUpdateSlice';
import { useAppDispatch, useAppSelector } from '../redux/hooks/hooks';
import { appLogo } from '../utils/images';
import LogoutCard from './cards/LogoutCard';
import { Link } from 'react-router-dom';

const DashboardSideNav = ({
children,
Expand All @@ -26,10 +27,10 @@ const DashboardSideNav = ({
className={`${otherStyles} h-screen bg-neutral-white fixed ipad:sticky top-0 left-0 z-40`}
>
<nav className="h-full flex flex-col justify-between border-r border-neutral-grey/30 shadow-sm">
<div className="p-5 pb-2 flex gap-3 items-center">
<Link to={'/'} className="p-5 pb-2 flex gap-3 items-center">
<img src={appLogo} alt="website logo" className="w-16" />
<h2 className="text-2xl font-semibold">ShopTrove</h2>
</div>
</Link>
<ul className="flex-1 mt-3">{children}</ul>
<div className="relative flex gap-3 items-center p-3">
<img
Expand Down
8 changes: 5 additions & 3 deletions src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ const Nav = () => {
const userData = fetchInfo();

useEffect(() => {
if (!data && accessToken) {
dispatch(fetchUserProfile()).unwrap();
if (accessToken) {
if (!data) {
dispatch(fetchUserProfile()).unwrap();
}
dispatch(getCarts()).unwrap();
}
dispatch(getCarts()).unwrap();
}, [accessToken, data, dispatch]);

const links = [
Expand Down
10 changes: 10 additions & 0 deletions src/components/ProfileDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ const ProfileDropdown = ({ image }: ProfileDropdownProps) => {
My orders
</Link>
)}
{(userData?.role === 'ADMIN' || userData?.role === 'SELLER') && (
<Link
to="/dashboard"
className="block px-4 py-2 text-sm text-gray-700 hover:text-[1rem] hover:text-neutral-white hover:bg-primary-lightblue"
role="menuitem"
onClick={toggleDropdown}
>
<p> Dashboard</p>
</Link>
)}
{userData?.role === 'BUYER' && (
<Link
to="/wishes"
Expand Down
38 changes: 38 additions & 0 deletions src/components/buttons/CopyBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { FC, useEffect, useState } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { CopyType } from '../../@types/userType';
import { FaRegCopy } from 'react-icons/fa';
import { TiTick } from 'react-icons/ti';

const CopyButton: FC<CopyType> = ({ name, text, value }) => {
const [copied, setCopied] = useState(false);

useEffect(() => {
const interval = setTimeout(() => {
setCopied(false);
}, 3000);

return () => clearTimeout(interval);
}, [copied]);

return (
<CopyToClipboard
text={value}
onCopy={() => name === text && setCopied(true)}
>
<div className="flex ipad:gap-5 items-center pl-4 pr-1 py-1 border rounded justify-between ">
<span className="text-[9px] ipad:text-xs">
{copied ? 'Copied' : name}
</span>
<button
disabled={copied}
className={`p-1 border rounded hover:bg-slate-200 ${copied && 'bg-action-success hover:bg-action-success'} transition-colors`}
>
{copied ? <TiTick size={24} fill="white" /> : <FaRegCopy size={24} />}
</button>
</div>
</CopyToClipboard>
);
};

export default CopyButton;
106 changes: 106 additions & 0 deletions src/pages/Admin/Querries.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { ScaleLoader } from 'react-spinners';
import { DynamicData } from '../../@types/DynamicData';
import { getQuerries } from '../../redux/features/Queries/querySlice';
import { useAppDispatch, useAppSelector } from '../../redux/hooks/hooks';
import formatDate from '../../utils/DateConversion';
import Pagination from '../../utils/Pagination';

const Querries = () => {
const dispatch = useAppDispatch();
const { isLoading, querries } = useAppSelector((state) => state.queries);
const navigate = useNavigate();

const numberOfQuerries = querries.length;

useEffect(() => {
dispatch(getQuerries()).unwrap();
}, [dispatch]);

const [currentPage, setCurrentPage] = useState(0);

const itemsPerPage = 10;
const orderArray = Array.isArray(querries) ? querries : [];
const reversedOrderArray = [...orderArray].reverse();
const offset = currentPage * itemsPerPage;
const query = reversedOrderArray.slice(offset, offset + itemsPerPage);
const pageCount = Math.ceil(reversedOrderArray.length / itemsPerPage);

const handlePageChange = (selectedPage: number) => {
setCurrentPage(selectedPage);
};

const handleSingleQuery = (id: string) => {
navigate(`/dashboard/querries/${id}`);
};
return (
<div className="parent_container relative max-h-[80%] overflow-y-scroll overflow-hidden pb-4 mt-4 h-full">
{isLoading ? (
<div className="w-full absolute h-full flex items-center justify-center">
<ScaleLoader
color="#256490"
role="progressbar"
aria-label="single_product_loader"
/>
</div>
) : numberOfQuerries <= 0 ? (
<div className="w-full h-[90%] flex items-center justify-center ">
<h2 className="text-center font-bold text-2xl">No Messages</h2>
</div>
) : (
<>
<div className="tableWrapper mt-1 text-[1rem] mx-5 laptop:mx-10 bg-neutral-white p-2 rounded-md max-w-[90%]">
<div className="flex justify-between items-center">
<h1 className="mb-5 text-lg">Messages</h1>
</div>
<table className="tables pt-2 p-3 overflow-hidden overflow-x-scroll max-w-[18rem] tablet:max-w-[100%]">
<thead className="bg-[#256490] text-neutral-white text-left overflow-hidden rounded-3xl p2">
<tr className="rounded-xl text-sm ">
<th>No</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Subject</th>
<th>Message</th>
<th className="expand">Date</th>
</tr>
</thead>
<tbody className="text-slate-700">
{query.map((query: DynamicData, index: number) => (
<tr
onClick={() => handleSingleQuery(query.id)}
key={index}
className={`hover:bg-primary-lightblue/30 cursor-pointer relative text-sm ${index % 2 !== 0 ? 'bg-[#DDDD]' : ''}`}
>
<td>{index + 1 + offset}</td>
<td>{query.firstName}</td>
<td>{query.lastName}</td>
<td>{query.email}</td>
<td>{query.subject}</td>
<td>
{query.message.length > 15
? query.message.slice(0, 25) + '...'
: query.message}
</td>
<td>{formatDate(query.createdAt)}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="flex items-center justify-center">
<Pagination
data-testid="pagination-component"
pageCount={pageCount}
currentPage={currentPage}
onPageChange={handlePageChange}
/>
</div>
</>
)}
</div>
);
};

export default Querries;
78 changes: 78 additions & 0 deletions src/pages/Admin/SingleQuerries.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { ScaleLoader } from 'react-spinners';
import { DynamicData } from '../../@types/DynamicData';
import BackButton from '../../components/buttons/BackButton';
import { getSingleQuery } from '../../redux/features/Queries/querySlice';
import { useAppDispatch, useAppSelector } from '../../redux/hooks/hooks';
import CopyButton from '../../components/buttons/CopyBtn';

const SingleQuerries = () => {
const dispatch = useAppDispatch();
const { isLoading, singleQuery } = useAppSelector((state) => state.queries);
const { id } = useParams<{ id: string }>();
const singleQueryNumber = singleQuery.length;
useEffect(() => {
if (id) {
dispatch(getSingleQuery(id || '')).unwrap();
}
}, [dispatch, id]);

return (
<div className="parent_container relative max-h-[80%] overflow-y-scroll overflow-hidden pb-4 mt-4 h-full">
{isLoading ? (
<div className="w-full absolute h-full flex items-center justify-center">
<ScaleLoader
color="#256490"
role="progressbar"
aria-label="single_product_loader"
/>
</div>
) : singleQueryNumber <= 0 ? (
<div className="w-full h-[90%] flex items-center justify-center ">
<h2 className="text-center font-bold text-2xl">No Messages</h2>
</div>
) : (
<>
<div className="tableWrapper mt-1 text-[1rem] mx-5 laptop:mx-10 bg-neutral-white px-8 rounded-md max-w-[100%] py-7">
<div className="flex justify-end">
<BackButton
isBordered
url="/dashboard/querries"
otherStyles="rounded-3xl p-2 bg-primary-lightblue/90 text-neutral-white hover:bg-primary-lightblue"
title={''}
/>
</div>
<div className="rounded-md">
{singleQuery.map((query: DynamicData, index: number) => (
<div key={index} className="space-y-4">
<h1 className="mb-5 text-2xl font-bold ">
{query.firstName} {query.lastName}
</h1>
<div className="flex gap-3 ipad:items-center flex-col tablet:flex-row ">
<h2 className="text-lg">
<span className="font-semibold pr-1">Email:</span>
<span className="italic">{query.email}</span>
</h2>
<CopyButton
name="Copy Email"
text="Copy Email"
value={query.email}
/>
</div>

<p className="text-lg">
<span className="font-semibold pr-1">Message:</span>
{query.message}
</p>
</div>
))}
</div>
</div>
</>
)}
</div>
);
};

export default SingleQuerries;
Loading

0 comments on commit 00fa2e2

Please sign in to comment.