Merge branch 'refs/heads/develop' into blog
This commit is contained in:
commit
80f53e871d
File diff suppressed because it is too large
Load Diff
|
@ -6,7 +6,15 @@ import { message } from 'antd';
|
||||||
import { ExpertData } from '../../../../../types/profile';
|
import { ExpertData } from '../../../../../types/profile';
|
||||||
import { AUTH_TOKEN_KEY } from '../../../../../constants/common';
|
import { AUTH_TOKEN_KEY } from '../../../../../constants/common';
|
||||||
import { useLocalStorage } from '../../../../../hooks/useLocalStorage';
|
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 { ExpertProfile } from '../../../../../components/ExpertProfile';
|
||||||
import { Loader } from '../../../../../components/view/Loader';
|
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 [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [data, setData] = useState<ExpertData | undefined>();
|
const [data, setData] = useState<ExpertData | undefined>();
|
||||||
|
const [isFull, setIsFull] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (jwt) {
|
if (jwt) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
Promise.all([
|
Promise.all([
|
||||||
|
getUserData(locale, jwt),
|
||||||
getPersonalData(locale, jwt),
|
getPersonalData(locale, jwt),
|
||||||
getEducation(locale, jwt),
|
getEducation(locale, jwt),
|
||||||
getTags(locale, jwt),
|
getTags(locale, jwt),
|
||||||
|
@ -27,13 +37,12 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo
|
||||||
getSchedule(locale, jwt),
|
getSchedule(locale, jwt),
|
||||||
getPayData(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('person', person);
|
||||||
console.log('education', education);
|
console.log('education', education);
|
||||||
console.log('tags', tags);
|
|
||||||
console.log('practice', practice);
|
|
||||||
console.log('schedule', schedule);
|
console.log('schedule', schedule);
|
||||||
console.log('payData', payData);
|
setIsFull(profile.fillProgress === 'full');
|
||||||
setData({
|
setData({
|
||||||
person,
|
person,
|
||||||
education,
|
education,
|
||||||
|
@ -56,6 +65,7 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo
|
||||||
<Loader isLoading={loading}>
|
<Loader isLoading={loading}>
|
||||||
{data && (
|
{data && (
|
||||||
<ExpertProfile
|
<ExpertProfile
|
||||||
|
isFull={isFull}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
data={data}
|
data={data}
|
||||||
updateData={setData}
|
updateData={setData}
|
||||||
|
|
|
@ -112,7 +112,11 @@ export default async function ExpertItem({ params: { expertId = '', locale } }:
|
||||||
{expert?.publicCoachDetails?.trainings && expert.publicCoachDetails.trainings?.map(generateDescription)}
|
{expert?.publicCoachDetails?.trainings && expert.publicCoachDetails.trainings?.map(generateDescription)}
|
||||||
{expert?.publicCoachDetails?.mbas && expert.publicCoachDetails.mbas?.map(generateDescription)}
|
{expert?.publicCoachDetails?.mbas && expert.publicCoachDetails.mbas?.map(generateDescription)}
|
||||||
{expert?.publicCoachDetails?.experiences && expert.publicCoachDetails.experiences?.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>
|
{/* <h2 className="title-h2">All Offers by this Expert</h2>
|
||||||
<div className="offers-list">
|
<div className="offers-list">
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { validateImage } from '../../utils/account';
|
||||||
import { useProfileSettings } from '../../actions/hooks/useProfileSettings';
|
import { useProfileSettings } from '../../actions/hooks/useProfileSettings';
|
||||||
import { CustomInput } from '../view/CustomInput';
|
import { CustomInput } from '../view/CustomInput';
|
||||||
import { OutlinedButton } from '../view/OutlinedButton';
|
import { OutlinedButton } from '../view/OutlinedButton';
|
||||||
import { FilledYellowButton } from '../view/FilledButton';
|
import {FilledButton, FilledSquareButton, FilledYellowButton} from '../view/FilledButton';
|
||||||
import { DeleteAccountModal } from '../Modals/DeleteAccountModal';
|
import { DeleteAccountModal } from '../Modals/DeleteAccountModal';
|
||||||
import { Loader } from '../view/Loader';
|
import { Loader } from '../view/Loader';
|
||||||
|
|
||||||
|
@ -55,14 +55,14 @@ export const ProfileSettings: FC<ProfileSettingsProps> = ({ locale }) => {
|
||||||
languagesLinks: languagesLinks?.map(({ languageId }) => ({ languageId })) || []
|
languagesLinks: languagesLinks?.map(({ languageId }) => ({ languageId })) || []
|
||||||
};
|
};
|
||||||
|
|
||||||
// if (photo) {
|
if (photo) {
|
||||||
// console.log(photo);
|
console.log(photo);
|
||||||
// const formData = new FormData();
|
const formData = new FormData();
|
||||||
// formData.append('file', photo as FileType);
|
formData.append('file', photo as FileType);
|
||||||
//
|
|
||||||
// newProfile.faceImage = photo;
|
newProfile.faceImage = `[${(photo as File).arrayBuffer()}]`;
|
||||||
// newProfile.isFaceImageKeepExisting = false;
|
newProfile.isFaceImageKeepExisting = false;
|
||||||
// }
|
}
|
||||||
|
|
||||||
console.log(newProfile);
|
console.log(newProfile);
|
||||||
|
|
||||||
|
@ -99,13 +99,6 @@ export const ProfileSettings: FC<ProfileSettingsProps> = ({ locale }) => {
|
||||||
return (
|
return (
|
||||||
<Loader isLoading={fetchLoading} refresh={fetchProfileSettings}>
|
<Loader isLoading={fetchLoading} refresh={fetchProfileSettings}>
|
||||||
<Form form={form} className="form-settings">
|
<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
|
<ImgCrop
|
||||||
modalTitle="Редактировать"
|
modalTitle="Редактировать"
|
||||||
modalOk="Сохранить"
|
modalOk="Сохранить"
|
||||||
|
@ -126,8 +119,17 @@ export const ProfileSettings: FC<ProfileSettingsProps> = ({ locale }) => {
|
||||||
multiple={false}
|
multiple={false}
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
>
|
>
|
||||||
{photo && <img height={100} width={100} src={URL.createObjectURL(photo)} />}
|
<div className="user-avatar">
|
||||||
<Button icon={<CameraOutlined />}>Click to Upload</Button>
|
<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>
|
</Upload>
|
||||||
</ImgCrop>
|
</ImgCrop>
|
||||||
<div className="form-fieldset">
|
<div className="form-fieldset">
|
||||||
|
|
|
@ -1,43 +1,96 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { message } from 'antd';
|
import {Alert, message} from 'antd';
|
||||||
import { EditOutlined } from '@ant-design/icons';
|
import Image from 'next/image';
|
||||||
import { i18nText } from '../../i18nKeys';
|
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 { AUTH_TOKEN_KEY } from '../../constants/common';
|
||||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
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 { Loader } from '../view/Loader';
|
||||||
import { LinkButton } from '../view/LinkButton';
|
|
||||||
import { ExpertTags } from './content/ExpertTags';
|
import { ExpertTags } from './content/ExpertTags';
|
||||||
import { ExpertSchedule } from './content/ExpertSchedule';
|
import { ExpertSchedule } from './content/ExpertSchedule';
|
||||||
import { ExpertPayData } from './content/ExpertPayData';
|
import { ExpertPayData } from './content/ExpertPayData';
|
||||||
import { ExpertEducation } from './content/ExpertEducation';
|
import { ExpertEducation } from './content/ExpertEducation';
|
||||||
|
import { ExpertAbout } from './content/ExpertAbout';
|
||||||
|
|
||||||
type ExpertProfileProps = {
|
type ExpertProfileProps = {
|
||||||
locale: string;
|
locale: string;
|
||||||
data: ExpertData;
|
data: ExpertData;
|
||||||
updateData: (data: ExpertData) => void;
|
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 [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||||
const [loading, setLoading] = useState<(keyof ExpertData)[]>([]);
|
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) => {
|
const updateExpert = (key: keyof ExpertData) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'tags':
|
case 'tags':
|
||||||
setLoading([key]);
|
getNewPartData<ExpertsTags>({
|
||||||
getTags(locale, jwt)
|
key,
|
||||||
.then((tags) => {
|
getNewData: getTags,
|
||||||
updateData({
|
errorMessage: 'Не удалось получить направления'
|
||||||
...data,
|
});
|
||||||
tags
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -52,36 +105,39 @@ export const ExpertProfile = ({ locale, data, updateData }: ExpertProfileProps)
|
||||||
<div className="coaching-info">
|
<div className="coaching-info">
|
||||||
<div className="coaching-profile">
|
<div className="coaching-profile">
|
||||||
<div className="coaching-profile__portrait">
|
<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>
|
||||||
<div className="coaching-profile__inner">
|
<div className="coaching-profile__inner" style={{ flex: 1 }}>
|
||||||
<div className="coaching-profile__name">
|
<div className="coaching-profile__name">
|
||||||
David
|
{`${data?.person?.username} ${data?.person?.surname || ''}`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{!isFull && (
|
||||||
</div>
|
<Alert
|
||||||
<div className="coaching-section__wrap">
|
message="Проверьте заполненность блоков"
|
||||||
<div className="coaching-section">
|
description={(
|
||||||
<div className="coaching-section__title">
|
<ul className="b-rules-list">
|
||||||
<h2 className="title-h2">{i18nText('aboutCoach', locale)}</h2>
|
<li>о себе</li>
|
||||||
<h2 className="title-h2">person1 + person4</h2>
|
<li>темы сессии</li>
|
||||||
<LinkButton
|
<li>рабочее расписание</li>
|
||||||
type="link"
|
<li>информация об образовании</li>
|
||||||
icon={<EditOutlined />}
|
<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>
|
||||||
</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')}>
|
<Loader isLoading={loading.includes('tags')}>
|
||||||
<ExpertTags
|
<ExpertTags
|
||||||
locale={locale}
|
locale={locale}
|
||||||
|
@ -90,9 +146,21 @@ export const ExpertProfile = ({ locale, data, updateData }: ExpertProfileProps)
|
||||||
/>
|
/>
|
||||||
</Loader>
|
</Loader>
|
||||||
<ExpertSchedule locale={locale} data={data?.schedule} />
|
<ExpertSchedule locale={locale} data={data?.schedule} />
|
||||||
<ExpertEducation locale={locale} data={data?.education} />
|
<Loader isLoading={loading.includes('education')}>
|
||||||
<ExpertPayData locale={locale} data={data?.payData?.person6Data} />
|
<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>
|
</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 { EditOutlined } from '@ant-design/icons';
|
||||||
import { EducationDTO } from '../../../types/education';
|
import { EducationDTO } from '../../../types/education';
|
||||||
import { i18nText } from '../../../i18nKeys';
|
import { i18nText } from '../../../i18nKeys';
|
||||||
import { LinkButton } from '../../view/LinkButton';
|
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 = {
|
type ExpertEducationProps = {
|
||||||
locale: string;
|
locale: string;
|
||||||
data?: EducationDTO;
|
data?: EducationDTO;
|
||||||
|
updateExpert: (key: keyof ExpertData) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExpertEducation = ({ locale, data }: ExpertEducationProps) => {
|
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 '';
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="coaching-section__wrap">
|
<div className="coaching-section__wrap">
|
||||||
<div className="coaching-section">
|
<div className="coaching-section">
|
||||||
<div className="coaching-section__title">
|
<div className="coaching-section__title">
|
||||||
<h2 className="title-h2">{i18nText('education', locale)}</h2>
|
<h2 className="title-h2">{i18nText('skillsInfo', locale)}</h2>
|
||||||
<h2 className="title-h2">person2</h2>
|
|
||||||
<LinkButton
|
<LinkButton
|
||||||
type="link"
|
type="link"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => setShowEdit(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{data?.person2Data?.educations?.length > 0 && (
|
||||||
<div className="coaching-section__desc">
|
<div className="coaching-section__desc">
|
||||||
<h3 className="title-h3">Psychologist</h3>
|
{data?.person2Data?.educations?.map(({ id, title, description, document }) => (
|
||||||
<div className="base-text">
|
<div key={id}>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
<h3 className="title-h3">{title}</h3>
|
||||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
{description && <div className="base-text">{description}</div>}
|
||||||
</div>
|
{document && (
|
||||||
<div className="sertific">
|
<div className="sertific">
|
||||||
<img src="/images/sertific.png" className="" alt="" />
|
<ExpertCertificate document={document} />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{data?.person2Data?.certificates?.length > 0 && (
|
||||||
<div className="coaching-section">
|
<div className="coaching-section">
|
||||||
<h2 className="title-h2">{i18nText('profCertification', locale)}</h2>
|
<h2 className="title-h2">{i18nText('profCertification', locale)}</h2>
|
||||||
<div className="coaching-section__desc">
|
<div className="coaching-section__desc">
|
||||||
|
{data?.person2Data?.certificates?.map((cert) => (
|
||||||
|
<div key={cert.id}>
|
||||||
<div className="base-text">
|
<div className="base-text">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
{`${getAssociationLevel(cert?.associationLevelId)} ${getAssociation(cert?.associationLevelId)}`}
|
||||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
</div>
|
||||||
</div>
|
{cert.document && (
|
||||||
|
<div className="sertific">
|
||||||
|
<ExpertCertificate document={cert.document} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{data?.person2Data?.trainings?.length > 0 && (
|
||||||
<div className="coaching-section">
|
<div className="coaching-section">
|
||||||
<h2 className="title-h2">
|
<h2 className="title-h2">
|
||||||
{`${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`}
|
{`${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="coaching-section__desc">
|
<div className="coaching-section__desc">
|
||||||
<div className="base-text">
|
{data?.person2Data?.trainings?.map(({ id, title, description, document }) => (
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
<div key={id}>
|
||||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
<h3 className="title-h3">{title}</h3>
|
||||||
</div>
|
{description && <div className="base-text">{description}</div>}
|
||||||
|
{document && (
|
||||||
|
<div className="sertific">
|
||||||
|
<ExpertCertificate document={document} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{data?.person2Data?.mbas?.length > 0 && (
|
||||||
<div className="coaching-section">
|
<div className="coaching-section">
|
||||||
<h2 className="title-h2">{i18nText('mba', locale)}</h2>
|
<h2 className="title-h2">{i18nText('mba', locale)}</h2>
|
||||||
<div className="coaching-section__desc">
|
<div className="coaching-section__desc">
|
||||||
<div className="base-text">
|
{data?.person2Data?.mbas?.map(({ id, title, description, document }) => (
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
<div key={id}>
|
||||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
<h3 className="title-h3">{title}</h3>
|
||||||
|
{description && <div className="base-text">{description}</div>}
|
||||||
|
{document && (
|
||||||
|
<div className="sertific">
|
||||||
|
<ExpertCertificate document={document} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</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>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<EditExpertEducationModal
|
||||||
|
open={showEdit}
|
||||||
|
handleCancel={() => setShowEdit(false)}
|
||||||
|
locale={locale}
|
||||||
|
data={data}
|
||||||
|
refresh={() => updateExpert('education')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,28 +1,65 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
import { EditOutlined } from '@ant-design/icons';
|
import { EditOutlined } from '@ant-design/icons';
|
||||||
import { i18nText } from '../../../i18nKeys';
|
import { i18nText } from '../../../i18nKeys';
|
||||||
import { PayInfo } from '../../../types/profile';
|
import { ExpertData, PayInfo } from '../../../types/profile';
|
||||||
import { LinkButton } from '../../view/LinkButton';
|
import { LinkButton } from '../../view/LinkButton';
|
||||||
|
import { EditExpertPayDataModal } from '../../Modals/EditExpertPayDataModal';
|
||||||
|
|
||||||
type ExpertPayDataProps = {
|
type ExpertPayDataProps = {
|
||||||
locale: string;
|
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 (
|
return (
|
||||||
<div className="coaching-section__wrap">
|
<div className="coaching-section__wrap">
|
||||||
<div className="coaching-section">
|
<div className="coaching-section">
|
||||||
<div className="coaching-section__title">
|
<div className="coaching-section__title">
|
||||||
<h2 className="title-h2">Card data - person6</h2>
|
<h2 className="title-h2">{i18nText('payInfo', locale)}</h2>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
type="link"
|
type="link"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => setShowEdit(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="base-text">
|
<div className="base-text pay-data-list">
|
||||||
Card
|
{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>
|
||||||
</div>
|
</div>
|
||||||
|
<EditExpertPayDataModal
|
||||||
|
locale={locale}
|
||||||
|
open={showEdit}
|
||||||
|
data={data}
|
||||||
|
handleCancel={() => setShowEdit(false)}
|
||||||
|
refresh={() => updateExpert('payData')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { EditOutlined } from '@ant-design/icons';
|
import { EditOutlined } from '@ant-design/icons';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { ScheduleDTO } from '../../../types/schedule';
|
import { ScheduleDTO } from '../../../types/schedule';
|
||||||
import { i18nText } from '../../../i18nKeys';
|
import { i18nText } from '../../../i18nKeys';
|
||||||
import { LinkButton } from '../../view/LinkButton';
|
import { LinkButton } from '../../view/LinkButton';
|
||||||
|
import {useState} from "react";
|
||||||
|
import {Tag} from "antd";
|
||||||
|
import {getCurrentTime} from "../../../utils/time";
|
||||||
|
|
||||||
type ExpertScheduleProps = {
|
type ExpertScheduleProps = {
|
||||||
locale: string;
|
locale: string;
|
||||||
|
@ -9,18 +13,34 @@ type ExpertScheduleProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExpertSchedule = ({ locale, data }: ExpertScheduleProps) => {
|
export const ExpertSchedule = ({ locale, data }: ExpertScheduleProps) => {
|
||||||
|
const [showEdit, setShowEdit] = useState<boolean>(false);
|
||||||
|
// person51
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="coaching-section__wrap">
|
<div className="coaching-section__wrap">
|
||||||
<div className="coaching-section">
|
<div className="coaching-section">
|
||||||
<div className="coaching-section__title">
|
<div className="coaching-section__title">
|
||||||
<h2 className="title-h2">Schedule - person51</h2>
|
<h2 className="title-h2">{i18nText('schedule', locale)}</h2>
|
||||||
<LinkButton
|
{/*<LinkButton
|
||||||
type="link"
|
type="link"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
/>
|
onClick={() => setShowEdit(true)}
|
||||||
|
/>*/}
|
||||||
</div>
|
</div>
|
||||||
<div className="base-text">
|
<div className="b-schedule-list">
|
||||||
Schedule
|
{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const ExpertTags = ({ locale, data, updateExpert }: ExpertTagsProps) => {
|
||||||
<div className="coaching-section__wrap">
|
<div className="coaching-section__wrap">
|
||||||
<div className="coaching-section">
|
<div className="coaching-section">
|
||||||
<div className="coaching-section__title">
|
<div className="coaching-section__title">
|
||||||
<h2 className="title-h2">{i18nText('direction', locale)}</h2>
|
<h2 className="title-h2">{i18nText('topics', locale)}</h2>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
type="link"
|
type="link"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
|
|
|
@ -4,7 +4,8 @@ import React, { FC } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { Tag, Image as AntdImage, Space } from 'antd';
|
import { Tag, Image as AntdImage, Space } from 'antd';
|
||||||
import { ZoomInOutlined, ZoomOutOutlined, StarFilled } from '@ant-design/icons';
|
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 { Locale } from '../../types/locale';
|
||||||
import { CustomRate } from '../view/CustomRate';
|
import { CustomRate } from '../view/CustomRate';
|
||||||
import { i18nText } from '../../i18nKeys';
|
import { i18nText } from '../../i18nKeys';
|
||||||
|
@ -15,6 +16,12 @@ type ExpertDetailsProps = {
|
||||||
locale?: string;
|
locale?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ExpertPracticeProps = {
|
||||||
|
cases?: Practice[];
|
||||||
|
themes?: ThemeGroup[];
|
||||||
|
locale?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale }) => {
|
export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale }) => {
|
||||||
const { publicCoachDetails } = expert || {};
|
const { publicCoachDetails } = expert || {};
|
||||||
|
|
||||||
|
@ -62,10 +69,10 @@ export const ExpertInformation: FC<ExpertDetailsProps> = ({ expert, locale }) =>
|
||||||
<div className="expert-info">
|
<div className="expert-info">
|
||||||
{/* <h2 className="title-h2">{}</h2> */}
|
{/* <h2 className="title-h2">{}</h2> */}
|
||||||
<div className="skills__list">
|
<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>
|
||||||
</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
|
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.
|
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
|
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 />
|
Strategic thinking <br /><br />
|
||||||
|
|
||||||
Oh, and I also speak Spanish!
|
Oh, and I also speak Spanish!
|
||||||
</p>
|
</p> */}
|
||||||
<div className="skills__list">
|
<div className="skills__list">
|
||||||
{tags?.map((skill) => <Tag key={skill?.id} className="skills__list__item">{skill?.name}</Tag>)}
|
{tags?.map((skill) => <Tag key={skill?.id} className="skills__list__item">{skill?.name}</Tag>)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,14 +100,12 @@ export const ExpertInformation: FC<ExpertDetailsProps> = ({ expert, locale }) =>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExpertPractice: FC<ExpertDetailsProps> = ({ expert, locale }) => {
|
export const ExpertPractice: FC<ExpertPracticeProps> = ({ themes = [], cases = [], locale }) => {
|
||||||
const { publicCoachDetails: { practiceCases = [], themesGroups = [] } } = expert || {};
|
return cases?.length > 0 ? (
|
||||||
|
|
||||||
return practiceCases?.length > 0 ? (
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="title-h3">{i18nText('successfulCase', locale)}</h3>
|
<h3 className="title-h3">{i18nText('successfulCase', locale)}</h3>
|
||||||
{practiceCases?.map(({ id, description, themesGroupIds }) => {
|
{cases?.map(({ id, description, themesGroupIds }) => {
|
||||||
const filtered = themesGroups?.filter(({ id }) => themesGroupIds?.includes(+id));
|
const filtered = themes ? themes.filter(({ id }) => themesGroupIds?.includes(+id)) : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={id} className="case-list">
|
<div key={id} className="case-list">
|
||||||
|
|
|
@ -162,18 +162,15 @@ export const ExpertsFilter = ({
|
||||||
), [filter, searchParams, searchData]);
|
), [filter, searchParams, searchData]);
|
||||||
|
|
||||||
const getLangList = () => {
|
const getLangList = () => {
|
||||||
const reg = searchLang ? new RegExp(searchLang, 'ig') : '';
|
const langList = searchLang ? (languages || []).filter(({ code, nativeSpelling }) => code.indexOf(searchLang) !== -1 || nativeSpelling.indexOf(searchLang) !== -1) : languages;
|
||||||
const langList = reg ? (languages || []).filter(({ code, nativeSpelling }) => reg.test(code) || reg.test(nativeSpelling)) : languages;
|
|
||||||
return langList?.length
|
return langList?.length
|
||||||
? getList('userLanguages', langList.map(({ code, nativeSpelling }) => ({ id: code, name: nativeSpelling })))
|
? getList('userLanguages', langList.map(({ code, nativeSpelling }) => ({ id: code, name: nativeSpelling })))
|
||||||
: null;
|
: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTagsList = () => {
|
const getTagsList = () => {
|
||||||
const reg = searchTags ? new RegExp(searchTags, 'ig') : '';
|
if (searchTags) {
|
||||||
|
const tagsList = filteredTags.filter(({ name, group }) => name.indexOf(searchTags) !== -1 || group.indexOf(searchTags) !== -1);
|
||||||
if (reg) {
|
|
||||||
const tagsList = filteredTags.filter(({ name, group }) => reg.test(name) || reg.test(group));
|
|
||||||
return getList('themesTagIds', tagsList);
|
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' }}/>}
|
closeIcon={<CloseOutlined style={{ fontSize: 20, color: '#000' }}/>}
|
||||||
>
|
>
|
||||||
<div className="b-modal__expert__content">
|
<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">
|
<div className="b-modal__expert__inner">
|
||||||
{data?.themesGroups && data.themesGroups.filter(({ isActive }) => isActive).map(({ id, name }) => (
|
{data?.themesGroups && data.themesGroups.filter(({ isActive }) => isActive).map(({ id, name }) => (
|
||||||
<div key={`group_${id}`}>
|
<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}
|
{props.children}
|
||||||
</Button>
|
</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';
|
import { Button } from 'antd';
|
||||||
|
|
||||||
export const LinkButton = (props: any) => (
|
export const LinkButton = (props: any) => (
|
||||||
<Button className="b-button__link" {...props}>
|
<Button className={`b-button__link${props?.danger ? ' danger': ''}`} {...props}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -105,6 +105,7 @@ export default {
|
||||||
signUp: 'Jetzt anmelden',
|
signUp: 'Jetzt anmelden',
|
||||||
noData: 'Keine Daten',
|
noData: 'Keine Daten',
|
||||||
notFound: 'Nicht gefunden',
|
notFound: 'Nicht gefunden',
|
||||||
|
skillsInfo: 'Fähigkeiten-Infos',
|
||||||
trainings: 'Trainings',
|
trainings: 'Trainings',
|
||||||
seminars: 'Seminare',
|
seminars: 'Seminare',
|
||||||
courses: 'Kurse',
|
courses: 'Kurse',
|
||||||
|
@ -112,7 +113,38 @@ export default {
|
||||||
aboutCoach: 'Über Coach',
|
aboutCoach: 'Über Coach',
|
||||||
education: 'Bildung',
|
education: 'Bildung',
|
||||||
coaching: 'Coaching',
|
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: {
|
errors: {
|
||||||
invalidEmail: 'Die E-Mail-Adresse ist ungültig',
|
invalidEmail: 'Die E-Mail-Adresse ist ungültig',
|
||||||
emptyEmail: 'Bitte geben Sie Ihre E-Mail ein',
|
emptyEmail: 'Bitte geben Sie Ihre E-Mail ein',
|
||||||
|
|
|
@ -110,8 +110,41 @@ export default {
|
||||||
courses: 'Courses',
|
courses: 'Courses',
|
||||||
mba: 'MBA Information',
|
mba: 'MBA Information',
|
||||||
aboutCoach: 'About Coach',
|
aboutCoach: 'About Coach',
|
||||||
|
skillsInfo: 'Skills Info',
|
||||||
education: 'Education',
|
education: 'Education',
|
||||||
coaching: 'Coaching',
|
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: {
|
errors: {
|
||||||
invalidEmail: 'The email address is not valid',
|
invalidEmail: 'The email address is not valid',
|
||||||
emptyEmail: 'Please enter your E-mail',
|
emptyEmail: 'Please enter your E-mail',
|
||||||
|
|
|
@ -105,6 +105,7 @@ export default {
|
||||||
signUp: 'Regístrate ahora',
|
signUp: 'Regístrate ahora',
|
||||||
noData: 'Sin datos',
|
noData: 'Sin datos',
|
||||||
notFound: 'No encontrado',
|
notFound: 'No encontrado',
|
||||||
|
skillsInfo: 'Información',
|
||||||
trainings: 'Formación',
|
trainings: 'Formación',
|
||||||
seminars: 'Seminarios',
|
seminars: 'Seminarios',
|
||||||
courses: 'Cursos',
|
courses: 'Cursos',
|
||||||
|
@ -112,7 +113,38 @@ export default {
|
||||||
aboutCoach: 'Sobre el coach',
|
aboutCoach: 'Sobre el coach',
|
||||||
education: 'Educación',
|
education: 'Educación',
|
||||||
coaching: 'Coaching',
|
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: {
|
errors: {
|
||||||
invalidEmail: 'La dirección de correo electrónico no es válida',
|
invalidEmail: 'La dirección de correo electrónico no es válida',
|
||||||
emptyEmail: 'Introduce tu correo electrónico',
|
emptyEmail: 'Introduce tu correo electrónico',
|
||||||
|
|
|
@ -105,6 +105,7 @@ export default {
|
||||||
signUp: 'Inscrivez-vous maintenant',
|
signUp: 'Inscrivez-vous maintenant',
|
||||||
noData: 'Aucune donnée',
|
noData: 'Aucune donnée',
|
||||||
notFound: 'Non trouvé',
|
notFound: 'Non trouvé',
|
||||||
|
skillsInfo: 'Infos sur les compétences',
|
||||||
trainings: 'Formations',
|
trainings: 'Formations',
|
||||||
seminars: 'Séminaires',
|
seminars: 'Séminaires',
|
||||||
courses: 'Cours',
|
courses: 'Cours',
|
||||||
|
@ -112,7 +113,38 @@ export default {
|
||||||
aboutCoach: 'À propos du coach',
|
aboutCoach: 'À propos du coach',
|
||||||
education: 'Éducation',
|
education: 'Éducation',
|
||||||
coaching: 'Coaching',
|
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: {
|
errors: {
|
||||||
invalidEmail: 'L\'adresse e-mail n\'est pas valide',
|
invalidEmail: 'L\'adresse e-mail n\'est pas valide',
|
||||||
emptyEmail: 'Veuillez saisir votre e-mail',
|
emptyEmail: 'Veuillez saisir votre e-mail',
|
||||||
|
|
|
@ -105,6 +105,7 @@ export default {
|
||||||
signUp: 'Iscriviti ora',
|
signUp: 'Iscriviti ora',
|
||||||
noData: 'Nessun dato',
|
noData: 'Nessun dato',
|
||||||
notFound: 'Non trovato',
|
notFound: 'Non trovato',
|
||||||
|
skillsInfo: 'Info su competenze',
|
||||||
trainings: 'Training',
|
trainings: 'Training',
|
||||||
seminars: 'Seminari',
|
seminars: 'Seminari',
|
||||||
courses: 'Corsi',
|
courses: 'Corsi',
|
||||||
|
@ -112,7 +113,38 @@ export default {
|
||||||
aboutCoach: 'Informazioni sul coach',
|
aboutCoach: 'Informazioni sul coach',
|
||||||
education: 'Istruzione',
|
education: 'Istruzione',
|
||||||
coaching: 'Coaching',
|
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: {
|
errors: {
|
||||||
invalidEmail: 'L\'indirizzo e-mail non è valido',
|
invalidEmail: 'L\'indirizzo e-mail non è valido',
|
||||||
emptyEmail: 'Inserisci l\'e-mail',
|
emptyEmail: 'Inserisci l\'e-mail',
|
||||||
|
|
|
@ -98,21 +98,53 @@ export default {
|
||||||
expertBackground: 'Профессиональный опыт эксперта',
|
expertBackground: 'Профессиональный опыт эксперта',
|
||||||
profCertification: 'Профессиональная сертификация',
|
profCertification: 'Профессиональная сертификация',
|
||||||
practiceHours: 'Часов практики',
|
practiceHours: 'Часов практики',
|
||||||
supervisionCount: 'Часов супервизии в год',
|
supervisionCount: 'Супервизий в год',
|
||||||
outOf: 'из',
|
outOf: 'из',
|
||||||
schedule: 'Расписание',
|
schedule: 'Расписание',
|
||||||
successfulCase: 'Успешные случаи из практики',
|
successfulCase: 'Успешные случаи из практики',
|
||||||
signUp: 'Записаться сейчас',
|
signUp: 'Записаться сейчас',
|
||||||
noData: 'Нет данных',
|
noData: 'Нет данных',
|
||||||
notFound: 'Не найдено',
|
notFound: 'Не найдено',
|
||||||
|
skillsInfo: 'Навыки',
|
||||||
trainings: 'Тренинги',
|
trainings: 'Тренинги',
|
||||||
seminars: 'Семинары',
|
seminars: 'Семинары',
|
||||||
courses: 'Курсы',
|
courses: 'Курсы',
|
||||||
mba: 'Информация о MBA',
|
mba: 'Информация о MBA',
|
||||||
|
experiences: 'Практический опыт',
|
||||||
aboutCoach: 'О коуче',
|
aboutCoach: 'О коуче',
|
||||||
education: 'Образование',
|
education: 'Образование',
|
||||||
coaching: 'Коучинг',
|
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: {
|
errors: {
|
||||||
invalidEmail: 'Адрес электронной почты недействителен',
|
invalidEmail: 'Адрес электронной почты недействителен',
|
||||||
emptyEmail: 'Пожалуйста, введите ваш E-mail',
|
emptyEmail: 'Пожалуйста, введите ваш E-mail',
|
||||||
|
|
|
@ -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 {
|
&__inner {
|
||||||
height: 60vh;
|
height: auto;
|
||||||
|
max-height: 60vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
|
@ -86,3 +87,13 @@
|
||||||
.ant-modal-mask {
|
.ant-modal-mask {
|
||||||
background-color: rgba(0, 59, 70, 0.4) !important;
|
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;
|
height: 86px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
border: 2px solid #FFF;
|
border: 2px solid #FFF;
|
||||||
background: lightgray 50%;
|
|
||||||
box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32);
|
box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
@ -1210,6 +1209,7 @@
|
||||||
@include rem(18);
|
@include rem(18);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 150%;
|
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 {
|
.title-h2 {
|
||||||
color: #003B46;
|
color: #003B46;
|
||||||
@include rem(18);
|
@include rem(18);
|
||||||
|
@ -1243,7 +1298,7 @@
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__desc {
|
&__desc, &__desc > div {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: #EFFCFF;
|
background: #EFFCFF;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
@ -1443,8 +1498,17 @@
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-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 "_message.scss";
|
||||||
@import "_auth-modal.scss";
|
@import "_auth-modal.scss";
|
||||||
@import "_modal.scss";
|
@import "_modal.scss";
|
||||||
|
@import "_edu.scss";
|
||||||
|
@import "_schedule.scss";
|
||||||
|
|
||||||
@import "./view/style.scss";
|
@import "./view/style.scss";
|
||||||
@import "./sessions/style.scss";
|
@import "./sessions/style.scss";
|
||||||
|
|
|
@ -17,6 +17,19 @@
|
||||||
padding: 4px 24px !important;
|
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 {
|
&.danger {
|
||||||
background: #D93E5C !important;
|
background: #D93E5C !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
|
@ -28,6 +41,10 @@
|
||||||
font-size: 15px !important;
|
font-size: 15px !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
color: #D93E5C !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__outlined {
|
&__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 {
|
input {
|
||||||
background-color: transparent !important;
|
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 {
|
&: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 "_spin.scss";
|
||||||
@import "_switch.scss";
|
@import "_switch.scss";
|
||||||
@import "_buttons.scss";
|
@import "_buttons.scss";
|
||||||
|
@import "_practice.scss";
|
||||||
|
@import "_collapse.scss";
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
import { ExpertDocument } from './file';
|
import { ExpertDocument } from './file';
|
||||||
|
|
||||||
export type Details = {
|
export type Details = {
|
||||||
id: number;
|
id?: number;
|
||||||
userId?:number;
|
userId?: number;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
document?: ExpertDocument;
|
document?: ExpertDocument | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Certificate = {
|
export type Certificate = {
|
||||||
id: number;
|
id?: number;
|
||||||
userId?: number;
|
userId?: number;
|
||||||
associationLevelId?: number;
|
associationLevelId?: number;
|
||||||
document?: ExpertDocument;
|
associationId?: number;
|
||||||
|
document?: ExpertDocument | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Experience = {
|
export type Experience = {
|
||||||
id: number,
|
id?: number,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
title?: string,
|
title?: string,
|
||||||
description?: string
|
description?: string
|
||||||
|
@ -24,10 +25,10 @@ export type Experience = {
|
||||||
|
|
||||||
export type Association = {
|
export type Association = {
|
||||||
id: number;
|
id: number;
|
||||||
name?: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AssociationLevel = Association & { associationId?: number };
|
export type AssociationLevel = Association & { associationId: number };
|
||||||
|
|
||||||
export type EducationData = {
|
export type EducationData = {
|
||||||
certificates?: Certificate[],
|
certificates?: Certificate[],
|
||||||
|
|
|
@ -6,7 +6,7 @@ export type Supervision = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PracticeCase = {
|
export type PracticeCase = {
|
||||||
id: number,
|
id?: number,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
description?: string,
|
description?: string,
|
||||||
themesGroupIds?: number[]
|
themesGroupIds?: number[]
|
||||||
|
@ -18,12 +18,14 @@ export type PracticeData = {
|
||||||
sessionDuration?: number,
|
sessionDuration?: number,
|
||||||
sessionCost?: number,
|
sessionCost?: number,
|
||||||
practiceCases?: PracticeCase[]
|
practiceCases?: PracticeCase[]
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface PracticeDTO {
|
export type PracticePersonData = PracticeData & {
|
||||||
person4Data: PracticeData & {
|
|
||||||
themesGroups?: ExpertsThemesGroups[],
|
themesGroups?: ExpertsThemesGroups[],
|
||||||
supervisionPerYears?: Supervision[],
|
supervisionPerYears?: Supervision[],
|
||||||
sessionCosts?: number[]
|
sessionCosts?: number[]
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export interface PracticeDTO {
|
||||||
|
person4Data: PracticePersonData
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ export type ProfileData = {
|
||||||
hasExternalLogin?: boolean;
|
hasExternalLogin?: boolean;
|
||||||
isTestMode?: boolean;
|
isTestMode?: boolean;
|
||||||
phone?: string;
|
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 };
|
export type Profile = ProfileData & { id: number };
|
||||||
|
|
|
@ -28,3 +28,19 @@ export const validateImage = (file: UploadFile, showMessage?: boolean): boolean
|
||||||
|
|
||||||
return isImage && isLt5M;
|
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