Compare commits
9 Commits
77d3c8f66b
...
28f5babf22
Author | SHA1 | Date |
---|---|---|
dzfelix | 28f5babf22 | |
dzfelix | 80f53e871d | |
dzfelix | f7fe427aae | |
dzfelix | 5844bd9e7c | |
norton81 | c563818e91 | |
norton81 | 1461c4948e | |
SD | f92810d320 | |
SD | 6f5c3738b7 | |
Dasha | 526e703d9a |
File diff suppressed because it is too large
Load Diff
|
@ -6,7 +6,15 @@ import { message } from 'antd';
|
|||
import { ExpertData } from '../../../../../types/profile';
|
||||
import { AUTH_TOKEN_KEY } from '../../../../../constants/common';
|
||||
import { useLocalStorage } from '../../../../../hooks/useLocalStorage';
|
||||
import { getEducation, getPersonalData, getTags, getPractice, getSchedule, getPayData } from '../../../../../actions/profile';
|
||||
import {
|
||||
getEducation,
|
||||
getPersonalData,
|
||||
getTags,
|
||||
getPractice,
|
||||
getSchedule,
|
||||
getPayData,
|
||||
getUserData
|
||||
} from '../../../../../actions/profile';
|
||||
import { ExpertProfile } from '../../../../../components/ExpertProfile';
|
||||
import { Loader } from '../../../../../components/view/Loader';
|
||||
|
||||
|
@ -15,11 +23,13 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo
|
|||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [data, setData] = useState<ExpertData | undefined>();
|
||||
const [isFull, setIsFull] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (jwt) {
|
||||
setLoading(true);
|
||||
Promise.all([
|
||||
getUserData(locale, jwt),
|
||||
getPersonalData(locale, jwt),
|
||||
getEducation(locale, jwt),
|
||||
getTags(locale, jwt),
|
||||
|
@ -27,13 +37,12 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo
|
|||
getSchedule(locale, jwt),
|
||||
getPayData(locale, jwt)
|
||||
])
|
||||
.then(([person, education, tags, practice, schedule, payData]) => {
|
||||
.then(([profile, person, education, tags, practice, schedule, payData]) => {
|
||||
console.log('profile', profile);
|
||||
console.log('person', person);
|
||||
console.log('education', education);
|
||||
console.log('tags', tags);
|
||||
console.log('practice', practice);
|
||||
console.log('schedule', schedule);
|
||||
console.log('payData', payData);
|
||||
setIsFull(profile.fillProgress === 'full');
|
||||
setData({
|
||||
person,
|
||||
education,
|
||||
|
@ -56,6 +65,7 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo
|
|||
<Loader isLoading={loading}>
|
||||
{data && (
|
||||
<ExpertProfile
|
||||
isFull={isFull}
|
||||
locale={locale}
|
||||
data={data}
|
||||
updateData={setData}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import type {Metadata, ResolvingMetadata} from 'next';
|
||||
import { draftMode } from 'next/headers'
|
||||
import { notFound } from 'next/navigation';
|
||||
import {fetchBlogPost, fetchBlogPosts, Widget} from "../../../../lib/contentful/blogPosts";
|
||||
|
@ -23,7 +23,7 @@ export async function generateMetadata({ params }: BlogPostPageProps, parent: Re
|
|||
|
||||
return {
|
||||
title: blogPost.title,
|
||||
// description: blogPost.metaDescription
|
||||
description: blogPost.metaDescription
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,11 @@ export default async function ExpertItem({ params: { expertId = '', locale } }:
|
|||
{expert?.publicCoachDetails?.trainings && expert.publicCoachDetails.trainings?.map(generateDescription)}
|
||||
{expert?.publicCoachDetails?.mbas && expert.publicCoachDetails.mbas?.map(generateDescription)}
|
||||
{expert?.publicCoachDetails?.experiences && expert.publicCoachDetails.experiences?.map(generateDescription)}
|
||||
<ExpertPractice expert={expert} locale={locale} />
|
||||
<ExpertPractice
|
||||
themes={expert?.publicCoachDetails?.themesGroups}
|
||||
cases={expert?.publicCoachDetails?.practiceCases}
|
||||
locale={locale}
|
||||
/>
|
||||
|
||||
{/* <h2 className="title-h2">All Offers by this Expert</h2>
|
||||
<div className="offers-list">
|
||||
|
|
|
@ -12,7 +12,7 @@ import { validateImage } from '../../utils/account';
|
|||
import { useProfileSettings } from '../../actions/hooks/useProfileSettings';
|
||||
import { CustomInput } from '../view/CustomInput';
|
||||
import { OutlinedButton } from '../view/OutlinedButton';
|
||||
import { FilledYellowButton } from '../view/FilledButton';
|
||||
import {FilledButton, FilledSquareButton, FilledYellowButton} from '../view/FilledButton';
|
||||
import { DeleteAccountModal } from '../Modals/DeleteAccountModal';
|
||||
import { Loader } from '../view/Loader';
|
||||
|
||||
|
@ -55,14 +55,14 @@ export const ProfileSettings: FC<ProfileSettingsProps> = ({ locale }) => {
|
|||
languagesLinks: languagesLinks?.map(({ languageId }) => ({ languageId })) || []
|
||||
};
|
||||
|
||||
// if (photo) {
|
||||
// console.log(photo);
|
||||
// const formData = new FormData();
|
||||
// formData.append('file', photo as FileType);
|
||||
//
|
||||
// newProfile.faceImage = photo;
|
||||
// newProfile.isFaceImageKeepExisting = false;
|
||||
// }
|
||||
if (photo) {
|
||||
console.log(photo);
|
||||
const formData = new FormData();
|
||||
formData.append('file', photo as FileType);
|
||||
|
||||
newProfile.faceImage = `[${(photo as File).arrayBuffer()}]`;
|
||||
newProfile.isFaceImageKeepExisting = false;
|
||||
}
|
||||
|
||||
console.log(newProfile);
|
||||
|
||||
|
@ -99,13 +99,6 @@ export const ProfileSettings: FC<ProfileSettingsProps> = ({ locale }) => {
|
|||
return (
|
||||
<Loader isLoading={fetchLoading} refresh={fetchProfileSettings}>
|
||||
<Form form={form} className="form-settings">
|
||||
<div className="user-avatar">
|
||||
<div className="user-avatar__edit" style={profileSettings?.faceImageUrl ? { backgroundImage: `url(${profileSettings.faceImageUrl})` } : undefined}>
|
||||
<input className="" type="file" id="input-file" />
|
||||
<label htmlFor="input-file" className="form-label" />
|
||||
</div>
|
||||
<div className="user-avatar__text">{i18nText('photoDesc', locale)}</div>
|
||||
</div>
|
||||
<ImgCrop
|
||||
modalTitle="Редактировать"
|
||||
modalOk="Сохранить"
|
||||
|
@ -126,8 +119,17 @@ export const ProfileSettings: FC<ProfileSettingsProps> = ({ locale }) => {
|
|||
multiple={false}
|
||||
showUploadList={false}
|
||||
>
|
||||
{photo && <img height={100} width={100} src={URL.createObjectURL(photo)} />}
|
||||
<Button icon={<CameraOutlined />}>Click to Upload</Button>
|
||||
<div className="user-avatar">
|
||||
<div className="user-avatar__edit" style={photo
|
||||
? { backgroundImage: `url(${URL.createObjectURL(photo)})` }
|
||||
: profileSettings?.faceImageUrl ? { backgroundImage: `url(${profileSettings.faceImageUrl})`} : undefined }>
|
||||
<FilledSquareButton
|
||||
type="primary"
|
||||
icon={<CameraOutlined style={{ fontSize: 28 }} />}
|
||||
/>
|
||||
</div>
|
||||
<div className="user-avatar__text">{i18nText('photoDesc', locale)}</div>
|
||||
</div>
|
||||
</Upload>
|
||||
</ImgCrop>
|
||||
<div className="form-fieldset">
|
||||
|
|
|
@ -1,43 +1,96 @@
|
|||
'use client'
|
||||
|
||||
import { useState } from 'react';
|
||||
import { message } from 'antd';
|
||||
import { EditOutlined } from '@ant-design/icons';
|
||||
import React, { useState } from 'react';
|
||||
import {Alert, message} from 'antd';
|
||||
import Image from 'next/image';
|
||||
import { i18nText } from '../../i18nKeys';
|
||||
import { ExpertData } from '../../types/profile';
|
||||
import { ExpertData, PayInfo, ProfileData } from '../../types/profile';
|
||||
import { ExpertsTags } from '../../types/tags';
|
||||
import { PracticeDTO } from '../../types/practice';
|
||||
import { EducationDTO } from '../../types/education';
|
||||
import { ScheduleDTO } from '../../types/schedule';
|
||||
import { AUTH_TOKEN_KEY } from '../../constants/common';
|
||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||
import { getTags } from '../../actions/profile';
|
||||
import { getTags, getPayData, getEducation, getPractice, getSchedule, getPersonalData } from '../../actions/profile';
|
||||
import { Loader } from '../view/Loader';
|
||||
import { LinkButton } from '../view/LinkButton';
|
||||
import { ExpertTags } from './content/ExpertTags';
|
||||
import { ExpertSchedule } from './content/ExpertSchedule';
|
||||
import { ExpertPayData } from './content/ExpertPayData';
|
||||
import { ExpertEducation } from './content/ExpertEducation';
|
||||
import { ExpertAbout } from './content/ExpertAbout';
|
||||
|
||||
type ExpertProfileProps = {
|
||||
locale: string;
|
||||
data: ExpertData;
|
||||
updateData: (data: ExpertData) => void;
|
||||
isFull: boolean;
|
||||
};
|
||||
|
||||
export const ExpertProfile = ({ locale, data, updateData }: ExpertProfileProps) => {
|
||||
type NewDataPartProps<T> = {
|
||||
key: keyof ExpertData,
|
||||
getNewData: (locale: string, token: string) => Promise<T>,
|
||||
errorMessage?: string;
|
||||
};
|
||||
|
||||
export const ExpertProfile = ({ locale, data, updateData, isFull }: ExpertProfileProps) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const [loading, setLoading] = useState<(keyof ExpertData)[]>([]);
|
||||
|
||||
function getNewPartData <T>({ key, getNewData, errorMessage = 'Не удалось получить данные' }: NewDataPartProps<T>) {
|
||||
setLoading([key]);
|
||||
getNewData(locale, jwt)
|
||||
.then((newData) => {
|
||||
updateData({
|
||||
...data,
|
||||
[key]: newData
|
||||
});
|
||||
})
|
||||
.catch(() => message.error(errorMessage))
|
||||
.finally(() => setLoading([]));
|
||||
}
|
||||
|
||||
const updateExpert = (key: keyof ExpertData) => {
|
||||
switch (key) {
|
||||
case 'tags':
|
||||
setLoading([key]);
|
||||
getTags(locale, jwt)
|
||||
.then((tags) => {
|
||||
updateData({
|
||||
...data,
|
||||
tags
|
||||
getNewPartData<ExpertsTags>({
|
||||
key,
|
||||
getNewData: getTags,
|
||||
errorMessage: 'Не удалось получить направления'
|
||||
});
|
||||
break;
|
||||
case 'practice':
|
||||
getNewPartData<PracticeDTO>({
|
||||
key,
|
||||
getNewData: getPractice
|
||||
});
|
||||
break;
|
||||
case 'education':
|
||||
getNewPartData<EducationDTO>({
|
||||
key,
|
||||
getNewData: getEducation,
|
||||
errorMessage: 'Не удалось получить информацию об образовании'
|
||||
});
|
||||
break;
|
||||
case 'schedule':
|
||||
getNewPartData<ScheduleDTO>({
|
||||
key,
|
||||
getNewData: getSchedule,
|
||||
errorMessage: 'Не удалось получить расписание'
|
||||
});
|
||||
break;
|
||||
case 'person':
|
||||
getNewPartData<ProfileData>({
|
||||
key,
|
||||
getNewData: getPersonalData,
|
||||
errorMessage: 'Не удалось получить информацию о пользователе'
|
||||
});
|
||||
break;
|
||||
case 'payData':
|
||||
getNewPartData<{ person6Data?: PayInfo }>({
|
||||
key,
|
||||
getNewData: getPayData,
|
||||
errorMessage: 'Не удалось получить платежную информацию'
|
||||
});
|
||||
})
|
||||
.catch(() => message.error('Не удалось обновить направления'))
|
||||
.finally(() => setLoading([]));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -52,36 +105,39 @@ export const ExpertProfile = ({ locale, data, updateData }: ExpertProfileProps)
|
|||
<div className="coaching-info">
|
||||
<div className="coaching-profile">
|
||||
<div className="coaching-profile__portrait">
|
||||
<img src="/images/person.png" className="" alt="" />
|
||||
<Image src={data?.person?.faceImageUrl || '/images/user-avatar.png'} width={216} height={216} alt="" />
|
||||
</div>
|
||||
<div className="coaching-profile__inner">
|
||||
<div className="coaching-profile__inner" style={{ flex: 1 }}>
|
||||
<div className="coaching-profile__name">
|
||||
David
|
||||
{`${data?.person?.username} ${data?.person?.surname || ''}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">{i18nText('aboutCoach', locale)}</h2>
|
||||
<h2 className="title-h2">person1 + person4</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
{!isFull && (
|
||||
<Alert
|
||||
message="Проверьте заполненность блоков"
|
||||
description={(
|
||||
<ul className="b-rules-list">
|
||||
<li>о себе</li>
|
||||
<li>темы сессии</li>
|
||||
<li>рабочее расписание</li>
|
||||
<li>информация об образовании</li>
|
||||
<li>платежная информация</li>
|
||||
</ul>
|
||||
)}
|
||||
type="warning"
|
||||
showIcon
|
||||
/>
|
||||
</div>
|
||||
<div className="card-profile__header__title">
|
||||
{`12 ${i18nText('practiceHours', locale)}`}
|
||||
</div>
|
||||
<div className="card-profile__header__title ">
|
||||
{`15 ${i18nText('supervisionCount', locale)}`}
|
||||
</div>
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Loader isLoading={loading.includes('practice') || loading.includes('person')}>
|
||||
<ExpertAbout
|
||||
locale={locale}
|
||||
practice={data?.practice}
|
||||
person={data?.person}
|
||||
updateExpert={updateExpert}
|
||||
/>
|
||||
</Loader>
|
||||
<Loader isLoading={loading.includes('tags')}>
|
||||
<ExpertTags
|
||||
locale={locale}
|
||||
|
@ -90,9 +146,21 @@ export const ExpertProfile = ({ locale, data, updateData }: ExpertProfileProps)
|
|||
/>
|
||||
</Loader>
|
||||
<ExpertSchedule locale={locale} data={data?.schedule} />
|
||||
<ExpertEducation locale={locale} data={data?.education} />
|
||||
<ExpertPayData locale={locale} data={data?.payData?.person6Data} />
|
||||
<Loader isLoading={loading.includes('education')}>
|
||||
<ExpertEducation
|
||||
locale={locale}
|
||||
data={data?.education}
|
||||
updateExpert={updateExpert}
|
||||
/>
|
||||
</Loader>
|
||||
<Loader isLoading={loading.includes('payData')}>
|
||||
<ExpertPayData
|
||||
locale={locale}
|
||||
data={data?.payData?.person6Data}
|
||||
updateExpert={updateExpert}
|
||||
/>
|
||||
</Loader>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
'use client'
|
||||
|
||||
import {useState} from "react";
|
||||
import {Tag} from "antd";
|
||||
import {EditOutlined} from "@ant-design/icons";
|
||||
import {LinkButton} from "../../view/LinkButton";
|
||||
import {ExpertData, ProfileData} from "../../../types/profile";
|
||||
import {i18nText} from "../../../i18nKeys/index";
|
||||
import {PracticeDTO} from "../../../types/practice";
|
||||
import {ExpertPractice} from "../../Experts/ExpertDetails";
|
||||
import {EditExpertAboutModal} from "../../Modals/EditExpertAboutModal";
|
||||
|
||||
type ExpertAboutProps = {
|
||||
locale: string;
|
||||
practice?: PracticeDTO;
|
||||
person?: ProfileData;
|
||||
updateExpert: (key: keyof ExpertData) => void;
|
||||
};
|
||||
|
||||
export const ExpertAbout = ({ locale, updateExpert, practice, person }: ExpertAboutProps) => {
|
||||
const [showEdit, setShowEdit] = useState<boolean>(false);
|
||||
|
||||
const supervisionCount = practice?.person4Data?.supervisionPerYears && practice?.person4Data?.supervisionPerYearId
|
||||
? practice.person4Data.supervisionPerYears.filter(({ id }) => id === practice.person4Data.supervisionPerYearId)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">{i18nText('aboutCoach', locale)}</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => setShowEdit(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="coaching-section__info">
|
||||
<div className="coaching-section__practice">
|
||||
{`${practice?.person4Data?.practiceHours || 0} ${i18nText('practiceHours', locale)} | ${supervisionCount.length > 0 ? supervisionCount[0].name : 0} ${i18nText('supervisionCount', locale)}`}
|
||||
</div>
|
||||
<div className="coaching-section__list">
|
||||
{practice?.person4Data?.sessionCost && (
|
||||
<div className="coaching-section__item">
|
||||
<div>{i18nText('price', locale)}</div>
|
||||
<div>{`${practice?.person4Data?.sessionCost} €`}</div>
|
||||
</div>
|
||||
)}
|
||||
{practice?.person4Data?.sessionDuration && (
|
||||
<div className="coaching-section__item">
|
||||
<div>{i18nText('duration', locale)}</div>
|
||||
<div>{`${practice?.person4Data?.sessionDuration} ${locale === 'ru' ? 'мин' : 'min'}`}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="coaching-section__lang">
|
||||
<div>{i18nText('sessionLang', locale)}</div>
|
||||
<div className="skills__list">
|
||||
{person?.languagesLinks && person.languagesLinks?.length > 0 && person.languagesLinks
|
||||
.map(({ language: { code, nativeSpelling } }) => <Tag key={code} className="skills__list__item">{nativeSpelling}</Tag>)}
|
||||
</div>
|
||||
</div>
|
||||
<ExpertPractice
|
||||
locale={locale}
|
||||
themes={practice?.person4Data?.themesGroups}
|
||||
cases={practice?.person4Data?.practiceCases}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<EditExpertAboutModal
|
||||
locale={locale}
|
||||
open={showEdit}
|
||||
practice={practice}
|
||||
person={person}
|
||||
handleCancel={() => setShowEdit(false)}
|
||||
refreshPractice={() => updateExpert('practice')}
|
||||
refreshPerson={() => updateExpert('person')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,65 +1,154 @@
|
|||
'use client'
|
||||
|
||||
import { EditOutlined } from '@ant-design/icons';
|
||||
import { EducationDTO } from '../../../types/education';
|
||||
import { i18nText } from '../../../i18nKeys';
|
||||
import { LinkButton } from '../../view/LinkButton';
|
||||
import {ExpertCertificate} from "../../Experts/ExpertDetails";
|
||||
import {useState} from "react";
|
||||
import {ExpertData} from "../../../types/profile";
|
||||
import {EditExpertEducationModal} from "../../Modals/EditExpertEducationModal";
|
||||
|
||||
type ExpertEducationProps = {
|
||||
locale: string;
|
||||
data?: EducationDTO;
|
||||
updateExpert: (key: keyof ExpertData) => void;
|
||||
};
|
||||
|
||||
export const ExpertEducation = ({ locale, data, updateExpert }: ExpertEducationProps) => {
|
||||
const [showEdit, setShowEdit] = useState<boolean>(false);
|
||||
|
||||
const getAssociationLevel = (accLevelId?: number) => {
|
||||
if (accLevelId) {
|
||||
const [cur] = (data?.associationLevels || []).filter(({ id }) => id === accLevelId) || [];
|
||||
|
||||
return cur?.name || '';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const getAssociation = (accLevelId?: number) => {
|
||||
if (accLevelId) {
|
||||
const [curLevel] = (data?.associationLevels || []).filter(({ id }) => id === accLevelId) || [];
|
||||
if (curLevel) {
|
||||
const [cur] = (data?.associations || []).filter(({ id }) => id === curLevel.associationId) || [];
|
||||
return cur?.name || '';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
export const ExpertEducation = ({ locale, data }: ExpertEducationProps) => {
|
||||
return (
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">{i18nText('education', locale)}</h2>
|
||||
<h2 className="title-h2">person2</h2>
|
||||
<h2 className="title-h2">{i18nText('skillsInfo', locale)}</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => setShowEdit(true)}
|
||||
/>
|
||||
</div>
|
||||
{data?.person2Data?.educations?.length > 0 && (
|
||||
<div className="coaching-section__desc">
|
||||
<h3 className="title-h3">Psychologist</h3>
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
{data?.person2Data?.educations?.map(({ id, title, description, document }) => (
|
||||
<div key={id}>
|
||||
<h3 className="title-h3">{title}</h3>
|
||||
{description && <div className="base-text">{description}</div>}
|
||||
{document && (
|
||||
<div className="sertific">
|
||||
<img src="/images/sertific.png" className="" alt="" />
|
||||
<ExpertCertificate document={document} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{data?.person2Data?.certificates?.length > 0 && (
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">{i18nText('profCertification', locale)}</h2>
|
||||
<div className="coaching-section__desc">
|
||||
{data?.person2Data?.certificates?.map((cert) => (
|
||||
<div key={cert.id}>
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
{`${getAssociationLevel(cert?.associationLevelId)} ${getAssociation(cert?.associationLevelId)}`}
|
||||
</div>
|
||||
{cert.document && (
|
||||
<div className="sertific">
|
||||
<ExpertCertificate document={cert.document} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data?.person2Data?.trainings?.length > 0 && (
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">
|
||||
{`${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`}
|
||||
</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
{data?.person2Data?.trainings?.map(({ id, title, description, document }) => (
|
||||
<div key={id}>
|
||||
<h3 className="title-h3">{title}</h3>
|
||||
{description && <div className="base-text">{description}</div>}
|
||||
{document && (
|
||||
<div className="sertific">
|
||||
<ExpertCertificate document={document} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data?.person2Data?.mbas?.length > 0 && (
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">{i18nText('mba', locale)}</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
{data?.person2Data?.mbas?.map(({ id, title, description, document }) => (
|
||||
<div key={id}>
|
||||
<h3 className="title-h3">{title}</h3>
|
||||
{description && <div className="base-text">{description}</div>}
|
||||
{document && (
|
||||
<div className="sertific">
|
||||
<ExpertCertificate document={document} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data?.person2Data?.experiences?.length > 0 && (
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">{i18nText('mExperiences', locale)}</h2>
|
||||
<div className="coaching-section__desc">
|
||||
{data?.person2Data?.experiences?.map(({ id, title, description, document }) => (
|
||||
<div key={id}>
|
||||
<h3 className="title-h3">{title}</h3>
|
||||
{description && <div className="base-text">{description}</div>}
|
||||
{document && (
|
||||
<div className="sertific">
|
||||
<ExpertCertificate document={document} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<EditExpertEducationModal
|
||||
open={showEdit}
|
||||
handleCancel={() => setShowEdit(false)}
|
||||
locale={locale}
|
||||
data={data}
|
||||
refresh={() => updateExpert('education')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,28 +1,65 @@
|
|||
'use client'
|
||||
|
||||
import { useState } from 'react';
|
||||
import { EditOutlined } from '@ant-design/icons';
|
||||
import { i18nText } from '../../../i18nKeys';
|
||||
import { PayInfo } from '../../../types/profile';
|
||||
import { ExpertData, PayInfo } from '../../../types/profile';
|
||||
import { LinkButton } from '../../view/LinkButton';
|
||||
import { EditExpertPayDataModal } from '../../Modals/EditExpertPayDataModal';
|
||||
|
||||
type ExpertPayDataProps = {
|
||||
locale: string;
|
||||
data?: PayInfo
|
||||
data?: PayInfo;
|
||||
updateExpert: (key: keyof ExpertData) => void;
|
||||
};
|
||||
|
||||
export const ExpertPayData = ({ locale, data }: ExpertPayDataProps) => {
|
||||
export const ExpertPayData = ({ locale, data, updateExpert }: ExpertPayDataProps) => {
|
||||
const [showEdit, setShowEdit] = useState<boolean>(false);
|
||||
|
||||
const hide = (str?: string) => {
|
||||
const reg = new RegExp('(.)(?=.*....)', 'gi');
|
||||
return str ? str.replace(reg, '*') : '';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">Card data - person6</h2>
|
||||
<h2 className="title-h2">{i18nText('payInfo', locale)}</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => setShowEdit(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="base-text">
|
||||
Card
|
||||
<div className="base-text pay-data-list">
|
||||
{data?.beneficiaryName && (
|
||||
<div>
|
||||
<div>{i18nText('beneficiaryName', locale)}</div>
|
||||
<div>{data.beneficiaryName}</div>
|
||||
</div>
|
||||
)}
|
||||
{data?.bicOrSwift && (
|
||||
<div>
|
||||
<div>{i18nText('bicOrSwift', locale)}</div>
|
||||
<div>{hide(data.bicOrSwift)}</div>
|
||||
</div>
|
||||
)}
|
||||
{data?.iban && (
|
||||
<div>
|
||||
<div>IBAN</div>
|
||||
<div>{hide(data.iban)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<EditExpertPayDataModal
|
||||
locale={locale}
|
||||
open={showEdit}
|
||||
data={data}
|
||||
handleCancel={() => setShowEdit(false)}
|
||||
refresh={() => updateExpert('payData')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { EditOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import { ScheduleDTO } from '../../../types/schedule';
|
||||
import { i18nText } from '../../../i18nKeys';
|
||||
import { LinkButton } from '../../view/LinkButton';
|
||||
import {useState} from "react";
|
||||
import {Tag} from "antd";
|
||||
import {getCurrentTime} from "../../../utils/time";
|
||||
|
||||
type ExpertScheduleProps = {
|
||||
locale: string;
|
||||
|
@ -9,18 +13,34 @@ type ExpertScheduleProps = {
|
|||
};
|
||||
|
||||
export const ExpertSchedule = ({ locale, data }: ExpertScheduleProps) => {
|
||||
const [showEdit, setShowEdit] = useState<boolean>(false);
|
||||
// person51
|
||||
|
||||
return (
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">Schedule - person51</h2>
|
||||
<LinkButton
|
||||
<h2 className="title-h2">{i18nText('schedule', locale)}</h2>
|
||||
{/*<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
/>
|
||||
onClick={() => setShowEdit(true)}
|
||||
/>*/}
|
||||
</div>
|
||||
<div className="base-text">
|
||||
Schedule
|
||||
<div className="b-schedule-list">
|
||||
{data && data?.workingTimes?.map((date, index) => {
|
||||
const { startDay, startTime, endDay, endTime } = getCurrentTime(date);
|
||||
|
||||
return (
|
||||
<div key={`date_${index}`}>
|
||||
<Tag className="skills__list__item">{i18nText(startDay, locale)}</Tag>
|
||||
<div>{startTime}</div>
|
||||
<span>-</span>
|
||||
{startDay !== endDay && <Tag className="skills__list__item">{i18nText(endDay, locale)}</Tag>}
|
||||
<div>{endTime}</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,7 @@ export const ExpertTags = ({ locale, data, updateExpert }: ExpertTagsProps) => {
|
|||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">{i18nText('direction', locale)}</h2>
|
||||
<h2 className="title-h2">{i18nText('topics', locale)}</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
|
|
|
@ -4,7 +4,8 @@ import React, { FC } from 'react';
|
|||
import Image from 'next/image';
|
||||
import { Tag, Image as AntdImage, Space } from 'antd';
|
||||
import { ZoomInOutlined, ZoomOutOutlined, StarFilled } from '@ant-design/icons';
|
||||
import { ExpertDetails, ExpertDocument } from '../../types/experts';
|
||||
import { ExpertDetails, Practice, ThemeGroup } from '../../types/experts';
|
||||
import { ExpertDocument } from '../../types/file';
|
||||
import { Locale } from '../../types/locale';
|
||||
import { CustomRate } from '../view/CustomRate';
|
||||
import { i18nText } from '../../i18nKeys';
|
||||
|
@ -15,6 +16,12 @@ type ExpertDetailsProps = {
|
|||
locale?: string;
|
||||
};
|
||||
|
||||
type ExpertPracticeProps = {
|
||||
cases?: Practice[];
|
||||
themes?: ThemeGroup[];
|
||||
locale?: string;
|
||||
};
|
||||
|
||||
export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale }) => {
|
||||
const { publicCoachDetails } = expert || {};
|
||||
|
||||
|
@ -62,10 +69,10 @@ export const ExpertInformation: FC<ExpertDetailsProps> = ({ expert, locale }) =>
|
|||
<div className="expert-info">
|
||||
{/* <h2 className="title-h2">{}</h2> */}
|
||||
<div className="skills__list">
|
||||
{coachLanguages?.map((skill) => <Tag key={skill} className="skills__list__item">{skill}</Tag>)}
|
||||
{coachLanguages?.map((lang) => <Tag key={lang} className="skills__list__item">{lang}</Tag>)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="base-text">
|
||||
{/* <p className="base-text">
|
||||
Hello, my name is Marcelo. I am a Senior UX Designer with more than 6 years of experience working
|
||||
with the largest companies in the world such as Disney, Globant and currently IBM.
|
||||
During my career, I have helped organizations solve complex problems using aesthetically pleasing
|
||||
|
@ -79,7 +86,7 @@ export const ExpertInformation: FC<ExpertDetailsProps> = ({ expert, locale }) =>
|
|||
Strategic thinking <br /><br />
|
||||
|
||||
Oh, and I also speak Spanish!
|
||||
</p>
|
||||
</p> */}
|
||||
<div className="skills__list">
|
||||
{tags?.map((skill) => <Tag key={skill?.id} className="skills__list__item">{skill?.name}</Tag>)}
|
||||
</div>
|
||||
|
@ -93,14 +100,12 @@ export const ExpertInformation: FC<ExpertDetailsProps> = ({ expert, locale }) =>
|
|||
);
|
||||
};
|
||||
|
||||
export const ExpertPractice: FC<ExpertDetailsProps> = ({ expert, locale }) => {
|
||||
const { publicCoachDetails: { practiceCases = [], themesGroups = [] } } = expert || {};
|
||||
|
||||
return practiceCases?.length > 0 ? (
|
||||
export const ExpertPractice: FC<ExpertPracticeProps> = ({ themes = [], cases = [], locale }) => {
|
||||
return cases?.length > 0 ? (
|
||||
<div>
|
||||
<h3 className="title-h3">{i18nText('successfulCase', locale)}</h3>
|
||||
{practiceCases?.map(({ id, description, themesGroupIds }) => {
|
||||
const filtered = themesGroups?.filter(({ id }) => themesGroupIds?.includes(+id));
|
||||
{cases?.map(({ id, description, themesGroupIds }) => {
|
||||
const filtered = themes ? themes.filter(({ id }) => themesGroupIds?.includes(+id)) : [];
|
||||
|
||||
return (
|
||||
<div key={id} className="case-list">
|
||||
|
|
|
@ -162,18 +162,15 @@ export const ExpertsFilter = ({
|
|||
), [filter, searchParams, searchData]);
|
||||
|
||||
const getLangList = () => {
|
||||
const reg = searchLang ? new RegExp(searchLang, 'ig') : '';
|
||||
const langList = reg ? (languages || []).filter(({ code, nativeSpelling }) => reg.test(code) || reg.test(nativeSpelling)) : languages;
|
||||
const langList = searchLang ? (languages || []).filter(({ code, nativeSpelling }) => code.indexOf(searchLang) !== -1 || nativeSpelling.indexOf(searchLang) !== -1) : languages;
|
||||
return langList?.length
|
||||
? getList('userLanguages', langList.map(({ code, nativeSpelling }) => ({ id: code, name: nativeSpelling })))
|
||||
: null;
|
||||
};
|
||||
|
||||
const getTagsList = () => {
|
||||
const reg = searchTags ? new RegExp(searchTags, 'ig') : '';
|
||||
|
||||
if (reg) {
|
||||
const tagsList = filteredTags.filter(({ name, group }) => reg.test(name) || reg.test(group));
|
||||
if (searchTags) {
|
||||
const tagsList = filteredTags.filter(({ name, group }) => name.indexOf(searchTags) !== -1 || group.indexOf(searchTags) !== -1);
|
||||
return getList('themesTagIds', tagsList);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
'use client';
|
||||
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { Modal, Button, message, Form, Input } from 'antd';
|
||||
import { CloseOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { i18nText } from '../../i18nKeys';
|
||||
import { ProfileData, ProfileRequest } from '../../types/profile';
|
||||
import { PracticePersonData, PracticeDTO, PracticeData, PracticeCase } from '../../types/practice';
|
||||
import { AUTH_TOKEN_KEY } from '../../constants/common';
|
||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||
import { setPersonData, setPractice } from '../../actions/profile';
|
||||
import { CustomInput } from '../view/CustomInput';
|
||||
import { CustomMultiSelect } from '../view/CustomMultiSelect';
|
||||
import { CustomSelect } from '../view/CustomSelect';
|
||||
import { LinkButton } from '../view/LinkButton';
|
||||
|
||||
type EditExpertAboutModalProps = {
|
||||
open: boolean;
|
||||
handleCancel: () => void;
|
||||
locale: string;
|
||||
practice?: PracticeDTO;
|
||||
person?: ProfileData;
|
||||
refreshPractice: () => void;
|
||||
refreshPerson: () => void;
|
||||
};
|
||||
|
||||
type FormPerson = PracticePersonData & {
|
||||
sessionLang: number[];
|
||||
};
|
||||
|
||||
export const EditExpertAboutModal: FC<EditExpertAboutModalProps> = ({
|
||||
open,
|
||||
handleCancel,
|
||||
locale,
|
||||
practice,
|
||||
person,
|
||||
refreshPerson,
|
||||
refreshPractice
|
||||
}) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [form] = Form.useForm<FormPerson>();
|
||||
const [practiceCases, setPracticeCases] = useState<PracticeCase[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (practice?.person4Data) {
|
||||
form.setFieldsValue(practice.person4Data);
|
||||
|
||||
setPracticeCases(practice.person4Data?.practiceCases || []);
|
||||
}
|
||||
if (person?.languagesLinks) {
|
||||
form.setFieldValue('sessionLang', person.languagesLinks.map(({ languageId }) => languageId))
|
||||
}
|
||||
}
|
||||
}, [open, practice?.person4Data]);
|
||||
|
||||
const addPracticeCase = () => {
|
||||
setPracticeCases([
|
||||
...practiceCases,
|
||||
{
|
||||
description: '',
|
||||
themesGroupIds: []
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
const deletePracticeCase = (index: number) => {
|
||||
setPracticeCases([...practiceCases].filter((cases, i) => i !== index));
|
||||
};
|
||||
|
||||
const onChangePracticeDescription = (value: string, index: number) => {
|
||||
setPracticeCases(practiceCases.map((cases, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...cases,
|
||||
description: value
|
||||
}
|
||||
}
|
||||
|
||||
return cases;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChangePracticeThemes = (value: number[], index: number) => {
|
||||
setPracticeCases(practiceCases.map((cases, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...cases,
|
||||
themesGroupIds: value
|
||||
}
|
||||
}
|
||||
|
||||
return cases;
|
||||
}));
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
form.validateFields().then((values) => {
|
||||
const newPersonData: ProfileRequest = {
|
||||
login: person?.login,
|
||||
isPasswordKeepExisting: true,
|
||||
username: person?.username,
|
||||
surname: person?.surname,
|
||||
isFaceImageKeepExisting: true,
|
||||
phone: person?.phone,
|
||||
languagesLinks: values?.sessionLang?.map((id) => ({ languageId: +id })) || []
|
||||
};
|
||||
|
||||
const newPracticeData: PracticeData = {
|
||||
practiceHours: values?.practiceHours,
|
||||
supervisionPerYearId: values?.supervisionPerYearId,
|
||||
sessionDuration: values?.sessionDuration ? (isNaN(Number(values.sessionDuration)) ? 0 : Number(values.sessionDuration)) : 0,
|
||||
sessionCost: values?.sessionCost ? (isNaN(Number(values.sessionCost)) ? 0 : Number(values.sessionCost)) : 0,
|
||||
practiceCases: practiceCases ? practiceCases : practice?.person4Data?.practiceCases
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
Promise.all([
|
||||
setPractice(locale, jwt, newPracticeData),
|
||||
setPersonData(newPersonData, locale, jwt)
|
||||
])
|
||||
.then(() => {
|
||||
handleCancel();
|
||||
refreshPractice();
|
||||
refreshPerson();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('Не удалось сохранить данные');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="b-modal"
|
||||
open={open}
|
||||
title={undefined}
|
||||
onOk={undefined}
|
||||
onCancel={handleCancel}
|
||||
footer={false}
|
||||
width={498}
|
||||
closeIcon={<CloseOutlined style={{ fontSize: 20, color: '#000' }}/>}
|
||||
>
|
||||
<div className="b-modal__expert__content">
|
||||
<div className="b-modal__expert__title">{i18nText('aboutCoach', locale)}</div>
|
||||
<div className="b-modal__expert__inner">
|
||||
<Form form={form} style={{ width: '100%', display: 'flex', gap: 16, flexDirection: 'column' }}>
|
||||
<Form.Item
|
||||
name="sessionLang"
|
||||
noStyle
|
||||
>
|
||||
<CustomMultiSelect
|
||||
label={i18nText('sessionLang', locale)}
|
||||
options={person?.allLanguages?.map(({ id, nativeSpelling }) => ({ value: id, label: nativeSpelling })) || []}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="sessionDuration"
|
||||
noStyle
|
||||
>
|
||||
<CustomInput
|
||||
size="small"
|
||||
placeholder={i18nText('sessionDuration', locale)}
|
||||
autoComplete="off"
|
||||
addonAfter={locale === 'ru' ? 'мин' : 'min'}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="sessionCost"
|
||||
noStyle
|
||||
>
|
||||
<CustomSelect
|
||||
label={i18nText('sessionCost', locale)}
|
||||
options={practice?.person4Data?.sessionCosts?.map((cost) => ({ value: cost, label: cost })) || []}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="practiceHours"
|
||||
noStyle
|
||||
>
|
||||
<CustomInput
|
||||
size="small"
|
||||
placeholder={i18nText('experienceHours', locale)}
|
||||
autoComplete="off"
|
||||
addonAfter={locale === 'ru' ? 'часов' : 'hours'}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="supervisionPerYearId"
|
||||
noStyle
|
||||
>
|
||||
<CustomSelect
|
||||
label={i18nText('supervisionCount', locale)}
|
||||
options={practice?.person4Data?.supervisionPerYears?.map(({ id, name }) => ({ value: id, label: name })) || []}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
<div className="b-practice-cases">
|
||||
<div className="b-practice-case__header">
|
||||
<div>{i18nText('successfulCase', locale)}</div>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={addPracticeCase}
|
||||
/>
|
||||
</div>
|
||||
{practiceCases.map(({ description, themesGroupIds }, index) => (
|
||||
<div key={index} className="b-practice-case__item">
|
||||
<div className="b-practice-case__content">
|
||||
<div>
|
||||
<CustomMultiSelect
|
||||
value={themesGroupIds || []}
|
||||
label={i18nText('topics', locale)}
|
||||
options={practice?.person4Data?.themesGroups?.map(({ id, name }) => ({ value: id, label: name })) || []}
|
||||
onChange={(val) => onChangePracticeThemes(val, index)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Input.TextArea
|
||||
value={description}
|
||||
className="b-textarea"
|
||||
rows={2}
|
||||
placeholder={i18nText('description', locale)}
|
||||
onChange={(e) => onChangePracticeDescription(e.target.value, index)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<LinkButton
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => deletePracticeCase(index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="b-modal__expert__button">
|
||||
<Button
|
||||
className="card-detail__apply"
|
||||
onClick={onSave}
|
||||
loading={loading}
|
||||
>
|
||||
{i18nText('save', locale)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,152 @@
|
|||
'use client';
|
||||
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import {Modal, Button, message, Form, Collapse, GetProp, UploadProps} from 'antd';
|
||||
import type { CollapseProps } from 'antd';
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { i18nText } from '../../i18nKeys';
|
||||
import { PracticePersonData, PracticeDTO, PracticeData, PracticeCase } from '../../types/practice';
|
||||
import { AUTH_TOKEN_KEY } from '../../constants/common';
|
||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||
import {setEducation} from '../../actions/profile';
|
||||
import {Certificate, Details, EducationData, EducationDTO, Experience} from "../../types/education";
|
||||
import {CertificatesContent} from "./educationModalContent/Certificates";
|
||||
import {EducationsContent} from "./educationModalContent/Educations";
|
||||
import {TrainingsContent} from "./educationModalContent/Trainings";
|
||||
import {MbasContent} from "./educationModalContent/Mbas";
|
||||
import {ExperiencesContent} from "./educationModalContent/Experiences";
|
||||
|
||||
type EditExpertEducationModalProps = {
|
||||
open: boolean;
|
||||
handleCancel: () => void;
|
||||
locale: string;
|
||||
data?: EducationDTO;
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
type FormPerson = PracticePersonData & {
|
||||
sessionLang: number[];
|
||||
};
|
||||
|
||||
export const EditExpertEducationModal: FC<EditExpertEducationModalProps> = ({
|
||||
open,
|
||||
handleCancel,
|
||||
locale,
|
||||
data,
|
||||
refresh
|
||||
}) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [form] = Form.useForm<FormPerson>();
|
||||
const [editedData, setEditedData] = useState<EducationData>(data?.person2Data as EducationData);
|
||||
|
||||
const onSave = () => {
|
||||
setLoading(true);
|
||||
setEducation(locale, jwt, editedData)
|
||||
.then(() => {
|
||||
handleCancel();
|
||||
refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('Не удалось сохранить образование');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
})
|
||||
};
|
||||
|
||||
const items: CollapseProps['items'] = [
|
||||
{
|
||||
key: 'certificates',
|
||||
label: i18nText('profCertification', locale),
|
||||
children: (
|
||||
<CertificatesContent
|
||||
certificates={editedData?.certificates}
|
||||
update={(certificates) => setEditedData({ ...editedData, certificates })}
|
||||
locale={locale}
|
||||
associationLevels={data?.associationLevels}
|
||||
associations={data?.associations}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'educations',
|
||||
label: i18nText('education', locale),
|
||||
children: (
|
||||
<EducationsContent
|
||||
educations={editedData?.educations}
|
||||
update={(educations) => setEditedData({ ...editedData, educations })}
|
||||
locale={locale}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'trainings',
|
||||
label: `${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`,
|
||||
children: (
|
||||
<TrainingsContent
|
||||
trainings={editedData?.trainings}
|
||||
update={(trainings) => setEditedData({ ...editedData, trainings })}
|
||||
locale={locale}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'mbas',
|
||||
label: i18nText('mba', locale),
|
||||
children: (
|
||||
<MbasContent
|
||||
mbas={editedData?.mbas}
|
||||
update={(mbas) => setEditedData({ ...editedData, mbas })}
|
||||
locale={locale}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'experiences',
|
||||
label: i18nText('mExperiences', locale),
|
||||
children: (
|
||||
<ExperiencesContent
|
||||
experiences={editedData?.experiences}
|
||||
update={(experiences) => setEditedData({ ...editedData, experiences })}
|
||||
locale={locale}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="b-modal"
|
||||
open={open}
|
||||
title={undefined}
|
||||
onOk={undefined}
|
||||
onCancel={handleCancel}
|
||||
footer={false}
|
||||
width={498}
|
||||
closeIcon={<CloseOutlined style={{ fontSize: 20, color: '#000' }}/>}
|
||||
>
|
||||
<div className="b-modal__expert__content">
|
||||
<div className="b-modal__expert__title">{i18nText('skillsInfo', locale)}</div>
|
||||
<div className="b-modal__expert__inner" style={{ paddingRight: 12 }}>
|
||||
<Form form={form} style={{ width: '100%' }}>
|
||||
<Collapse
|
||||
ghost
|
||||
expandIconPosition="end"
|
||||
items={items}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
<div className="b-modal__expert__button">
|
||||
<Button
|
||||
className="card-detail__apply"
|
||||
onClick={onSave}
|
||||
loading={loading}
|
||||
>
|
||||
{i18nText('save', locale)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,118 @@
|
|||
'use client';
|
||||
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { Modal, Button, message, Form } from 'antd';
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { i18nText } from '../../i18nKeys';
|
||||
import { PayInfo } from '../../types/profile';
|
||||
import { AUTH_TOKEN_KEY } from '../../constants/common';
|
||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||
import { setPayData } from '../../actions/profile';
|
||||
import { CustomInput } from '../view/CustomInput';
|
||||
|
||||
type EditExpertPayDataModalProps = {
|
||||
open: boolean;
|
||||
handleCancel: () => void;
|
||||
locale: string;
|
||||
data?: PayInfo;
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
export const EditExpertPayDataModal: FC<EditExpertPayDataModalProps> = ({
|
||||
open,
|
||||
handleCancel,
|
||||
locale,
|
||||
data,
|
||||
refresh
|
||||
}) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [form] = Form.useForm<PayInfo>();
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (data) {
|
||||
form.setFieldsValue(data);
|
||||
} else {
|
||||
form.resetFields();
|
||||
}
|
||||
}
|
||||
}, [open, data]);
|
||||
|
||||
const onSavePayData = () => {
|
||||
form.validateFields().then(({ beneficiaryName, bicOrSwift, iban }) => {
|
||||
setLoading(true);
|
||||
setPayData(locale, jwt, { beneficiaryName, bicOrSwift, iban })
|
||||
.then(() => {
|
||||
handleCancel();
|
||||
refresh();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('Не удалось сохранить платежную информацию');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="b-modal"
|
||||
open={open}
|
||||
title={undefined}
|
||||
onOk={undefined}
|
||||
onCancel={handleCancel}
|
||||
footer={false}
|
||||
width={498}
|
||||
closeIcon={<CloseOutlined style={{ fontSize: 20, color: '#000' }}/>}
|
||||
>
|
||||
<div className="b-modal__expert__content">
|
||||
<div className="b-modal__expert__title">{i18nText('payInfo', locale)}</div>
|
||||
<div className="b-modal__expert__inner">
|
||||
<Form form={form} style={{ width: '100%', display: 'flex', gap: 16, flexDirection: 'column' }}>
|
||||
<Form.Item
|
||||
name="beneficiaryName"
|
||||
noStyle
|
||||
>
|
||||
<CustomInput
|
||||
size="small"
|
||||
placeholder={i18nText('beneficiaryName', locale)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="bicOrSwift"
|
||||
noStyle
|
||||
>
|
||||
<CustomInput
|
||||
size="small"
|
||||
placeholder={i18nText('bicOrSwift', locale)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="iban"
|
||||
noStyle
|
||||
>
|
||||
<CustomInput
|
||||
size="small"
|
||||
placeholder="IBAN"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
<div className="b-modal__expert__button">
|
||||
<Button
|
||||
className="card-detail__apply"
|
||||
onClick={onSavePayData}
|
||||
loading={loading}
|
||||
>
|
||||
{i18nText('save', locale)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -69,7 +69,7 @@ export const EditExpertTagsModal: FC<EditExpertTagsModalProps> = ({
|
|||
closeIcon={<CloseOutlined style={{ fontSize: 20, color: '#000' }}/>}
|
||||
>
|
||||
<div className="b-modal__expert__content">
|
||||
<div className="b-modal__expert__title">{i18nText('direction', locale)}</div>
|
||||
<div className="b-modal__expert__title">{i18nText('selectTopic', locale)}</div>
|
||||
<div className="b-modal__expert__inner">
|
||||
{data?.themesGroups && data.themesGroups.filter(({ isActive }) => isActive).map(({ id, name }) => (
|
||||
<div key={`group_${id}`}>
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
import { Upload, UploadFile } from 'antd';
|
||||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { Association, AssociationLevel, Certificate } from '../../../types/education';
|
||||
import { CustomSelect } from '../../view/CustomSelect';
|
||||
import { LinkButton } from '../../view/LinkButton';
|
||||
import { OutlinedButton } from '../../view/OutlinedButton';
|
||||
import { i18nText } from '../../../i18nKeys';
|
||||
import { validateDoc } from '../../../utils/account';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
import { AUTH_TOKEN_KEY } from '../../../constants/common';
|
||||
|
||||
type CertificatesContentProps = {
|
||||
certificates?: Certificate[];
|
||||
update: (cert?: Certificate[]) => void;
|
||||
associations?: Association[];
|
||||
associationLevels?: AssociationLevel[];
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export const CertificatesContent = ({
|
||||
certificates,
|
||||
update,
|
||||
associations,
|
||||
associationLevels,
|
||||
locale
|
||||
}: CertificatesContentProps) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
|
||||
const addCertificate = () => {
|
||||
const cert = {
|
||||
associationLevelId: undefined,
|
||||
document: null
|
||||
};
|
||||
|
||||
update(certificates?.length > 0
|
||||
? [
|
||||
...certificates,
|
||||
cert
|
||||
]
|
||||
: [cert]);
|
||||
};
|
||||
|
||||
const deleteCertificate = (index: number) => {
|
||||
update([...certificates].filter((cert, i) => i !== index));
|
||||
};
|
||||
|
||||
const beforeUpload = (file: UploadFile) => {
|
||||
const isValid = validateDoc(file);
|
||||
|
||||
if (!isValid) {
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const onRemoveFile = (index: number) => {
|
||||
update(certificates?.map((cert, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...cert,
|
||||
document: null,
|
||||
}
|
||||
}
|
||||
|
||||
return cert;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChangeAssociation = (val: number, index: number) => {
|
||||
update(certificates?.map((cert, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...cert,
|
||||
associationId: val,
|
||||
associationLevelId: undefined
|
||||
}
|
||||
}
|
||||
|
||||
return cert;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChangeLevel = (val: number, index: number) => {
|
||||
update(certificates?.map((cert, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...cert,
|
||||
associationLevelId: val
|
||||
}
|
||||
}
|
||||
|
||||
return cert;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChange = (file: any, index: number) => {
|
||||
if (file?.response) {
|
||||
update([...certificates].map((cert, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...cert,
|
||||
document: file?.response || null,
|
||||
}
|
||||
}
|
||||
|
||||
return cert;
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="b-edu-content">
|
||||
<div className="b-edu-list">
|
||||
{certificates?.map(({ associationId, associationLevelId, document: file }, index) => {
|
||||
let cAssociationId = associationId;
|
||||
|
||||
if (!cAssociationId) {
|
||||
const [cAssLvl] = associationLevels ? associationLevels.filter(({ id }) => id === associationLevelId) : [];
|
||||
|
||||
if (cAssLvl?.associationId) {
|
||||
cAssociationId = associations ? associations.filter(({ id }) => id === cAssLvl.associationId)[0]?.id : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="b-edu-list__item" key={`cert_${index}`}>
|
||||
<div>
|
||||
<CustomSelect
|
||||
value={cAssociationId}
|
||||
label={i18nText('association', locale)}
|
||||
options={associations?.map(({ id, name }) => ({ value: id, label: name })) || []}
|
||||
onChange={(val) => onChangeAssociation(val, index)}
|
||||
style={{ maxWidth: 320, minWidth: 320 }}
|
||||
/>
|
||||
<CustomSelect
|
||||
value={associationLevelId}
|
||||
label={i18nText('level', locale)}
|
||||
options={associationLevels && associationLevels.length > 0
|
||||
? associationLevels
|
||||
.filter(({ associationId }) => associationId === cAssociationId)
|
||||
.map(({ id, name }) => ({ value: id, label: name }))
|
||||
: []}
|
||||
onChange={(val) => onChangeLevel(val, index)}
|
||||
/>
|
||||
{/*<Upload
|
||||
fileList={tmpFile ? [tmpFile] : file ? [
|
||||
{
|
||||
uid: file.original?.id,
|
||||
name: file.fileName,
|
||||
status: 'done',
|
||||
url: file.original?.url
|
||||
}
|
||||
] : undefined}
|
||||
accept=".jpg,.jpeg,.png,.pdf"
|
||||
beforeUpload={(file) => beforeUpload(file as UploadFile, index)}
|
||||
multiple={false}
|
||||
onRemove={() => onRemoveFile(index)}
|
||||
>
|
||||
<LinkButton type="link">{i18nText('addDiploma', locale)}</LinkButton>
|
||||
</Upload>*/}
|
||||
<Upload
|
||||
fileList={file ? [
|
||||
{
|
||||
uid: file.original?.id,
|
||||
name: file.fileName,
|
||||
status: 'done',
|
||||
url: file.original?.url
|
||||
}
|
||||
] : undefined}
|
||||
accept=".jpg,.jpeg,.png,.pdf"
|
||||
beforeUpload={beforeUpload}
|
||||
multiple={false}
|
||||
onRemove={() => onRemoveFile(index)}
|
||||
action="https://api.bbuddy.expert/api/home/uploadfile"
|
||||
method="POST"
|
||||
headers={{
|
||||
authorization: `Bearer ${jwt}`,
|
||||
'X-User-Language': locale,
|
||||
'X-Referrer-Channel': 'site',
|
||||
}}
|
||||
onChange={(obj) => onChange(obj.file, index)}
|
||||
>
|
||||
<LinkButton type="link">{i18nText('addDiploma', locale)}</LinkButton>
|
||||
</Upload>
|
||||
</div>
|
||||
<LinkButton
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => deleteCertificate(index)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<OutlinedButton
|
||||
type="link"
|
||||
onClick={addCertificate}
|
||||
>
|
||||
{i18nText('addNew', locale)}
|
||||
</OutlinedButton>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,166 @@
|
|||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { CustomInput } from '../../view/CustomInput';
|
||||
import { LinkButton } from '../../view/LinkButton';
|
||||
import { OutlinedButton } from '../../view/OutlinedButton';
|
||||
import { Details } from '../../../types/education';
|
||||
import { i18nText } from '../../../i18nKeys';
|
||||
import { Upload, UploadFile } from 'antd';
|
||||
import { validateDoc } from '../../../utils/account';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
import { AUTH_TOKEN_KEY } from '../../../constants/common';
|
||||
|
||||
type EducationsContentProps = {
|
||||
educations?: Details[];
|
||||
update: (edu?: Details[]) => void;
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export const EducationsContent = ({
|
||||
educations,
|
||||
update,
|
||||
locale
|
||||
}: EducationsContentProps) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
|
||||
const addEdu = () => {
|
||||
const edu = {
|
||||
title: undefined,
|
||||
description: undefined,
|
||||
document: null
|
||||
};
|
||||
|
||||
update(educations?.length > 0
|
||||
? [
|
||||
...educations,
|
||||
edu
|
||||
]
|
||||
: [edu]);
|
||||
};
|
||||
|
||||
const deleteEdu = (index: number) => {
|
||||
update([...educations].filter((ed, i) => i !== index));
|
||||
};
|
||||
|
||||
const beforeUpload = (file: UploadFile) => {
|
||||
const isValid = validateDoc(file);
|
||||
|
||||
if (!isValid) {
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const onRemoveFile = (index: number) => {
|
||||
update(educations?.map((edu, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...edu,
|
||||
document: null,
|
||||
}
|
||||
}
|
||||
|
||||
return edu;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChange = (file: any, index: number) => {
|
||||
if (file?.response) {
|
||||
update([...educations].map((edu, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...edu,
|
||||
document: file?.response || null,
|
||||
}
|
||||
}
|
||||
|
||||
return edu;
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeUniversity = (val: string, index: number) => {
|
||||
update(educations?.map((edu, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...edu,
|
||||
title: val,
|
||||
}
|
||||
}
|
||||
|
||||
return edu;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChangeDesc = (val: string, index: number) => {
|
||||
update(educations?.map((edu, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...edu,
|
||||
description: val,
|
||||
}
|
||||
}
|
||||
|
||||
return edu;
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="b-edu-content">
|
||||
<div className="b-edu-list">
|
||||
{educations?.map(({ title, description, document: file}, index) => (
|
||||
<div className="b-edu-list__item" key={`edu_${index}`}>
|
||||
<div>
|
||||
<CustomInput
|
||||
value={title}
|
||||
placeholder={i18nText('university', locale)}
|
||||
onChange={(e) => onChangeUniversity(e?.target?.value, index)}
|
||||
/>
|
||||
<CustomInput
|
||||
value={description}
|
||||
placeholder={i18nText('description', locale)}
|
||||
onChange={(e) => onChangeDesc(e?.target?.value, index)}
|
||||
/>
|
||||
<Upload
|
||||
fileList={file ? [
|
||||
{
|
||||
uid: file.original?.id,
|
||||
name: file.fileName,
|
||||
status: 'done',
|
||||
url: file.original?.url
|
||||
}
|
||||
] : undefined}
|
||||
accept=".jpg,.jpeg,.png,.pdf"
|
||||
beforeUpload={beforeUpload}
|
||||
multiple={false}
|
||||
onRemove={() => onRemoveFile(index)}
|
||||
action="https://api.bbuddy.expert/api/home/uploadfile"
|
||||
method="POST"
|
||||
headers={{
|
||||
authorization: `Bearer ${jwt}`,
|
||||
'X-User-Language': locale,
|
||||
'X-Referrer-Channel': 'site',
|
||||
}}
|
||||
onChange={(obj) => onChange(obj.file, index)}
|
||||
>
|
||||
<LinkButton type="link">{i18nText('addDiploma', locale)}</LinkButton>
|
||||
</Upload>
|
||||
</div>
|
||||
<LinkButton
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => deleteEdu(index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<OutlinedButton
|
||||
type="link"
|
||||
onClick={addEdu}
|
||||
>
|
||||
{i18nText('addNew', locale)}
|
||||
</OutlinedButton>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,101 @@
|
|||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { CustomInput } from '../../view/CustomInput';
|
||||
import { LinkButton } from '../../view/LinkButton';
|
||||
import { OutlinedButton } from '../../view/OutlinedButton';
|
||||
import { Experience } from '../../../types/education';
|
||||
import { i18nText } from '../../../i18nKeys';
|
||||
import {useLocalStorage} from "../../../hooks/useLocalStorage";
|
||||
import {AUTH_TOKEN_KEY} from "../../../constants/common";
|
||||
|
||||
type ExperiencesContentProps = {
|
||||
experiences?: Experience[];
|
||||
update: (ex?: Experience[]) => void;
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export const ExperiencesContent = ({
|
||||
experiences,
|
||||
update,
|
||||
locale
|
||||
}: ExperiencesContentProps) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
|
||||
const addExperience = () => {
|
||||
const ex = {
|
||||
title: undefined,
|
||||
description: undefined,
|
||||
};
|
||||
|
||||
update(experiences?.length > 0
|
||||
? [
|
||||
...experiences,
|
||||
ex
|
||||
]
|
||||
: [ex]);
|
||||
};
|
||||
|
||||
const deleteExperience = (index: number) => {
|
||||
update([...experiences].filter((ex, i) => i !== index));
|
||||
};
|
||||
|
||||
const onChangeName = (val: string, index: number) => {
|
||||
update(experiences?.map((ex, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...ex,
|
||||
title: val,
|
||||
}
|
||||
}
|
||||
|
||||
return ex;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChangeDesc = (val: string, index: number) => {
|
||||
update(experiences?.map((ex, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...ex,
|
||||
description: val,
|
||||
}
|
||||
}
|
||||
|
||||
return ex;
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="b-edu-content">
|
||||
<div className="b-edu-list">
|
||||
{experiences?.map(({ title, description}, index) => (
|
||||
<div className="b-edu-list__item" key={`ex_${index}`}>
|
||||
<div>
|
||||
<CustomInput
|
||||
value={title}
|
||||
placeholder={i18nText('name', locale)}
|
||||
onChange={(e) => onChangeName(e?.target?.value, index)}
|
||||
/>
|
||||
<CustomInput
|
||||
value={description}
|
||||
placeholder={i18nText('description', locale)}
|
||||
onChange={(e) => onChangeDesc(e?.target?.value, index)}
|
||||
/>
|
||||
</div>
|
||||
<LinkButton
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => deleteExperience(index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<OutlinedButton
|
||||
type="link"
|
||||
onClick={addExperience}
|
||||
>
|
||||
{i18nText('addNew', locale)}
|
||||
</OutlinedButton>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,166 @@
|
|||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { CustomInput } from '../../view/CustomInput';
|
||||
import { LinkButton } from '../../view/LinkButton';
|
||||
import { OutlinedButton } from '../../view/OutlinedButton';
|
||||
import { Details } from '../../../types/education';
|
||||
import { i18nText } from '../../../i18nKeys';
|
||||
import { Upload, UploadFile } from 'antd';
|
||||
import { validateDoc } from '../../../utils/account';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
import { AUTH_TOKEN_KEY } from '../../../constants/common';
|
||||
|
||||
type MbasContentProps = {
|
||||
mbas?: Details[];
|
||||
update: (mba?: Details[]) => void;
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export const MbasContent = ({
|
||||
mbas,
|
||||
update,
|
||||
locale
|
||||
}: MbasContentProps) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
|
||||
const addMba = () => {
|
||||
const mba = {
|
||||
title: undefined,
|
||||
description: undefined,
|
||||
document: null
|
||||
};
|
||||
|
||||
update(mbas?.length > 0
|
||||
? [
|
||||
...mbas,
|
||||
mba
|
||||
]
|
||||
: [mba]);
|
||||
};
|
||||
|
||||
const deleteMba = (index: number) => {
|
||||
update([...mbas].filter((mba, i) => i !== index));
|
||||
};
|
||||
|
||||
const beforeUpload = (file: UploadFile) => {
|
||||
const isValid = validateDoc(file);
|
||||
|
||||
if (!isValid) {
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const onRemoveFile = (index: number) => {
|
||||
update(mbas?.map((mb, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...mb,
|
||||
document: null,
|
||||
}
|
||||
}
|
||||
|
||||
return mb;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChange = (file: any, index: number) => {
|
||||
if (file?.response) {
|
||||
update([...mbas].map((mb, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...mb,
|
||||
document: file?.response || null,
|
||||
}
|
||||
}
|
||||
|
||||
return mb;
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeName = (val: string, index: number) => {
|
||||
update(mbas?.map((mb, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...mb,
|
||||
title: val,
|
||||
}
|
||||
}
|
||||
|
||||
return mb;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChangeDesc = (val: string, index: number) => {
|
||||
update(mbas?.map((mb, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...mb,
|
||||
description: val,
|
||||
}
|
||||
}
|
||||
|
||||
return mb;
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="b-edu-content">
|
||||
<div className="b-edu-list">
|
||||
{mbas?.map(({ title, description, document: file}, index) => (
|
||||
<div className="b-edu-list__item" key={`mba_${index}`}>
|
||||
<div>
|
||||
<CustomInput
|
||||
value={title}
|
||||
placeholder={i18nText('university', locale)}
|
||||
onChange={(e) => onChangeName(e?.target?.value, index)}
|
||||
/>
|
||||
<CustomInput
|
||||
value={description}
|
||||
placeholder={i18nText('description', locale)}
|
||||
onChange={(e) => onChangeDesc(e?.target?.value, index)}
|
||||
/>
|
||||
<Upload
|
||||
fileList={file ? [
|
||||
{
|
||||
uid: file.original?.id,
|
||||
name: file.fileName,
|
||||
status: 'done',
|
||||
url: file.original?.url
|
||||
}
|
||||
] : undefined}
|
||||
accept=".jpg,.jpeg,.png,.pdf"
|
||||
beforeUpload={beforeUpload}
|
||||
multiple={false}
|
||||
onRemove={() => onRemoveFile(index)}
|
||||
action="https://api.bbuddy.expert/api/home/uploadfile"
|
||||
method="POST"
|
||||
headers={{
|
||||
authorization: `Bearer ${jwt}`,
|
||||
'X-User-Language': locale,
|
||||
'X-Referrer-Channel': 'site',
|
||||
}}
|
||||
onChange={(obj) => onChange(obj.file, index)}
|
||||
>
|
||||
<LinkButton type="link">{i18nText('addDiploma', locale)}</LinkButton>
|
||||
</Upload>
|
||||
</div>
|
||||
<LinkButton
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => deleteMba(index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<OutlinedButton
|
||||
type="link"
|
||||
onClick={addMba}
|
||||
>
|
||||
{i18nText('addNew', locale)}
|
||||
</OutlinedButton>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,166 @@
|
|||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { CustomInput } from '../../view/CustomInput';
|
||||
import { LinkButton } from '../../view/LinkButton';
|
||||
import { OutlinedButton } from '../../view/OutlinedButton';
|
||||
import { Details } from '../../../types/education';
|
||||
import { i18nText } from '../../../i18nKeys';
|
||||
import { Upload, UploadFile } from 'antd';
|
||||
import { validateDoc } from '../../../utils/account';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
import { AUTH_TOKEN_KEY } from '../../../constants/common';
|
||||
|
||||
type TrainingsContentProps = {
|
||||
trainings?: Details[];
|
||||
update: (tr?: Details[]) => void;
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export const TrainingsContent = ({
|
||||
trainings,
|
||||
update,
|
||||
locale
|
||||
}: TrainingsContentProps) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
|
||||
const addTrainings = () => {
|
||||
const training = {
|
||||
title: undefined,
|
||||
description: undefined,
|
||||
document: null
|
||||
};
|
||||
|
||||
update(trainings?.length > 0
|
||||
? [
|
||||
...trainings,
|
||||
training
|
||||
]
|
||||
: [training]);
|
||||
};
|
||||
|
||||
const deleteTrainings = (index: number) => {
|
||||
update([...trainings].filter((tr, i) => i !== index));
|
||||
};
|
||||
|
||||
const beforeUpload = (file: UploadFile) => {
|
||||
const isValid = validateDoc(file);
|
||||
|
||||
if (!isValid) {
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const onRemoveFile = (index: number) => {
|
||||
update(trainings?.map((tr, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...tr,
|
||||
document: null,
|
||||
}
|
||||
}
|
||||
|
||||
return tr;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChange = (file: any, index: number) => {
|
||||
if (file?.response) {
|
||||
update([...trainings].map((tr, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...tr,
|
||||
document: file?.response || null,
|
||||
}
|
||||
}
|
||||
|
||||
return tr;
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeName = (val: string, index: number) => {
|
||||
update(trainings?.map((tr, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...tr,
|
||||
title: val,
|
||||
}
|
||||
}
|
||||
|
||||
return tr;
|
||||
}));
|
||||
};
|
||||
|
||||
const onChangeDesc = (val: string, index: number) => {
|
||||
update(trainings?.map((tr, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...tr,
|
||||
description: val,
|
||||
}
|
||||
}
|
||||
|
||||
return tr;
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="b-edu-content">
|
||||
<div className="b-edu-list">
|
||||
{trainings?.map(({ title, description, document: file}, index) => (
|
||||
<div className="b-edu-list__item" key={`training_${index}`}>
|
||||
<div>
|
||||
<CustomInput
|
||||
value={title}
|
||||
placeholder={i18nText('name', locale)}
|
||||
onChange={(e) => onChangeName(e?.target?.value, index)}
|
||||
/>
|
||||
<CustomInput
|
||||
value={description}
|
||||
placeholder={i18nText('description', locale)}
|
||||
onChange={(e) => onChangeDesc(e?.target?.value, index)}
|
||||
/>
|
||||
<Upload
|
||||
fileList={file ? [
|
||||
{
|
||||
uid: file.original?.id,
|
||||
name: file.fileName,
|
||||
status: 'done',
|
||||
url: file.original?.url
|
||||
}
|
||||
] : undefined}
|
||||
accept=".jpg,.jpeg,.png,.pdf"
|
||||
beforeUpload={beforeUpload}
|
||||
multiple={false}
|
||||
onRemove={() => onRemoveFile(index)}
|
||||
action="https://api.bbuddy.expert/api/home/uploadfile"
|
||||
method="POST"
|
||||
headers={{
|
||||
authorization: `Bearer ${jwt}`,
|
||||
'X-User-Language': locale,
|
||||
'X-Referrer-Channel': 'site',
|
||||
}}
|
||||
onChange={(obj) => onChange(obj.file, index)}
|
||||
>
|
||||
<LinkButton type="link">{i18nText('addDiploma', locale)}</LinkButton>
|
||||
</Upload>
|
||||
</div>
|
||||
<LinkButton
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => deleteTrainings(index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<OutlinedButton
|
||||
type="link"
|
||||
onClick={addTrainings}
|
||||
>
|
||||
{i18nText('addNew', locale)}
|
||||
</OutlinedButton>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -12,3 +12,9 @@ export const FilledYellowButton = (props: any) => (
|
|||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
|
||||
export const FilledSquareButton = (props: any) => (
|
||||
<Button className="b-button__filled_square" {...props}>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { Button } from 'antd';
|
||||
|
||||
export const LinkButton = (props: any) => (
|
||||
<Button className="b-button__link" {...props}>
|
||||
<Button className={`b-button__link${props?.danger ? ' danger': ''}`} {...props}>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
|
|
|
@ -105,6 +105,7 @@ export default {
|
|||
signUp: 'Jetzt anmelden',
|
||||
noData: 'Keine Daten',
|
||||
notFound: 'Nicht gefunden',
|
||||
skillsInfo: 'Fähigkeiten-Infos',
|
||||
trainings: 'Trainings',
|
||||
seminars: 'Seminare',
|
||||
courses: 'Kurse',
|
||||
|
@ -112,7 +113,38 @@ export default {
|
|||
aboutCoach: 'Über Coach',
|
||||
education: 'Bildung',
|
||||
coaching: 'Coaching',
|
||||
|
||||
experiences: 'Praktische Erfahrung',
|
||||
payInfo: 'Zahlungsdaten',
|
||||
sessionDuration: 'Sitzungsdauer',
|
||||
experienceHours: 'Gesamtstunden praktischer Erfahrung',
|
||||
topics: 'Themen',
|
||||
selectTopic: 'Thema auswählen',
|
||||
title: 'Titel',
|
||||
description: 'Beschreibung',
|
||||
sessionCost: 'Sitzungskosten in Euro',
|
||||
yourTimezone: 'Deine Zeitzone',
|
||||
workTime: 'Arbeitszeit',
|
||||
startAt: 'Beginn um',
|
||||
finishAt: 'Ende um',
|
||||
addWorkingHours: 'Arbeitszeiten hinzufügen',
|
||||
specialisation: 'Spezialisierung',
|
||||
selectSpecialisation: 'Wählen Sie Ihre Spezialisierung, um fortzufahren',
|
||||
fillWeeklySchedule: 'Trage Sachen in deinen Wochenplan ein',
|
||||
beneficiaryName: 'Name des Empfängers',
|
||||
bicOrSwift: 'BIC/Swift-Code',
|
||||
association: 'Verband',
|
||||
level: 'Stufe',
|
||||
addDiploma: 'Zertifikat hinzufügen',
|
||||
university: 'Institution',
|
||||
sunday: 'So',
|
||||
monday: 'Mo',
|
||||
tuesday: 'Di',
|
||||
wednesday: 'Mi',
|
||||
thursday: 'Do',
|
||||
friday: 'Fr',
|
||||
saturday: 'Sa',
|
||||
addNew: 'Neu hinzufügen',
|
||||
mExperiences: 'Führungserfahrung',
|
||||
errors: {
|
||||
invalidEmail: 'Die E-Mail-Adresse ist ungültig',
|
||||
emptyEmail: 'Bitte geben Sie Ihre E-Mail ein',
|
||||
|
|
|
@ -110,8 +110,41 @@ export default {
|
|||
courses: 'Courses',
|
||||
mba: 'MBA Information',
|
||||
aboutCoach: 'About Coach',
|
||||
skillsInfo: 'Skills Info',
|
||||
education: 'Education',
|
||||
coaching: 'Coaching',
|
||||
experiences: 'Practical experience',
|
||||
payInfo: 'Payment Info',
|
||||
sessionDuration: 'Session duration',
|
||||
experienceHours: 'Total hours of practical experience',
|
||||
topics: 'Topics',
|
||||
selectTopic: 'Select Topic',
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
sessionCost: 'Session cost in euro',
|
||||
yourTimezone: 'Your timezone',
|
||||
workTime: 'Work time',
|
||||
startAt: 'Start at',
|
||||
finishAt: 'Finish at',
|
||||
addWorkingHours: 'Add working hours',
|
||||
specialisation: 'Specialisation',
|
||||
selectSpecialisation: 'Select your specialisation to proceed',
|
||||
fillWeeklySchedule: 'Fill up your weekly schedule',
|
||||
beneficiaryName: 'Beneficiary Name',
|
||||
bicOrSwift: 'BIC/Swift code',
|
||||
association: 'Association',
|
||||
level: 'Level',
|
||||
addDiploma: 'Add Diploma',
|
||||
university: 'Institution',
|
||||
sunday: 'Su',
|
||||
monday: 'Mo',
|
||||
tuesday: 'Tu',
|
||||
wednesday: 'We',
|
||||
thursday: 'Th',
|
||||
friday: 'Fr',
|
||||
saturday: 'Sa',
|
||||
addNew: 'Add New',
|
||||
mExperiences: 'Managerial Experience',
|
||||
errors: {
|
||||
invalidEmail: 'The email address is not valid',
|
||||
emptyEmail: 'Please enter your E-mail',
|
||||
|
|
|
@ -105,6 +105,7 @@ export default {
|
|||
signUp: 'Regístrate ahora',
|
||||
noData: 'Sin datos',
|
||||
notFound: 'No encontrado',
|
||||
skillsInfo: 'Información',
|
||||
trainings: 'Formación',
|
||||
seminars: 'Seminarios',
|
||||
courses: 'Cursos',
|
||||
|
@ -112,7 +113,38 @@ export default {
|
|||
aboutCoach: 'Sobre el coach',
|
||||
education: 'Educación',
|
||||
coaching: 'Coaching',
|
||||
|
||||
experiences: 'Experiencia práctica',
|
||||
payInfo: 'Información de pago',
|
||||
sessionDuration: 'Duración de la sesión',
|
||||
experienceHours: 'Total de horas de experiencia práctica',
|
||||
topics: 'Temas',
|
||||
selectTopic: 'Seleccione el tema',
|
||||
title: 'Título',
|
||||
description: 'Descripción',
|
||||
sessionCost: 'Coste de la sesión en euros',
|
||||
yourTimezone: 'Tu zona horaria',
|
||||
workTime: 'Tiempo de trabajo',
|
||||
startAt: 'Empieza a las',
|
||||
finishAt: 'Termina a las',
|
||||
addWorkingHours: 'Añadir horas de trabajo',
|
||||
specialisation: 'Especialización',
|
||||
selectSpecialisation: 'Selecciona tu especialización para continuar',
|
||||
fillWeeklySchedule: 'Rellena tu agenda semanal',
|
||||
beneficiaryName: 'Nombre del beneficiario',
|
||||
bicOrSwift: 'Código BIC/Swift',
|
||||
association: 'Asociación',
|
||||
level: 'Nivel',
|
||||
addDiploma: 'Añadir diploma',
|
||||
university: 'Institución',
|
||||
sunday: 'D',
|
||||
monday: 'L',
|
||||
tuesday: 'M',
|
||||
wednesday: 'X',
|
||||
thursday: 'J',
|
||||
friday: 'V',
|
||||
saturday: 'S',
|
||||
addNew: 'Añadir nuevo',
|
||||
mExperiences: 'Experiencia de dirección',
|
||||
errors: {
|
||||
invalidEmail: 'La dirección de correo electrónico no es válida',
|
||||
emptyEmail: 'Introduce tu correo electrónico',
|
||||
|
|
|
@ -105,6 +105,7 @@ export default {
|
|||
signUp: 'Inscrivez-vous maintenant',
|
||||
noData: 'Aucune donnée',
|
||||
notFound: 'Non trouvé',
|
||||
skillsInfo: 'Infos sur les compétences',
|
||||
trainings: 'Formations',
|
||||
seminars: 'Séminaires',
|
||||
courses: 'Cours',
|
||||
|
@ -112,7 +113,38 @@ export default {
|
|||
aboutCoach: 'À propos du coach',
|
||||
education: 'Éducation',
|
||||
coaching: 'Coaching',
|
||||
|
||||
experiences: 'Expérience pratique',
|
||||
payInfo: 'Infos sur le paiement',
|
||||
sessionDuration: 'Durée de la session',
|
||||
experienceHours: 'Heures totales d\'expérience pratique',
|
||||
topics: 'Sujets',
|
||||
selectTopic: 'Sélectionnez un sujet',
|
||||
title: 'Titre',
|
||||
description: 'Description',
|
||||
sessionCost: 'Coût de la session en euros',
|
||||
yourTimezone: 'Votre fuseau horaire',
|
||||
workTime: 'Heures de travail',
|
||||
startAt: 'Commencer à',
|
||||
finishAt: 'Finir à',
|
||||
addWorkingHours: 'Ajouter des heures de travail',
|
||||
specialisation: 'Spécialisation',
|
||||
selectSpecialisation: 'Sélectionnez votre spécialisation pour continuer',
|
||||
fillWeeklySchedule: 'Remplissez votre emploi du temps hebdomadaire',
|
||||
beneficiaryName: 'Nom du bénéficiaire',
|
||||
bicOrSwift: 'Code BIC/Swift',
|
||||
association: 'Association',
|
||||
level: 'Niveau',
|
||||
addDiploma: 'Ajouter un diplôme',
|
||||
university: 'Institution',
|
||||
sunday: 'Di',
|
||||
monday: 'Lu',
|
||||
tuesday: 'Ma',
|
||||
wednesday: 'Me',
|
||||
thursday: 'Je',
|
||||
friday: 'Ve',
|
||||
saturday: 'Sa',
|
||||
addNew: 'Ajouter un nouveau',
|
||||
mExperiences: 'Expérience en gestion',
|
||||
errors: {
|
||||
invalidEmail: 'L\'adresse e-mail n\'est pas valide',
|
||||
emptyEmail: 'Veuillez saisir votre e-mail',
|
||||
|
|
|
@ -105,6 +105,7 @@ export default {
|
|||
signUp: 'Iscriviti ora',
|
||||
noData: 'Nessun dato',
|
||||
notFound: 'Non trovato',
|
||||
skillsInfo: 'Info su competenze',
|
||||
trainings: 'Training',
|
||||
seminars: 'Seminari',
|
||||
courses: 'Corsi',
|
||||
|
@ -112,7 +113,38 @@ export default {
|
|||
aboutCoach: 'Informazioni sul coach',
|
||||
education: 'Istruzione',
|
||||
coaching: 'Coaching',
|
||||
|
||||
experiences: 'Esperienza pratica',
|
||||
payInfo: 'Info pagamento',
|
||||
sessionDuration: 'Durata della sessione',
|
||||
experienceHours: 'Totale ore di esperienza pratica',
|
||||
topics: 'Argomenti',
|
||||
selectTopic: 'Seleziona l\'argomento',
|
||||
title: 'Titolo',
|
||||
description: 'Descrizione',
|
||||
sessionCost: 'Costo della sessione in euro',
|
||||
yourTimezone: 'Il tuo fuso orario',
|
||||
workTime: 'Orario di lavoro',
|
||||
startAt: 'Inizia alle',
|
||||
finishAt: 'Termina alle',
|
||||
addWorkingHours: 'Aggiungi ore lavorative',
|
||||
specialisation: 'Specializzazione',
|
||||
selectSpecialisation: 'Seleziona la tua specializzazione per continuare',
|
||||
fillWeeklySchedule: 'Compila la tua agenda settimanale',
|
||||
beneficiaryName: 'Nome del beneficiario',
|
||||
bicOrSwift: 'BIC/codice Swift',
|
||||
association: 'Associazione',
|
||||
level: 'Livello',
|
||||
addDiploma: 'Aggiungi diploma',
|
||||
university: 'Istituto',
|
||||
sunday: 'Do',
|
||||
monday: 'Lu',
|
||||
tuesday: 'Ma',
|
||||
wednesday: 'Me',
|
||||
thursday: 'Gi',
|
||||
friday: 'Ve',
|
||||
saturday: 'Sa',
|
||||
addNew: 'Aggiungi nuovo',
|
||||
mExperiences: 'Esperienza manageriale',
|
||||
errors: {
|
||||
invalidEmail: 'L\'indirizzo e-mail non è valido',
|
||||
emptyEmail: 'Inserisci l\'e-mail',
|
||||
|
|
|
@ -98,21 +98,53 @@ export default {
|
|||
expertBackground: 'Профессиональный опыт эксперта',
|
||||
profCertification: 'Профессиональная сертификация',
|
||||
practiceHours: 'Часов практики',
|
||||
supervisionCount: 'Часов супервизии в год',
|
||||
supervisionCount: 'Супервизий в год',
|
||||
outOf: 'из',
|
||||
schedule: 'Расписание',
|
||||
successfulCase: 'Успешные случаи из практики',
|
||||
signUp: 'Записаться сейчас',
|
||||
noData: 'Нет данных',
|
||||
notFound: 'Не найдено',
|
||||
skillsInfo: 'Навыки',
|
||||
trainings: 'Тренинги',
|
||||
seminars: 'Семинары',
|
||||
courses: 'Курсы',
|
||||
mba: 'Информация о MBA',
|
||||
experiences: 'Практический опыт',
|
||||
aboutCoach: 'О коуче',
|
||||
education: 'Образование',
|
||||
coaching: 'Коучинг',
|
||||
|
||||
payInfo: 'Платежная информация',
|
||||
sessionDuration: 'Продолжительность сессии',
|
||||
experienceHours: 'Общее количество часов практического опыта',
|
||||
topics: 'Темы',
|
||||
selectTopic: 'Выберите тему',
|
||||
title: 'Название',
|
||||
description: 'Описание',
|
||||
sessionCost: 'Стоимость сессии в евро',
|
||||
yourTimezone: 'Ваш часовой пояс',
|
||||
workTime: 'Рабочее время',
|
||||
startAt: 'Начало в',
|
||||
finishAt: 'Завершение в',
|
||||
addWorkingHours: 'Добавить рабочие часы',
|
||||
specialisation: 'Специализация',
|
||||
selectSpecialisation: 'Выберите свою специализацию для продолжения',
|
||||
fillWeeklySchedule: 'Заполните свое недельное расписание',
|
||||
beneficiaryName: 'Имя получателя',
|
||||
bicOrSwift: 'BIC/Swift код',
|
||||
association: 'Ассоциация',
|
||||
level: 'Уровень',
|
||||
addDiploma: 'Добавить диплом',
|
||||
university: 'ВУЗ',
|
||||
sunday: 'Вс',
|
||||
monday: 'Пн',
|
||||
tuesday: 'Вт',
|
||||
wednesday: 'Ср',
|
||||
thursday: 'Чт',
|
||||
friday: 'Пт',
|
||||
saturday: 'Сб',
|
||||
addNew: 'Добавить',
|
||||
mExperiences: 'Управленческий опыт',
|
||||
errors: {
|
||||
invalidEmail: 'Адрес электронной почты недействителен',
|
||||
emptyEmail: 'Пожалуйста, введите ваш E-mail',
|
||||
|
|
|
@ -77,7 +77,7 @@ export async function fetchBlogPosts({ preview, category, page, sticky }: FetchB
|
|||
const contentful = contentfulClient({ preview })
|
||||
const query = {
|
||||
content_type: 'blogPost',
|
||||
select: ['fields.title', 'fields.excerpt', 'fields.author', 'fields.listImage', 'fields.author', 'fields.category', 'sys.createdAt', 'fields.slug'],
|
||||
select: ['fields.title', 'fields.excerpt', 'fields.author', 'fields.listImage', 'fields.author', 'fields.category', 'sys.createdAt', 'fields.slug', 'fields.metaDescription'],
|
||||
order: ['sys.createdAt'],
|
||||
}
|
||||
if (category){
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
.b-edu {
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
|
||||
&__item {
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #C4DFE6;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,7 +51,8 @@
|
|||
}
|
||||
|
||||
&__inner {
|
||||
height: 60vh;
|
||||
height: auto;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
& > div {
|
||||
|
@ -86,3 +87,13 @@
|
|||
.ant-modal-mask {
|
||||
background-color: rgba(0, 59, 70, 0.4) !important;
|
||||
}
|
||||
|
||||
.ant-upload-list-item-name {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.ant-upload-list-item {
|
||||
&:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1188,7 +1188,6 @@
|
|||
height: 86px;
|
||||
border-radius: 16px;
|
||||
border: 2px solid #FFF;
|
||||
background: lightgray 50%;
|
||||
box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32);
|
||||
overflow: hidden;
|
||||
|
||||
|
@ -1210,6 +1209,7 @@
|
|||
@include rem(18);
|
||||
font-weight: 600;
|
||||
line-height: 150%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1233,6 +1233,61 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.title-h3 {
|
||||
color: #003B46;
|
||||
@include rem(16);
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.case-list {
|
||||
margin-top: 8px;
|
||||
|
||||
p {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__practice {
|
||||
color: #2C7873;
|
||||
@include rem(16);
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
&__lang {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
& > div {
|
||||
color: #003B46;
|
||||
@include rem(16);
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
color: #003B46;
|
||||
@include rem(16);
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.title-h2 {
|
||||
color: #003B46;
|
||||
@include rem(18);
|
||||
|
@ -1243,7 +1298,7 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
&__desc, &__desc > div {
|
||||
border-radius: 16px;
|
||||
background: #EFFCFF;
|
||||
padding: 16px;
|
||||
|
@ -1443,8 +1498,17 @@
|
|||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pay-data-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
.b-schedule-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
color: #003B46;
|
||||
@include rem(16);
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 150%;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@
|
|||
@import "_message.scss";
|
||||
@import "_auth-modal.scss";
|
||||
@import "_modal.scss";
|
||||
@import "_edu.scss";
|
||||
@import "_schedule.scss";
|
||||
|
||||
@import "./view/style.scss";
|
||||
@import "./sessions/style.scss";
|
||||
|
|
|
@ -17,6 +17,19 @@
|
|||
padding: 4px 24px !important;
|
||||
}
|
||||
|
||||
&_square {
|
||||
width: 42px !important;
|
||||
height: 42px !important;
|
||||
background: #66A5AD !important;
|
||||
font-size: 15px !important;
|
||||
border-radius: 8px !important;
|
||||
box-shadow: 0px 2px 4px 0px rgba(102, 165, 173, 0.32) !important;
|
||||
position: absolute !important;
|
||||
right: -8px !important;
|
||||
bottom: -8px !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background: #D93E5C !important;
|
||||
box-shadow: none !important;
|
||||
|
@ -28,6 +41,10 @@
|
|||
font-size: 15px !important;
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
|
||||
&.danger {
|
||||
color: #D93E5C !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__outlined {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
.ant-collapse-header {
|
||||
padding: 12px 0 !important;
|
||||
}
|
||||
|
||||
.ant-collapse-header-text {
|
||||
color: #003B46;
|
||||
@include rem(16);
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 133.333%;
|
||||
}
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 12px 0 !important;
|
||||
}
|
|
@ -8,6 +8,14 @@
|
|||
|
||||
input {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.ant-input-group-addon {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&:focus, &:hover, &:focus-within {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
.b-practice {
|
||||
&-cases {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&-case {
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,3 +6,5 @@
|
|||
@import "_spin.scss";
|
||||
@import "_switch.scss";
|
||||
@import "_buttons.scss";
|
||||
@import "_practice.scss";
|
||||
@import "_collapse.scss";
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface BlogPostFields {
|
|||
title?: EntryFieldTypes.Symbol
|
||||
slug: EntryFieldTypes.Symbol
|
||||
excerpt: EntryFieldTypes.Symbol
|
||||
metaDescription: EntryFieldTypes.Symbol
|
||||
listImage?: EntryFieldTypes.AssetLink
|
||||
author?: AuthorSkeleton
|
||||
category: BlogPostCategorySkeleton
|
||||
|
@ -28,6 +29,7 @@ export interface BlogPost {
|
|||
author: Author | null
|
||||
category: string
|
||||
createdAt: string
|
||||
metaDescription: string
|
||||
body: Array<WidgetMedia | WidgetParagraph>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import { ExpertDocument } from './file';
|
||||
|
||||
export type Details = {
|
||||
id: number;
|
||||
id?: number;
|
||||
userId?: number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
document?: ExpertDocument;
|
||||
document?: ExpertDocument | null;
|
||||
};
|
||||
|
||||
export type Certificate = {
|
||||
id: number;
|
||||
id?: number;
|
||||
userId?: number;
|
||||
associationLevelId?: number;
|
||||
document?: ExpertDocument;
|
||||
associationId?: number;
|
||||
document?: ExpertDocument | null;
|
||||
};
|
||||
|
||||
export type Experience = {
|
||||
id: number,
|
||||
id?: number,
|
||||
userId?: number,
|
||||
title?: string,
|
||||
description?: string
|
||||
|
@ -24,10 +25,10 @@ export type Experience = {
|
|||
|
||||
export type Association = {
|
||||
id: number;
|
||||
name?: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type AssociationLevel = Association & { associationId?: number };
|
||||
export type AssociationLevel = Association & { associationId: number };
|
||||
|
||||
export type EducationData = {
|
||||
certificates?: Certificate[],
|
||||
|
|
|
@ -6,7 +6,7 @@ export type Supervision = {
|
|||
};
|
||||
|
||||
export type PracticeCase = {
|
||||
id: number,
|
||||
id?: number,
|
||||
userId?: number,
|
||||
description?: string,
|
||||
themesGroupIds?: number[]
|
||||
|
@ -18,12 +18,14 @@ export type PracticeData = {
|
|||
sessionDuration?: number,
|
||||
sessionCost?: number,
|
||||
practiceCases?: PracticeCase[]
|
||||
}
|
||||
};
|
||||
|
||||
export interface PracticeDTO {
|
||||
person4Data: PracticeData & {
|
||||
export type PracticePersonData = PracticeData & {
|
||||
themesGroups?: ExpertsThemesGroups[],
|
||||
supervisionPerYears?: Supervision[],
|
||||
sessionCosts?: number[]
|
||||
}
|
||||
};
|
||||
|
||||
export interface PracticeDTO {
|
||||
person4Data: PracticePersonData
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@ export type ProfileData = {
|
|||
hasExternalLogin?: boolean;
|
||||
isTestMode?: boolean;
|
||||
phone?: string;
|
||||
languagesLinks?: { language: { id: number, code: string, nativeSpelling: string }, languageId: number }[]
|
||||
languagesLinks?: { language: { id: number, code: string, nativeSpelling: string }, languageId: number }[];
|
||||
allLanguages?: { id: number, code: string, nativeSpelling: string }[]
|
||||
}
|
||||
|
||||
export type Profile = ProfileData & { id: number };
|
||||
|
|
|
@ -28,3 +28,19 @@ export const validateImage = (file: UploadFile, showMessage?: boolean): boolean
|
|||
|
||||
return isImage && isLt5M;
|
||||
};
|
||||
|
||||
|
||||
export const validateDoc = (file: UploadFile): boolean => {
|
||||
const isDoc = file.type === 'image/jpg' || file.type === 'image/jpeg'
|
||||
|| file.type === 'image/png' || file.type === 'image/gif' || file.type === 'application/pdf';
|
||||
if (!isDoc) {
|
||||
message.error('You can only upload JPG/PNG/PDF file');
|
||||
}
|
||||
|
||||
const isLt5M = file.size / 1024 / 1024 <= 5;
|
||||
if (!isLt5M) {
|
||||
message.error('Image must smaller than 5MB');
|
||||
}
|
||||
|
||||
return isDoc && isLt5M;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import dayjs from 'dayjs';
|
||||
import { WorkingTime } from '../types/schedule';
|
||||
|
||||
const WEEK_DAY = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
||||
const MAX_DAY_TIME = 24 * 60; // min
|
||||
|
||||
export const getCurrentTime = (data: WorkingTime) => {
|
||||
let startDay = data.startDayOfWeekUtc;
|
||||
let endDay = data.endDayOfWeekUtc;
|
||||
const currentTimeZone = dayjs().format('Z');
|
||||
const startUtc = data.startTimeUtc / (1000 * 1000 * 60);
|
||||
const endUtc = data.endTimeUtc / (1000 * 1000 * 60);
|
||||
const matches = currentTimeZone.match(/(\+|-)([0-9]{2}):([0-9]{2})/);
|
||||
const sign = matches[1];
|
||||
const h = matches[2];
|
||||
const m = matches[3];
|
||||
let startMin;
|
||||
let endMin;
|
||||
|
||||
if (sign === '+') {
|
||||
startMin = startUtc + (Number(h) * 60) + Number(m);
|
||||
endMin = endUtc + (Number(h) * 60) + Number(m);
|
||||
// startMin = startUtc;
|
||||
// endMin = endUtc;
|
||||
}
|
||||
|
||||
if (sign === '-') {
|
||||
startMin = startUtc - (Number(h) * 60) - Number(m);
|
||||
endMin = endUtc - (Number(h) * 60) - Number(m);
|
||||
}
|
||||
|
||||
if (startMin > MAX_DAY_TIME) {
|
||||
startMin = startMin - MAX_DAY_TIME;
|
||||
const ind = startDay ? WEEK_DAY.indexOf(startDay) + 1 : 0;
|
||||
startDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind];
|
||||
}
|
||||
|
||||
if (endMin > MAX_DAY_TIME) {
|
||||
endMin = endMin - MAX_DAY_TIME;
|
||||
const ind = endDay ? WEEK_DAY.indexOf(endDay) + 1 : 0;
|
||||
endDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind];
|
||||
}
|
||||
|
||||
if (startMin <= 0) {
|
||||
startMin = MAX_DAY_TIME - startMin;
|
||||
const ind = startDay ? WEEK_DAY.indexOf(startDay) - 1 : 0;
|
||||
startDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind];
|
||||
}
|
||||
|
||||
if (endMin <= 0) {
|
||||
endMin = MAX_DAY_TIME - endMin;
|
||||
const ind = endDay ? WEEK_DAY.indexOf(endDay) - 1 : 0;
|
||||
endDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind];
|
||||
}
|
||||
|
||||
return {
|
||||
startDay,
|
||||
startTime: `${(startMin - startMin % 60)/60}:${startMin % 60 || '00'}`,
|
||||
endDay: endDay,
|
||||
endTime: `${(endMin - endMin % 60)/60}:${endMin % 60 || '00'}`
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue