feat: rating feedback pagination

feat/discover
Arik Chakma 5 months ago
parent 75e933a6e4
commit f9ddbd2e31
  1. 30
      src/components/CustomRoadmap/CustomRoadmapRatingsModal.tsx
  2. 57
      src/components/CustomRoadmap/ListRoadmapRatings.tsx
  3. 16
      src/components/Rating/Rating.tsx

@ -37,30 +37,12 @@ export function CustomRoadmapRatingsModal(
]; ];
return ( return (
<Modal onClose={onClose} bodyClassName="bg-transparent shadow-none"> <Modal
{/*{canManage && (*/} onClose={onClose}
{/* <div className="mb-1 flex items-center gap-1">*/} bodyClassName="bg-transparent shadow-none"
{/* {tabs.map((tab) => {*/} wrapperClassName="h-auto"
{/* const isActive = tab.id === activeTab;*/} overlayClassName="items-start md:items-center"
>
{/* return (*/}
{/* <button*/}
{/* key={tab.id}*/}
{/* onClick={() => {*/}
{/* setActiveTab(tab.id);*/}
{/* }}*/}
{/* className={cn('rounded-md bg-white px-3 py-2 text-sm', {*/}
{/* 'bg-gray-100 font-medium text-black': isActive,*/}
{/* 'text-gray-500 hover:text-gray-700': !isActive,*/}
{/* })}*/}
{/* >*/}
{/* {tab.label}*/}
{/* </button>*/}
{/* );*/}
{/* })}*/}
{/* </div>*/}
{/*)}*/}
{activeTab === 'ratings' && ( {activeTab === 'ratings' && (
<RateRoadmapForm <RateRoadmapForm
ratings={ratings} ratings={ratings}

@ -8,6 +8,7 @@ import { Spinner } from '../ReactIcons/Spinner.tsx';
import { getRelativeTimeString } from '../../lib/date.ts'; import { getRelativeTimeString } from '../../lib/date.ts';
import { cn } from '../../lib/classname.ts'; import { cn } from '../../lib/classname.ts';
import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal.tsx'; import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal.tsx';
import { Pagination } from '../Pagination/Pagination.tsx';
export interface RoadmapRatingDocument { export interface RoadmapRatingDocument {
_id?: string; _id?: string;
@ -20,10 +21,16 @@ export interface RoadmapRatingDocument {
updatedAt: Date; updatedAt: Date;
} }
type ListRoadmapRatingsResponse = (RoadmapRatingDocument & { type ListRoadmapRatingsResponse = {
data: (RoadmapRatingDocument & {
name: string; name: string;
avatar: string; avatar?: string;
})[]; })[];
totalCount: number;
totalPages: number;
currPage: number;
perPage: number;
};
type ListRoadmapRatingsProps = { type ListRoadmapRatingsProps = {
roadmapSlug: string; roadmapSlug: string;
@ -41,11 +48,18 @@ export function ListRoadmapRatings(props: ListRoadmapRatingsProps) {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [ratings, setRatings] = useState<ListRoadmapRatingsResponse>([]); const [ratingsResponse, setRatingsResponse] =
useState<ListRoadmapRatingsResponse | null>(null);
const listRoadmapRatings = async (currPage: number = 1) => {
setIsLoading(true);
const listRoadmapRatings = async () => {
const { response, error } = await httpGet<ListRoadmapRatingsResponse>( const { response, error } = await httpGet<ListRoadmapRatingsResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-list-roadmap-ratings/${roadmapSlug}`, `${import.meta.env.PUBLIC_API_URL}/v1-list-roadmap-ratings/${roadmapSlug}`,
{
currPage,
perPage: 1,
},
); );
if (!response || error) { if (!response || error) {
@ -54,7 +68,7 @@ export function ListRoadmapRatings(props: ListRoadmapRatingsProps) {
return; return;
} }
setRatings(response); setRatingsResponse(response);
setError(''); setError('');
setIsLoading(false); setIsLoading(false);
}; };
@ -76,8 +90,10 @@ export function ListRoadmapRatings(props: ListRoadmapRatingsProps) {
); );
} }
const ratings = ratingsResponse?.data || [];
return ( return (
<div className="relative min-h-[100px] rounded-lg bg-white p-2 overflow-hidden"> <div className="relative min-h-[100px] overflow-auto rounded-lg bg-white p-2 md:max-h-[550px]">
{isLoading && ( {isLoading && (
<div className="absolute inset-0 flex items-center justify-center"> <div className="absolute inset-0 flex items-center justify-center">
<Spinner isDualRing={false} /> <Spinner isDualRing={false} />
@ -85,14 +101,20 @@ export function ListRoadmapRatings(props: ListRoadmapRatingsProps) {
)} )}
{!isLoading && ratings.length > 0 && ( {!isLoading && ratings.length > 0 && (
<div> <div className="relative">
<div className='text-sm px-2 py-1.5 text-yellow-900 mb-2 rounded-lg bg-yellow-50 flex items-center gap-1 justify-center'> <div className="sticky top-1.5 mb-2 flex items-center justify-center gap-1 rounded-lg bg-yellow-50 px-2 py-1.5 text-sm text-yellow-900">
<span>Rated <span className="font-medium">{averageRating.toFixed(1)}</span></span> <span>
Rated{' '}
<span className="font-medium">{averageRating.toFixed(1)}</span>
</span>
<Rating starSize={15} rating={averageRating} readOnly /> <Rating starSize={15} rating={averageRating} readOnly />
by <span className="font-medium">{totalWhoRated} user{totalWhoRated > 1 && 's'}</span> by{' '}
<span className="font-medium">
{totalWhoRated} user{totalWhoRated > 1 && 's'}
</span>
</div> </div>
<div className="flex flex-col"> <div className="mb-3 flex flex-col">
{ratings.map((rating) => { {ratings.map((rating) => {
const userAvatar = rating?.avatar const userAvatar = rating?.avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${rating.avatar}` ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${rating.avatar}`
@ -135,6 +157,17 @@ export function ListRoadmapRatings(props: ListRoadmapRatingsProps) {
); );
})} })}
</div> </div>
<Pagination
variant="minimal"
totalCount={ratingsResponse?.totalCount || 1}
currPage={ratingsResponse?.currPage || 1}
totalPages={ratingsResponse?.totalPages || 1}
perPage={ratingsResponse?.perPage || 1}
onPageChange={(page) => {
listRoadmapRatings(page).then();
}}
/>
</div> </div>
)} )}

@ -37,6 +37,10 @@ export function Rating(props: RatingProps) {
isActive ? 100 : hasDecimal ? decimalWidthPercentage : 0 isActive ? 100 : hasDecimal ? decimalWidthPercentage : 0
} }
onClick={() => { onClick={() => {
if (readOnly) {
return;
}
setStars(counter); setStars(counter);
onRatingChange?.(counter); onRatingChange?.(counter);
}} }}
@ -64,15 +68,15 @@ function RatingStar(props: RatingStarProps) {
const { onClick, widthPercentage = 100, starSize = 20, readOnly } = props; const { onClick, widthPercentage = 100, starSize = 20, readOnly } = props;
return ( return (
<button <div
onClick={onClick} className="relative block cursor-pointer text-gray-300 disabled:cursor-default aria-disabled:cursor-default"
className="relative block cursor-pointer text-gray-300 disabled:cursor-default"
style={{ style={{
width: `${starSize}px`, width: `${starSize}px`,
height: `${starSize}px`, height: `${starSize}px`,
}} }}
disabled={readOnly} onClick={onClick}
type="button" aria-disabled={readOnly}
role="button"
> >
<span className="absolute inset-0"> <span className="absolute inset-0">
<svg <svg
@ -112,6 +116,6 @@ function RatingStar(props: RatingStarProps) {
</svg> </svg>
</span> </span>
</span> </span>
</button> </div>
); );
} }

Loading…
Cancel
Save