334 lines
14 KiB
TypeScript
334 lines
14 KiB
TypeScript
'use client'
|
||
import AnimatedBottomSheet from '@/components/bottomsheet/AnimatedBottomSheet';
|
||
import EqualizerIcon from '@/components/icons/EqualizerIcon';
|
||
import TelegramIcon from '@/components/icons/TelegramIcon';
|
||
import TabContainer from '@/components/tab/TabContainer';
|
||
import { TabHeader } from '@/components/tab/TabHeader';
|
||
import Comment from '@/components/utils/Comment';
|
||
import RateBar from '@/components/utils/RateBar';
|
||
import useToggle from '@/hooks/helpers/useToggle';
|
||
import { ArrowDown2, CallCalling, Clock, Gallery, InfoCircle, Instagram, Location, Star1, Whatsapp } from 'iconsax-react';
|
||
import { useQueryState } from 'next-usequerystate';
|
||
import Image from 'next/image';
|
||
import React from 'react'
|
||
import { useGetAbout, useGetReviews, useGetSchedules } from './hooks/useAboutData';
|
||
import AboutSkeleton from './components/AboutSkeleton';
|
||
|
||
const sortings = [
|
||
'جدیدترین',
|
||
'قدیمی ترین'
|
||
]
|
||
|
||
function AboutPage() {
|
||
|
||
const { data: aboutData, isLoading: aboutLoading } = useGetAbout();
|
||
const { data: reviewsData, isLoading: reviewsLoading } = useGetReviews();
|
||
const { data: schedulesData, isLoading: schedulesLoading } = useGetSchedules();
|
||
const [sorting, setSorting] = useQueryState('sortBy', { defaultValue: '0' });
|
||
const { state: sortingModal, toggle: toggleSortingModal, set: setSortingModal } = useToggle();
|
||
|
||
const shop = aboutData?.data;
|
||
const schedules = schedulesData?.data || [];
|
||
|
||
const isLoading = aboutLoading || reviewsLoading || schedulesLoading;
|
||
|
||
if (isLoading) {
|
||
return <AboutSkeleton />;
|
||
}
|
||
|
||
const changeSorting = (index: number) => {
|
||
setSorting(() => String(index));
|
||
setSortingModal(false);
|
||
}
|
||
|
||
const formatTime = (time: string): string => {
|
||
return time.substring(0, 5);
|
||
};
|
||
|
||
const getDayName = (weekDay: number): string => {
|
||
const days = ['یکشنبه', 'دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'];
|
||
return days[weekDay] || '';
|
||
};
|
||
|
||
const groupSchedulesByDay = () => {
|
||
const grouped: Record<number, typeof schedules> = {};
|
||
schedules
|
||
.filter(schedule => schedule.isActive)
|
||
.forEach(schedule => {
|
||
if (!grouped[schedule.weekDay]) {
|
||
grouped[schedule.weekDay] = [];
|
||
}
|
||
grouped[schedule.weekDay].push(schedule);
|
||
});
|
||
return grouped;
|
||
};
|
||
|
||
const getTodaySchedule = () => {
|
||
const today = new Date().getDay();
|
||
const todaySchedules = schedules.filter(schedule =>
|
||
schedule.isActive && schedule.weekDay === today
|
||
);
|
||
if (todaySchedules.length > 0) {
|
||
const firstSchedule = todaySchedules[0];
|
||
return `${formatTime(firstSchedule.openTime)} تا ${formatTime(firstSchedule.closeTime)}`;
|
||
}
|
||
return null;
|
||
};
|
||
|
||
const firstTab = () => {
|
||
if (!shop) return null;
|
||
|
||
return (
|
||
<section aria-labelledby="about-title" className='py-4'>
|
||
<section
|
||
className="bg-container rounded-container shadow-container p-4">
|
||
<div className="flex justify-between items-center border-b-[1.5px] border-gray-200 pb-[25px]">
|
||
<div className="">
|
||
<h2 className='text-sm2 font-bold leading-5'>{shop.name}</h2>
|
||
{shop.establishedYear && (
|
||
<p className="text-sm2 leading-5 mt-4">تاسیس: {shop.establishedYear}</p>
|
||
)}
|
||
{shop.tagNames && shop.tagNames.length > 0 && (
|
||
<p className="text-sm2 leading-5 mt-2">نوع محصولات: {shop.tagNames.join('، ')}</p>
|
||
)}
|
||
</div>
|
||
{shop.logo && (
|
||
<div className="rounded-normal overflow-clip">
|
||
<Image
|
||
alt='logo'
|
||
src={shop.logo}
|
||
width={88}
|
||
height={88}
|
||
unoptimized
|
||
priority
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
{shop.description && (
|
||
<div className="mt-[23px]">
|
||
<h3 className="text-sm2 font-bold leading-5">درباره مجموعه</h3>
|
||
<p className="text-sm2 leading-5 mt-3 border-spacing-48" style={{ wordSpacing: '0.01em' }}>
|
||
{shop.description}
|
||
</p>
|
||
{shop.images && shop.images.length > 0 && (
|
||
<div className='inline-flex gap-2 mt-[23px] items-center'>
|
||
<Gallery size={20} className='stroke-disabled-text' />
|
||
<span className='text-sm2 text-disabled-text font-medium pt-0.5'>عکس های فروشگاه</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
{(shop.phone || shop.telegram || shop.whatsapp || shop.instagram) && (
|
||
<section
|
||
className="bg-container rounded-container shadow-container pt-3 pb-3.5 px-[16px] mt-4 grid grid-cols-3 items-center">
|
||
<h2 className='text-sm2 font-medium leading-5'>ارتباط</h2>
|
||
<div className='col-span-1 text-center flex justify-center gap-4'>
|
||
{shop.phone &&
|
||
<a href={`tel://${shop.phone}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
|
||
<CallCalling className='stroke-foreground' size={24} />
|
||
</a>
|
||
}
|
||
{shop.telegram &&
|
||
<a href={`https://t.me/${shop.telegram}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
|
||
<TelegramIcon className='stroke-foreground' width={24} height={24} />
|
||
</a>
|
||
}
|
||
{shop.whatsapp &&
|
||
<a href={`https://whatsapp.com/?phone=${shop.whatsapp}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
|
||
<Whatsapp className='stroke-foreground' size={24} />
|
||
</a>
|
||
}
|
||
{shop.instagram &&
|
||
<a href={`https://instagram.com/${shop.instagram}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
|
||
<Instagram className='stroke-foreground' size={24} />
|
||
</a>
|
||
}
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{shop.address && (
|
||
<section className="bg-container rounded-container shadow-container px-4 pt-6 pb-6 mt-4">
|
||
<h2 className='text-sm2 font-medium leading-5'>آدرس</h2>
|
||
<p className='text-sm2 mt-[9px] leading-5'>{shop.address}</p>
|
||
{shop.latitude && shop.longitude && (
|
||
<div className='inline-flex gap-2 mt-[23px] items-center '>
|
||
<Location size={20} className='stroke-disabled-text' />
|
||
<span className='text-sm2 text-disabled-text '>موقعیت روی نقشه</span>
|
||
</div>
|
||
)}
|
||
</section>
|
||
)}
|
||
|
||
{schedules.length > 0 && getTodaySchedule() && (
|
||
<section
|
||
aria-label='ساعات کاری'
|
||
className="bg-container rounded-container shadow-container py-6 px-4 mt-4 flex justify-between items-center">
|
||
<div className="flex items-center gap-2 justify-start">
|
||
<Clock size={16} className='stroke-disabled-text' />
|
||
<div className='text-sm2 font-medium leading-5 mt-0.5 text-[#8C90A3]'>باز</div>
|
||
<div className='size-1.5 bg-[#D9D9D9] rounded-full'></div>
|
||
<div className='text-sm2 mt-0.5'>امروز از ساعت {getTodaySchedule()}</div>
|
||
</div>
|
||
<div className="">
|
||
<ArrowDown2 size={16} className='stroke-[#292D32]' />
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{schedules.length > 0 && (
|
||
<section>
|
||
{Object.entries(groupSchedulesByDay())
|
||
.sort(([a], [b]) => Number(a) - Number(b))
|
||
.map(([weekDay, daySchedules]) => (
|
||
<div
|
||
key={weekDay}
|
||
style={{ boxShadow: '0px 0px 14px 0px #0000000F' }}
|
||
className="bg-[#F6F6FA] dark:bg-border rounded-container leading-5 py-6 px-4 mt-4 flex justify-between items-center">
|
||
<div className="text-sm2">
|
||
{getDayName(Number(weekDay))}
|
||
</div>
|
||
<div className='text-sm2 font-light'>
|
||
{daySchedules.map((schedule, index) => (
|
||
<span key={schedule.id}>
|
||
{formatTime(schedule.openTime)} - {formatTime(schedule.closeTime)}
|
||
{index < daySchedules.length - 1 && '، '}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</section>
|
||
)}
|
||
</section>
|
||
)
|
||
}
|
||
|
||
const formatDate = (dateString: string): string => {
|
||
const date = new Date(dateString);
|
||
return date.toLocaleDateString('fa-IR', {
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric'
|
||
});
|
||
};
|
||
|
||
const secondTab = () => {
|
||
const reviews = (reviewsData?.data || []).filter(review => review.isApproved);
|
||
|
||
const sortedReviews = [...reviews].sort((a, b) => {
|
||
if (sorting === '0') {
|
||
// جدیدترین
|
||
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
||
} else {
|
||
// قدیمی ترین
|
||
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||
}
|
||
});
|
||
|
||
const averageRating = reviews.length > 0
|
||
? reviews.reduce((sum, review) => sum + review.rating, 0) / reviews.length
|
||
: 0;
|
||
|
||
const ratingCounts = {
|
||
5: reviews.filter(r => r.rating === 5).length,
|
||
4: reviews.filter(r => r.rating === 4).length,
|
||
3: reviews.filter(r => r.rating === 3).length,
|
||
2: reviews.filter(r => r.rating === 2).length,
|
||
1: reviews.filter(r => r.rating === 1).length,
|
||
};
|
||
|
||
const totalReviews = reviews.length;
|
||
const ratingPercentages = {
|
||
5: totalReviews > 0 ? Math.round((ratingCounts[5] / totalReviews) * 100) : 0,
|
||
4: totalReviews > 0 ? Math.round((ratingCounts[4] / totalReviews) * 100) : 0,
|
||
3: totalReviews > 0 ? Math.round((ratingCounts[3] / totalReviews) * 100) : 0,
|
||
2: totalReviews > 0 ? Math.round((ratingCounts[2] / totalReviews) * 100) : 0,
|
||
1: totalReviews > 0 ? Math.round((ratingCounts[1] / totalReviews) * 100) : 0,
|
||
};
|
||
|
||
return (
|
||
<section aria-labelledby="reviews-title" className='py-4'>
|
||
<section
|
||
aria-label='امتیاز کاربران'
|
||
className="bg-container rounded-container shadow-container p-4 py-6 grid grid-cols-2 items-center">
|
||
<div className="text-center font-bold text-5xl">
|
||
{averageRating.toFixed(1)}
|
||
</div>
|
||
<div className="flex flex-col w-fit items-end">
|
||
<RateBar className='mt-1' content='5' percentage={String(ratingPercentages[5])} />
|
||
<RateBar className='mt-1' content='4' percentage={String(ratingPercentages[4])} />
|
||
<RateBar className='mt-1' content='3' percentage={String(ratingPercentages[3])} />
|
||
<RateBar className='mt-1' content='2' percentage={String(ratingPercentages[2])} />
|
||
<RateBar className='mt-1' content='1' percentage={String(ratingPercentages[1])} />
|
||
</div>
|
||
</section>
|
||
|
||
<section className="bg-container rounded-container shadow-container pt-3 pb-3.5 px-[16px] mt-4">
|
||
<div className="flex justify-between items-center border-b-[1.5px] border-border pb-2">
|
||
<h2 className='text-sm2 font-medium leading-5'>نظرات کاربران</h2>
|
||
<button onClick={toggleSortingModal} className="rounded-xl h-8 bg-container ps-4 pe-2 py-1.5 inline-flex items-center justify-between gap-[7px]">
|
||
<EqualizerIcon className='dark:text-white' />
|
||
<span className="text-xs leading-5 font-medium">{sortings[+sorting]}</span>
|
||
</button>
|
||
</div>
|
||
<div className="">
|
||
{sortedReviews.length > 0 ? (
|
||
sortedReviews.map((review) => (
|
||
<article key={review.id}>
|
||
<Comment
|
||
className='pt-8'
|
||
user={review.user?.firstName + ' ' + review.user?.lastName}
|
||
rating={review.rating}
|
||
date={formatDate(review.createdAt)}
|
||
text={review.comment}
|
||
tags={[review.food?.title ?? '']}
|
||
positivePoints={review.positivePoints}
|
||
negativePoints={review.negativePoints}
|
||
/>
|
||
</article>
|
||
))
|
||
) : (
|
||
<p className='text-sm2 text-center py-8 text-disabled-text'>هنوز نظری ثبت نشده است</p>
|
||
)}
|
||
</div>
|
||
</section>
|
||
<AnimatedBottomSheet title="مرتب کردن بر اساس" visible={sortingModal} inDelay={150} onClick={toggleSortingModal}>
|
||
<div className="px-8.5 py-10 justify-between">
|
||
{sortings.map((v, i) => {
|
||
return (
|
||
<div key={i}>
|
||
<div onClick={() => changeSorting(i)} className="text-sm2 font-normal cursor-pointer">
|
||
{v}
|
||
</div>
|
||
{i < sortings.length - 1 && <hr className="border-white/40 dark:border-border mb-4 mt-4" />}
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
</AnimatedBottomSheet>
|
||
</section>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className='pt-8 h-full overflow-y-auto noscrollbar'>
|
||
<TabContainer>
|
||
<TabHeader
|
||
viewRenderer={firstTab()}
|
||
title='درباره ما' icon={<InfoCircle size={24} />}>
|
||
</TabHeader>
|
||
<TabHeader
|
||
viewRenderer={secondTab()}
|
||
title='امتیازات و نظرات' icon={<Star1 size={24} />}>
|
||
</TabHeader>
|
||
</TabContainer>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
|
||
export default AboutPage |