feat: add session styles, fix expert list

This commit is contained in:
SD 2024-05-22 03:26:20 +04:00
parent c2e29cbef3
commit 8f00a5c41c
12 changed files with 721 additions and 484 deletions

789
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
import { apiClient } from '../lib/apiClient'; import { apiClient } from '../lib/apiClient';
import { GeneralFilter, ExpertsData, ExpertDetails } from '../types/experts'; import { GeneralFilter, ExpertsData, ExpertDetails } from '../types/experts';
export const getExpertsList = async (filter: GeneralFilter, locale: string) => { export const getExpertsList = async (locale: string, filter?: GeneralFilter) => {
const response = await apiClient.post( const response = await apiClient.post(
'/home/coachsearch1', '/home/coachsearch1',
{ ...filter }, { ...filter },

View File

@ -20,15 +20,7 @@ export async function generateStaticParams({
params: { locale }, params: { locale },
}: { params: { locale: string } }) { }: { params: { locale: string } }) {
const result: { locale: string, expertId: string }[] = []; const result: { locale: string, expertId: string }[] = [];
const experts = await getExpertsList({ const experts = await getExpertsList(locale, { themesTagIds: [] });
"themesTagIds": [
1,2,3,4,5,6,7,8
],
"priceFrom": null,
"priceTo": null,
"durationFrom": null,
"durationTo": null
}, locale);
experts?.coaches?.forEach(({ id }) => { experts?.coaches?.forEach(({ id }) => {
result.push({ locale, expertId: id.toString() }); result.push({ locale, expertId: id.toString() });
@ -118,7 +110,7 @@ export default async function ExpertItem({ params: { expertId = '', locale} }: {
{expert?.publicCoachDetails?.experiences && expert.publicCoachDetails.experiences?.map(generateDescription)} {expert?.publicCoachDetails?.experiences && expert.publicCoachDetails.experiences?.map(generateDescription)}
<ExpertPractice expert={expert} /> <ExpertPractice expert={expert} />
<h2 className="title-h2">All Offers by this Expert</h2> {/* <h2 className="title-h2">All Offers by this Expert</h2>
<div className="offers-list"> <div className="offers-list">
<div className="card-profile"> <div className="card-profile">
<div className="card-profile__skills"> <div className="card-profile__skills">
@ -164,7 +156,7 @@ export default async function ExpertItem({ params: { expertId = '', locale} }: {
</a> </a>
</div> </div>
</div> </div>
</div> </div> */}
</div> </div>
</div> </div>
); );

View File

@ -1,14 +1,14 @@
'use client' 'use client'
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { Tag } from 'antd'; import { Tag, Button } from 'antd';
import { RightOutlined } from '@ant-design/icons'; import { RightOutlined, PlusOutlined, LeftOutlined } from '@ant-design/icons';
import Image from 'next/image'; import Image from 'next/image';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { Link } from '../../../navigation'; import { Link } from '../../../navigation';
import { i18nText } from '../../../i18nKeys'; import { i18nText } from '../../../i18nKeys';
import { getDuration, getPrice } from '../../../utils/expert'; import { getDuration, getPrice } from '../../../utils/expert';
import { Session } from '../../../types/sessions'; import {PublicUser, Session} from '../../../types/sessions';
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../constants/common'; import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../constants/common';
import { getSessionDetails } from '../../../actions/profile'; import { getSessionDetails } from '../../../actions/profile';
import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { useLocalStorage } from '../../../hooks/useLocalStorage';
@ -18,15 +18,18 @@ type SessionDetailsProps = {
locale: string; locale: string;
sessionId: number; sessionId: number;
goBack: () => void; goBack: () => void;
activeTab: number;
}; };
export const SessionDetails = ({ sessionId, locale, goBack }: SessionDetailsProps) => { export const SessionDetails = ({ sessionId, locale, goBack, activeTab }: SessionDetailsProps) => {
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
const [userId] = useLocalStorage(AUTH_USER, ''); const [userId] = useLocalStorage(AUTH_USER, '');
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [errorData, setErrorData] = useState<any>(); const [errorData, setErrorData] = useState<any>();
const [session, setSession] = useState<Session>(); const [session, setSession] = useState<Session>();
console.log('activeTab', activeTab);
const fetchData = useCallback(() => { const fetchData = useCallback(() => {
setLoading(true); setLoading(true);
setErrorData(undefined); setErrorData(undefined);
@ -49,12 +52,82 @@ export const SessionDetails = ({ sessionId, locale, goBack }: SessionDetailsProp
fetchData(); fetchData();
}, [sessionId]); }, [sessionId]);
const client = session?.clients?.length ? session?.clients[0] : null;
const current = +userId !== client?.id ? client : session?.coach;
const startDate = session?.scheduledStartAtUtc ? dayjs(session?.scheduledStartAtUtc).locale(locale) : null; const startDate = session?.scheduledStartAtUtc ? dayjs(session?.scheduledStartAtUtc).locale(locale) : null;
const endDate = session?.scheduledEndAtUtc ? dayjs(session?.scheduledEndAtUtc).locale(locale) : null; const endDate = session?.scheduledEndAtUtc ? dayjs(session?.scheduledEndAtUtc).locale(locale) : null;
const today = startDate ? dayjs().format('YYYY-MM-DD') === startDate.format('YYYY-MM-DD') : false; const today = startDate ? dayjs().format('YYYY-MM-DD') === startDate.format('YYYY-MM-DD') : false;
const CoachCard = (coach?: PublicUser) => coach ? (
<div className="card-detail__expert">
<div className="card-detail__portrait">
<Image src={coach?.faceImageUrl || '/images/person.png'} width={140} height={140} alt="" />
</div>
<div className="card-detail__inner">
<Link href={`/experts/${coach?.id}` as any} target="_blank">
<div className="card-detail__name">{`${coach?.name} ${coach?.surname || ''}`}</div>
</Link>
{/* <div className="card-detail__info">
<div className="card-profile__subtitle">{coach?.specialityDesc}</div>
<div className="card-detail__lang">
{coach?.coachLanguages?.map((lang) => (
<Tag key={lang} className="skills__list__item">{lang}</Tag>
))}
</div>
</div> */}
<div className="card-detail__cost">
{getPrice(session?.cost)} <span>/ {getDuration(locale, session?.totalDuration)}</span>
</div>
<div className={`card-detail__date${today ? ' chosen': ''}${activeTab === 2 ? ' history' : ''}`}>
{today
? `${i18nText('today', locale)} ${startDate?.format('HH:mm')} - ${endDate?.format('HH:mm')}`
: `${startDate?.format('D MMMM')} ${startDate?.format('HH:mm')} - ${endDate?.format('HH:mm')}`}
</div>
<div className="card-detail__skills">
<div className="skills__list">
{session?.themesTags?.slice(0, 2).map((skill) => <Tag key={skill?.id} className="skills__list__item">{skill?.name}</Tag>)}
{session?.themesTags?.length > 2
? (
<Tag className="skills__list__more">
<Link href={`/experts/${coach?.id}` as any} target="_blank">
{`+${session?.themesTags?.length - 2}`}
</Link>
</Tag>
) : null }
</div>
</div>
{/* <div className="card-profile__desc">{coach?.description}</div> */}
<Link href={`/experts/${coach?.id}` as any} target="_blank" className="card-detail__more">
{i18nText('details', locale)}
<RightOutlined style={{ fontSize: '10px', padding: '0 7px' }}/>
</Link>
</div>
</div>
) : null;
const StudentCard = (student?: PublicUser | null) => student ? (
<div className="card-detail__expert">
<div className="card-detail__portrait">
<Image src={student?.faceImageUrl || '/images/person.png'} width={140} height={140} alt="" />
</div>
<div className="card-detail__inner">
<div className="card-detail__name">{`${student?.name} ${student?.surname || ''}`}</div>
<div className={`card-detail__date${today ? ' chosen': ''}${activeTab === 2 ? ' history' : ''}`}>
{today
? `${i18nText('today', locale)} ${startDate?.format('HH:mm')} - ${endDate?.format('HH:mm')}`
: `${startDate?.format('D MMMM')} ${startDate?.format('HH:mm')} - ${endDate?.format('HH:mm')}`}
</div>
<div className="card-detail__skills">
<div className="skills__list">
<Tag className="skills__list__item">{session?.themesTagName}</Tag>
</div>
</div>
{/* <div className="card-profile__desc">{student?.description}</div> */}
</div>
</div>
) : null;
const client = session?.clients?.length ? session?.clients[0] : null;
const Current = +userId !== client?.id ? StudentCard(client) : CoachCard(session?.coach);
return ( return (
<Loader <Loader
isLoading={loading} isLoading={loading}
@ -63,74 +136,91 @@ export const SessionDetails = ({ sessionId, locale, goBack }: SessionDetailsProp
> >
<div className="card-detail"> <div className="card-detail">
<div> <div>
<button onClick={goBack}>back</button> <Button
className="card-detail__back"
type="link"
icon={<LeftOutlined />}
onClick={goBack}
>
Back
</Button>
</div> </div>
<div className="card-detail__expert"> {Current}
<div className="card-detail__portrait"> {(activeTab === 0 || activeTab === 1) && (
<Image src={current?.faceImageUrl || '/images/person.png'} width={140} height={140} alt="" /> <div className="card-detail__actions">
{activeTab === 0 ? (
<>
<Button className="card-detail__apply">Start Session</Button>
<Button className="card-detail__decline">Decline Session</Button>
</>
) : (
<>
<Button className="card-detail__apply">Confirm Session</Button>
<Button className="card-detail__decline">Decline Session</Button>
</>
)}
</div> </div>
<div className="card-detail__inner"> )}
<Link href={`/experts/${current?.id}` as any} target="_blank"> {activeTab !== 1 && (
<div className="card-detail__name">{`${current?.name} ${current?.surname || ''}`}</div> <>
</Link> {activeTab === 2 && (
<div className="card-detail__info"> <>
<div className="card-detail__lang"> <div className="card-detail__name">Course Info</div>
{/* current?.coachLanguages?.map((lang) => ( <div className="card-detail__inner">
<Tag key={lang} className="skills__list__item">{lang}</Tag> {/* <div className="card-detail__info">
)) */} <div className="card-profile__subtitle">{current?.specialityDesc}</div>
<div className="card-detail__lang">
{current?.coachLanguages?.map((lang) => (
<Tag key={lang} className="skills__list__item">{lang}</Tag>
))}
</div>
</div> */}
<div className="card-detail__cost">
{getPrice(session?.cost)} <span>/ {getDuration(locale, session?.totalDuration)}</span>
</div>
<div className="card-detail__skills">
<div className="skills__list">
{session?.themesTags?.slice(0, 2).map((skill) => <Tag key={skill?.id} className="skills__list__item">{skill?.name}</Tag>)}
{session?.themesTags?.length > 2
? (
<Tag className="skills__list__more">
{`+${session?.themesTags?.length - 2}`}
</Tag>
) : null }
</div>
</div>
{/* <div className="card-profile__desc">{current?.description}</div> */}
</div>
</>
)}
<div className="card-detail__comments">
<div className="card-detail__comments_header">
<div className="card-detail__comments_title">
My Comments
</div>
{activeTab === 0 && (
<Button
className="card-detail__comments_add"
type="link"
iconPosition="end"
icon={<PlusOutlined style={{ fontSize: 18 }} />}
>
Add new
</Button>
)}
</div>
<div className="card-detail__comments_item">
Sed tincidunt finibus eros nec feugiat. Nulla facilisi. Nunc maximus magna et egestas tincidunt. Integer lobortis laoreet neque at sodales. Aenean eget risus pharetra, efficitur dolor ut, commodo lacus. Sed vitae nunc odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis et velit et dolor rutrum euismod a pretium est.
</div>
<div className="card-detail__comments_title">
Coach Comments
</div>
<div className="card-detail__comments_item">
Sed tincidunt finibus eros nec feugiat. Nulla facilisi. Nunc maximus magna et egestas tincidunt. Integer lobortis laoreet neque at sodales. Aenean eget risus pharetra, efficitur dolor ut, commodo lacus. Sed vitae nunc odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis et velit et dolor rutrum euismod a pretium est.
</div> </div>
</div> </div>
{/* <div className="card-profile__title">{current?.speciality}</div> */} </>
<div className="card-detail__coast"> )}
{getPrice(session?.cost)} <span>/ {getDuration(locale, session?.totalDuration)}</span>
</div>
<div className={`card-detail__date${today ? ' chosen': ''}`}>
{today
? `${i18nText('today', locale)} ${startDate.format('HH:mm')} - ${endDate.format('HH:mm')}`
: `${startDate.format('D MMMM')} ${startDate.format('HH:mm')} - ${endDate.format('HH:mm')}`}
</div>
<div className="card-detail__skills">
<div className="skills__list">
{session?.themesTags?.slice(0, 2).map((skill) => <Tag key={skill?.id} className="skills__list__item">{skill?.name}</Tag>)}
{session?.themesTags?.length > 2
? (
<Tag className="skills__list__more">
<Link href={`/experts/${current?.id}` as any} target="_blank">
{`+${session?.themesTags?.length - 2}`}
</Link>
</Tag>
) : null }
</div>
</div>
{/* <div className="card-profile__subtitle">{current?.specialityDesc}</div>
<div className="card-profile__desc">{current?.description}</div> */}
<Link href={`/experts/${current?.id}` as any} target="_blank">
{i18nText('details', locale)}
<RightOutlined style={{ fontSize: '10px', padding: '0 7px' }}/>
</Link>
</div>
</div>
<div className="card-detail__actions">
<button>Start Session</button>
<button>Decline Session</button>
</div>
<div className="card-detail__comments">
<div className="card-detail__comments_header">
<div className="card-detail__comments_title">
My Comments
</div>
<button>Add new</button>
</div>
<div className="card-detail__comments_item">
Sed tincidunt finibus eros nec feugiat. Nulla facilisi. Nunc maximus magna et egestas tincidunt. Integer lobortis laoreet neque at sodales. Aenean eget risus pharetra, efficitur dolor ut, commodo lacus. Sed vitae nunc odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis et velit et dolor rutrum euismod a pretium est.
</div>
<div className="card-detail__comments_title">
Coach Comments
</div>
<div className="card-detail__comments_item">
Sed tincidunt finibus eros nec feugiat. Nulla facilisi. Nunc maximus magna et egestas tincidunt. Integer lobortis laoreet neque at sodales. Aenean eget risus pharetra, efficitur dolor ut, commodo lacus. Sed vitae nunc odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis et velit et dolor rutrum euismod a pretium est.
</div>
</div>
</div> </div>
</Loader> </Loader>
); );

View File

@ -19,10 +19,11 @@ import { i18nText } from '../../../i18nKeys';
type SessionsTabsProps = { type SessionsTabsProps = {
locale: string; locale: string;
updateSession: (val: number) => void; updateSession: (val: number) => void;
activeTab: number;
updateTab: (tab: number) => void;
}; };
export const SessionsTabs = ({ locale, updateSession }: SessionsTabsProps) => { export const SessionsTabs = ({ locale, updateSession, activeTab, updateTab }: SessionsTabsProps) => {
const [activeTab, setActiveTab] = useState<number>(0);
const [sort, setSort] = useState<string>(); const [sort, setSort] = useState<string>();
const [sessions, setSessions] = useState<Sessions>(); const [sessions, setSessions] = useState<Sessions>();
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
@ -158,7 +159,7 @@ export const SessionsTabs = ({ locale, updateSession }: SessionsTabsProps) => {
<Space <Space
key={index} key={index}
className={`tabs-session__item ${index === activeTab ? 'active' : ''}`} className={`tabs-session__item ${index === activeTab ? 'active' : ''}`}
onClick={() => setActiveTab(index)} onClick={() => updateTab(index)}
> >
{tab.label} {tab.label}
</Space> </Space>

View File

@ -6,17 +6,21 @@ import { SessionsTabs } from './SessionsTabs';
export const SessionsAll = ({ locale }: { locale: string }) => { export const SessionsAll = ({ locale }: { locale: string }) => {
const [customSession, setCustomSession] = useState<number | undefined>(); const [customSession, setCustomSession] = useState<number | undefined>();
const [activeTab, setActiveTab] = useState<number>(0);
return customSession ? ( return customSession ? (
<SessionDetails <SessionDetails
locale={locale} locale={locale}
sessionId={customSession} sessionId={customSession}
goBack={() => setCustomSession(undefined)} goBack={() => setCustomSession(undefined)}
activeTab={activeTab}
/> />
) : ( ) : (
<SessionsTabs <SessionsTabs
locale={locale} locale={locale}
updateSession={setCustomSession} updateSession={setCustomSession}
activeTab={activeTab}
updateTab={setActiveTab}
/> />
); );
}; };

View File

@ -79,7 +79,7 @@ export const ExpertInformation: FC<ExpertDetailsProps> = ({ expert, locale }) =>
Sign Up Now Sign Up Now
</a> </a>
<div className="wrap-btn-prise__text"> <div className="wrap-btn-prise__text">
{`${sessionCost}${isRus ? '₽' : '€'}`} <span>/ {`${sessionDuration}${isRus ? 'мин' : 'min'}`}</span> {`${sessionCost}`} <span>/ {`${sessionDuration}${isRus ? 'мин' : 'min'}`}</span>
</div> </div>
</div> </div>
</> </>

View File

@ -18,7 +18,7 @@ export const Experts = async ({ basePath = '/', locale, pageSize = DEFAULT_PAGE_
const searchData = await getTagList(locale); const searchData = await getTagList(locale);
const languages = await getLanguages(locale); const languages = await getLanguages(locale);
const filter = getFilter({ pageSize }); const filter = getFilter({ pageSize });
const experts = await getExpertsList(filter, locale); const experts = await getExpertsList(locale, filter);
const durFrom = `${searchData?.sessionCostMin || 0}${locale === 'ru' ? 'мин' : 'min'}`; const durFrom = `${searchData?.sessionCostMin || 0}${locale === 'ru' ? 'мин' : 'min'}`;
const durTo = `${searchData?.sessionDurationMax || 0}${locale === 'ru' ? 'мин' : 'min'}`; const durTo = `${searchData?.sessionDurationMax || 0}${locale === 'ru' ? 'мин' : 'min'}`;
const priceFrom = `${searchData?.sessionCostMin || 0}`; const priceFrom = `${searchData?.sessionCostMin || 0}`;

View File

@ -8,7 +8,7 @@ import isEqual from 'lodash/isEqual';
import Image from 'next/image'; import Image from 'next/image';
import { Link, useRouter } from '../../navigation'; import { Link, useRouter } from '../../navigation';
import { ExpertsData, Filter, GeneralFilter } from '../../types/experts'; import { ExpertsData, Filter, GeneralFilter } from '../../types/experts';
import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filter'; import { getObjectByFilter, getObjectByAdditionalFilter, getSearchParamsString } from '../../utils/filter';
import { getDuration, getPrice } from '../../utils/expert'; import { getDuration, getPrice } from '../../utils/expert';
import { getExpertsList } from '../../actions/experts'; import { getExpertsList } from '../../actions/experts';
import { CustomPagination } from '../view/CustomPagination'; import { CustomPagination } from '../view/CustomPagination';
@ -44,7 +44,7 @@ export const ExpertsList = ({
if (!isEqual(baseFilter, filter)) { if (!isEqual(baseFilter, filter)) {
setLoading(true); setLoading(true);
getExpertsList(filter, locale) getExpertsList(locale, filter)
.then((experts) => { .then((experts) => {
setExperts(experts); setExperts(experts);
}) })
@ -70,10 +70,9 @@ export const ExpertsList = ({
delete newFilter.page; delete newFilter.page;
} }
router.push({ const search = getSearchParamsString(newFilter);
pathname: basePath as any,
query: newFilter router.push(search ? `${basePath}?${search}#filter` : `${basePath}#filter`);
});
}; };
const currentPage = searchParams.has('page') ? Number(searchParams.get('page')) : baseFilter.page; const currentPage = searchParams.has('page') ? Number(searchParams.get('page')) : baseFilter.page;

View File

@ -320,8 +320,8 @@ a {
margin-bottom: 16px; margin-bottom: 16px;
} }
.ant-collapse-ghost >.ant-collapse-item, .ant-collapse-item { .ant-collapse-ghost >.ant-collapse-item, .ant-collapse-item, .ant-collapse-item:last-child {
border-bottom: 1px solid #C4DFE6; border-bottom: 1px solid #C4DFE6 !important;
border-radius: 0 !important; border-radius: 0 !important;
} }

View File

@ -33,6 +33,14 @@
justify-content: flex-start; justify-content: flex-start;
} }
&__back {
color: #6FB98F !important;
padding: 0 !important;
@include rem(18);
font-weight: 600;
line-height: 120%;
}
&__comments { &__comments {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -60,6 +68,136 @@
font-weight: 500; font-weight: 500;
line-height: 133.333%; line-height: 133.333%;
} }
&_add {
color: #FF8A00 !important;
padding: 0 !important;
@include rem(15);
font-weight: 400;
line-height: 160%;
}
}
&__name {
color: #003B46;
@include rem(18);
font-weight: 600;
line-height: 150%;
}
&__info {
display: inline-flex;
gap: 8px;
align-items: center;
justify-content: flex-start;
}
&__subtitle {
color: #003B46;
@include rem(15);
font-weight: 600;
line-height: 133.333%;
}
&__cost {
color: #6FB98F;
@include rem(15);
font-style: normal;
font-weight: 600;
line-height: 120%;
span {
color: #B7B7B7;
}
}
&__date {
color: #66A5AD;
@include rem(12);
font-style: normal;
font-weight: 300;
line-height: 116.667%;
&.chosen {
color: #D93E5C;
}
&.history {
color: #C4C4C4 !important;
}
}
&__desc {
color: #66A5AD;
@include rem(13);
font-style: normal;
font-weight: 500;
line-height: 123.077%;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
&__more {
text-decoration: none;
color: #FF8A00 !important;
@include rem(15);
font-style: normal;
font-weight: 400;
line-height: 160%;
display: inline-flex;
align-items: center;
}
&__apply {
user-select: none;
outline: none !important;
border: none !important;
text-decoration: none;
cursor: pointer;
border-radius: 8px !important;
background: #FFBD00 !important;
box-shadow: none !important;
display: flex;
height: 54px !important;
padding: 15px 24px;
justify-content: center;
align-items: center;
color: #003B46 !important;
@include rem(15);
font-style: normal;
font-weight: 400;
line-height: 160%;
&:hover, &:active {
color: #003B46 !important;
}
}
&__decline {
user-select: none;
outline: none !important;
border: none !important;
text-decoration: none;
cursor: pointer;
border-radius: 8px !important;
background: #D93E5C !important;
box-shadow: none !important;
display: flex;
height: 54px !important;
padding: 15px 24px;
justify-content: center;
align-items: center;
color: #fff !important;
@include rem(15);
font-style: normal;
font-weight: 400;
line-height: 160%;
&:hover, &:active {
color: #fff !important;
}
} }
&__header { &__header {

View File

@ -1,6 +1,6 @@
import { Language } from './tags'; import { Language } from './tags';
type PublicUser = { export type PublicUser = {
id: number; id: number;
login?: string; login?: string;
name?: string; name?: string;