Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
dzfelix | 2da77f7347 | |
SD | 87b14e8716 | |
SD | 52fba3a879 | |
SD | dbd5eaa014 | |
SD | 3ab06523cc | |
SD | a13eac0ac4 | |
SD | 79a133c3ca | |
dzfelix | 61de5c81e7 |
1
.env
1
.env
|
@ -7,3 +7,4 @@ STRIPE_PAYMENT_DESCRIPTION='BBuddy services'
|
||||||
NEXT_PUBLIC_CONTENTFUL_SPACE_ID = voxpxjq7y7vf
|
NEXT_PUBLIC_CONTENTFUL_SPACE_ID = voxpxjq7y7vf
|
||||||
NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc
|
NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc
|
||||||
NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE
|
NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "bbuddy-ui",
|
"name": "bbuddy-ui",
|
||||||
"version": "0.2.3",
|
"version": "0.2.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 4200",
|
"dev": "next dev -p 4200",
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
"@ant-design/icons": "^5.2.6",
|
"@ant-design/icons": "^5.2.6",
|
||||||
"@ant-design/nextjs-registry": "^1.0.0",
|
"@ant-design/nextjs-registry": "^1.0.0",
|
||||||
"@contentful/rich-text-react-renderer": "^15.22.9",
|
"@contentful/rich-text-react-renderer": "^15.22.9",
|
||||||
|
"@microsoft/signalr": "^8.0.7",
|
||||||
"@stripe/react-stripe-js": "^2.7.3",
|
"@stripe/react-stripe-js": "^2.7.3",
|
||||||
"@stripe/stripe-js": "^4.1.0",
|
"@stripe/stripe-js": "^4.1.0",
|
||||||
"agora-rtc-react": "2.1.0",
|
"agora-rtc-react": "2.1.0",
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
"next-intl": "^3.3.1",
|
"next-intl": "^3.3.1",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-signalr": "^0.2.24",
|
||||||
"react-slick": "^0.29.0",
|
"react-slick": "^0.29.0",
|
||||||
"react-stripe-js": "^1.1.5",
|
"react-stripe-js": "^1.1.5",
|
||||||
"slick-carousel": "^1.8.1",
|
"slick-carousel": "^1.8.1",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"package_name": "com.bbuddy.whistle",
|
"package_name": "com.bbuddy.whistle",
|
||||||
"sha256_cert_fingerprints": [
|
"sha256_cert_fingerprints": [
|
||||||
"87:A2:49:9A:F4:05:9C:06:3C:3D:F3:10:88:F5:49:6D:5F:F2:BC:1E:90:0D:F2:37:A5:BA:37:19:5C:A3:75:C2",
|
"87:A2:49:9A:F4:05:9C:06:3C:3D:F3:10:88:F5:49:6D:5F:F2:BC:1E:90:0D:F2:37:A5:BA:37:19:5C:A3:75:C2",
|
||||||
|
"D0:28:97:E7:64:5D:ED:8D:7F:F1:41:B2:E8:F6:AB:7B:EE:FB:A3:1A:A2:D7:92:D4:C5:41:9A:3C:47:CE:EB:43",
|
||||||
"86:42:FE:EA:44:22:9D:16:7F:FC:70:92:A6:39:9D:B1:C3:F1:DE:21:32:4A:45:8C:07:98:39:55:AF:47:32:66"
|
"86:42:FE:EA:44:22:9D:16:7F:FC:70:92:A6:39:9D:B1:C3:F1:DE:21:32:4A:45:8C:07:98:39:55:AF:47:32:66"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import {apiRequest} from "../helpers";
|
||||||
|
|
||||||
|
|
||||||
|
export const getChatList = (locale: string, token: string): Promise<any> => apiRequest({
|
||||||
|
url: '/chat/chatList',
|
||||||
|
method: 'get',
|
||||||
|
locale,
|
||||||
|
token
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getChatMessages = (locale: string, token: string, group: number): Promise<any> => apiRequest({
|
||||||
|
url: '/chat/chat_messages/'+group,
|
||||||
|
method: 'get',
|
||||||
|
locale,
|
||||||
|
token
|
||||||
|
});
|
|
@ -23,22 +23,22 @@ export const apiRequest = async <T = any, K = any>(
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// const {
|
const {
|
||||||
// response: {
|
response: {
|
||||||
// status: responseCode = null,
|
status: responseCode = null,
|
||||||
// statusText = '',
|
statusText = '',
|
||||||
// data: { message = '', status: errorKey = '' } = {},
|
data,
|
||||||
// } = {},
|
} = {},
|
||||||
// code: statusCode = '',
|
code: statusCode = '',
|
||||||
// } = err as AxiosError;
|
} = err as AxiosError;
|
||||||
//
|
|
||||||
// throw new Error(
|
throw new Error(
|
||||||
// JSON.stringify({
|
JSON.stringify({
|
||||||
// statusCode,
|
statusCode,
|
||||||
// statusMessage: message || statusText,
|
statusMessage: statusText,
|
||||||
// responseCode,
|
responseCode,
|
||||||
// errorKey,
|
details: data
|
||||||
// }),
|
}),
|
||||||
// );
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import {useCallback, useEffect, useState} from 'react';
|
||||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
import {useLocalStorage} from '../../hooks/useLocalStorage';
|
||||||
import { AUTH_TOKEN_KEY } from '../../constants/common';
|
import {AUTH_TOKEN_KEY} from '../../constants/common';
|
||||||
import { Room } from '../../types/rooms';
|
import {Room} from '../../types/rooms';
|
||||||
import { getRoomDetails } from '../rooms';
|
import {getRoomDetails} from '../rooms';
|
||||||
|
import {SessionState} from "../../types/sessions";
|
||||||
|
|
||||||
export const useRoomDetails = (locale: string, roomId: number) => {
|
export const useRoomDetails = (locale: string, roomId: number) => {
|
||||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||||
const [room, setRoom] = useState<Room>();
|
const [room, setRoom] = useState<Room>();
|
||||||
const [errorData, setErrorData] = useState<any>();
|
const [errorData, setErrorData] = useState<any>();
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [isStarted, setIsStarted] = useState(false);
|
||||||
|
|
||||||
const fetchData = useCallback(() => {
|
const fetchData = useCallback(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
@ -33,10 +35,17 @@ export const useRoomDetails = (locale: string, roomId: number) => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (room?.state === SessionState.STARTED) {
|
||||||
|
setIsStarted(true);
|
||||||
|
}
|
||||||
|
}, [room?.state])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetchData,
|
fetchData,
|
||||||
loading,
|
loading,
|
||||||
room,
|
room,
|
||||||
errorData
|
errorData,
|
||||||
|
isStarted
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { apiRequest } from './helpers';
|
import { apiRequest } from './helpers';
|
||||||
import {GetUsersForRooms, Room, RoomEdit, RoomEditDTO} from '../types/rooms';
|
import {GetUsersForRooms, Report, ReportData, Room, RoomEdit, RoomEditDTO} from '../types/rooms';
|
||||||
|
|
||||||
export const getUpcomingRooms = (locale: string, token: string): Promise<Room[]> => apiRequest({
|
export const getUpcomingRooms = (locale: string, token: string): Promise<Room[]> => apiRequest({
|
||||||
url: '/home/upcomingsessionsall',
|
url: '/home/upcomingsessionsall',
|
||||||
|
@ -107,3 +107,19 @@ export const getRoomById = (locale: string, token: string, id: number): Promise<
|
||||||
locale,
|
locale,
|
||||||
token
|
token
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// report
|
||||||
|
export const getReport = (locale: string, token: string, id: number): Promise<Report[]> => apiRequest({
|
||||||
|
url: `/home/getsessionsupervisorscores?sessionId=${id}`,
|
||||||
|
method: 'post',
|
||||||
|
locale,
|
||||||
|
token
|
||||||
|
});
|
||||||
|
|
||||||
|
export const saveReport = (locale: string, token: string, data: ReportData): Promise<any> => apiRequest({
|
||||||
|
url: '/home/setsessionsupervisorscores',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
locale,
|
||||||
|
token
|
||||||
|
});
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
'use client'
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { unstable_setRequestLocale } from 'next-intl/server';
|
|
||||||
import { Link } from '../../../../../../navigation';
|
import { Link } from '../../../../../../navigation';
|
||||||
import { i18nText } from '../../../../../../i18nKeys';
|
import { i18nText } from '../../../../../../i18nKeys';
|
||||||
|
import {ChatMessages} from "../../../../../../components/Chat/ChatMessages";
|
||||||
|
/*
|
||||||
export function generateStaticParams({
|
export function generateStaticParams({
|
||||||
params: { locale },
|
params: { locale },
|
||||||
}: { params: { locale: string } }) {
|
}: { params: { locale: string } }) {
|
||||||
|
@ -15,56 +16,22 @@ export function generateStaticParams({
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
export default function Message({ params }: { params: { locale: string, textId: string } }) {
|
export default function Message({ params: { locale, textId } }: { params: { locale: string, textId: string } }) {
|
||||||
unstable_setRequestLocale(params.locale);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ol className="breadcrumb">
|
<ol className="breadcrumb">
|
||||||
<li className="breadcrumb-item">
|
<li className="breadcrumb-item">
|
||||||
<Link href={'/account/messages' as any}>
|
<Link href={'/account/messages' as any}>
|
||||||
{i18nText('accountMenu.messages', params.locale)}
|
{i18nText('accountMenu.messages', locale)}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="breadcrumb-item active" aria-current="page">{`Person ${params.textId}`}</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<div className="b-message">
|
<ChatMessages locale={locale} groupId={parseInt(textId)} />
|
||||||
<div className="b-message__inner">
|
|
||||||
<div className="b-message__list b-message__list--me">
|
|
||||||
<div className="b-message__item ">
|
|
||||||
<div className="b-message__avatar">
|
|
||||||
<img src="/images/person.png" className="" alt="" />
|
|
||||||
</div>
|
|
||||||
<div className="b-message__text">
|
|
||||||
🤩 It all for you!
|
|
||||||
|
|
||||||
<span className="date">07.09.2022</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="b-message__list">
|
|
||||||
<div className="b-message__item">
|
|
||||||
<div className="b-message__avatar">
|
|
||||||
<img src="/images/person.png" className="" alt="" />
|
|
||||||
</div>
|
|
||||||
<div className="b-message__text">
|
|
||||||
🤩 It all for you!
|
|
||||||
<span className="date">07.09.2022</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form className="b-message__form" action="">
|
|
||||||
<textarea placeholder="Type your message here" name="" id="" />
|
|
||||||
<label className="b-message__upload-file">
|
|
||||||
<input type="file" required />
|
|
||||||
</label>
|
|
||||||
<div className="b-message__microphone" />
|
|
||||||
<button className="b-message__btn" type="submit" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
|
'use client'
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { unstable_setRequestLocale } from 'next-intl/server';
|
|
||||||
import { Link } from '../../../../../navigation';
|
|
||||||
import { CustomInput } from '../../../../../components/view/CustomInput';
|
import { CustomInput } from '../../../../../components/view/CustomInput';
|
||||||
import { i18nText } from '../../../../../i18nKeys';
|
import { i18nText } from '../../../../../i18nKeys';
|
||||||
|
import {ChatList} from "../../../../../components/Chat/ChatList";
|
||||||
|
|
||||||
export default function Messages({ params: { locale } }: { params: { locale: string } }) {
|
export default function Messages({ params: { locale } }: { params: { locale: string } }) {
|
||||||
unstable_setRequestLocale(locale);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ol className="breadcrumb">
|
<ol className="breadcrumb">
|
||||||
|
@ -15,74 +13,7 @@ export default function Messages({ params: { locale } }: { params: { locale: str
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<CustomInput placeholder={i18nText('name', locale)} />
|
<CustomInput placeholder={i18nText('name', locale)} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<div className="messages-session">
|
<ChatList locale={locale}/>
|
||||||
<Link
|
|
||||||
className="card-profile"
|
|
||||||
href={'messages/1' as any}
|
|
||||||
>
|
|
||||||
<div className="card-profile__header">
|
|
||||||
<div className="card-profile__header__portrait">
|
|
||||||
<img src="/images/person.png" className="" alt="" />
|
|
||||||
</div>
|
|
||||||
<div className="card-profile__header__inner">
|
|
||||||
<div style={{ width: '100%' }}>
|
|
||||||
<div className="card-profile__header__name">
|
|
||||||
David
|
|
||||||
<span className="count">14</span>
|
|
||||||
</div>
|
|
||||||
<div className="card-profile__header__title">
|
|
||||||
Lorem ipsum dolor sit at, consecte...
|
|
||||||
</div>
|
|
||||||
<div className="card-profile__header__date ">
|
|
||||||
25 may
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
className="card-profile"
|
|
||||||
href={'messages/2' as any}
|
|
||||||
>
|
|
||||||
<div className="card-profile__header">
|
|
||||||
<div className="card-profile__header__portrait">
|
|
||||||
<img src="/images/person.png" className="" alt="" />
|
|
||||||
</div>
|
|
||||||
<div className="card-profile__header__inner">
|
|
||||||
<div style={{ width: '100%' }}>
|
|
||||||
<div className="card-profile__header__name">David</div>
|
|
||||||
<div className="card-profile__header__title">
|
|
||||||
Lorem ipsum dolor sit at, consecte...
|
|
||||||
</div>
|
|
||||||
<div className="card-profile__header__date ">
|
|
||||||
25 may
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
className="card-profile"
|
|
||||||
href={'messages/3' as any}
|
|
||||||
>
|
|
||||||
<div className="card-profile__header">
|
|
||||||
<div className="card-profile__header__portrait">
|
|
||||||
<img src="/images/person.png" className="" alt="" />
|
|
||||||
</div>
|
|
||||||
<div className="card-profile__header__inner">
|
|
||||||
<div style={{ width: '100%' }}>
|
|
||||||
<div className="card-profile__header__name">David</div>
|
|
||||||
<div className="card-profile__header__title">
|
|
||||||
Lorem ipsum dolor sit at, consecte...
|
|
||||||
</div>
|
|
||||||
<div className="card-profile__header__date ">
|
|
||||||
25 may
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,21 +4,44 @@ import { Button } from 'antd';
|
||||||
import { useSelectedLayoutSegment, usePathname } from 'next/navigation';
|
import { useSelectedLayoutSegment, usePathname } from 'next/navigation';
|
||||||
import { Link } from '../../navigation';
|
import { Link } from '../../navigation';
|
||||||
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../constants/common';
|
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../constants/common';
|
||||||
import { deleteStorageKey } from '../../hooks/useLocalStorage';
|
import {deleteStorageKey, useLocalStorage} from '../../hooks/useLocalStorage';
|
||||||
import { i18nText } from '../../i18nKeys';
|
import { i18nText } from '../../i18nKeys';
|
||||||
import { getMenuConfig } from '../../utils/account';
|
import { getMenuConfig } from '../../utils/account';
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {getChatList} from "../../actions/chat/groups";
|
||||||
|
|
||||||
export const AccountMenu = ({ locale }: { locale: string }) => {
|
export const AccountMenu = ({ locale }: { locale: string }) => {
|
||||||
const selectedLayoutSegment = useSelectedLayoutSegment();
|
const selectedLayoutSegment = useSelectedLayoutSegment();
|
||||||
const pathname = selectedLayoutSegment || '';
|
const pathname = selectedLayoutSegment || '';
|
||||||
const paths = usePathname();
|
const paths = usePathname();
|
||||||
const menu: { path: string, title: string, count?: number }[] = getMenuConfig(locale);
|
const menu: { path: string, title: string, count?: number }[] = getMenuConfig(locale);
|
||||||
|
const [counts, setCounts] = useState<any>({});
|
||||||
|
|
||||||
const onLogout = () => {
|
const onLogout = () => {
|
||||||
deleteStorageKey(AUTH_TOKEN_KEY);
|
deleteStorageKey(AUTH_TOKEN_KEY);
|
||||||
deleteStorageKey(AUTH_USER);
|
deleteStorageKey(AUTH_USER);
|
||||||
window?.location?.replace(`/${paths.split('/')[1]}/`);
|
window?.location?.replace(`/${paths.split('/')[1]}/`);
|
||||||
};
|
};
|
||||||
|
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||||
|
let init = false
|
||||||
|
useEffect(() => {
|
||||||
|
if (jwt && locale && !init) {
|
||||||
|
init = true;
|
||||||
|
getChatList(locale, jwt).then((payload: any)=> {
|
||||||
|
if (payload?.directs) {
|
||||||
|
let summ = 0;
|
||||||
|
payload?.directs.forEach((el: any) => {
|
||||||
|
summ = summ + el.newMessagesCount
|
||||||
|
})
|
||||||
|
setCounts({'messages': summ})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [jwt, locale])
|
||||||
|
|
||||||
|
const getterCount = (path: string, count: number)=> {
|
||||||
|
return counts[path]? counts[path] : count
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="list-sidebar">
|
<ul className="list-sidebar">
|
||||||
|
@ -26,8 +49,8 @@ export const AccountMenu = ({ locale }: { locale: string }) => {
|
||||||
<li key={path} className="list-sidebar__item">
|
<li key={path} className="list-sidebar__item">
|
||||||
<Link href={`/account/${path}` as any} className={path === pathname ? 'active' : ''}>
|
<Link href={`/account/${path}` as any} className={path === pathname ? 'active' : ''}>
|
||||||
{title}
|
{title}
|
||||||
{count ? (
|
{getterCount(path, count) ? (
|
||||||
<span className="count">{count}</span>
|
<span className="count">{getterCount(path, count)}</span>
|
||||||
) : null}
|
) : null}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { RoomsType } from '../../../types/rooms';
|
import { RoomsType } from '../../../types/rooms';
|
||||||
import { useSessionTracking } from '../../../actions/hooks/useSessionTracking';
|
import { useSessionTracking } from '../../../actions/hooks/useSessionTracking';
|
||||||
import { AccountMenu } from '../AccountMenu';
|
import { AccountMenu } from '../AccountMenu';
|
||||||
|
@ -8,6 +8,9 @@ import { Loader } from '../../view/Loader';
|
||||||
import { RoomDetailsContent } from './RoomDetailsContent';
|
import { RoomDetailsContent } from './RoomDetailsContent';
|
||||||
import { useRoomDetails } from '../../../actions/hooks/useRoomDetails';
|
import { useRoomDetails } from '../../../actions/hooks/useRoomDetails';
|
||||||
import { AgoraClientGroup } from '../agora';
|
import { AgoraClientGroup } from '../agora';
|
||||||
|
import { SupervisorReportModal } from '../../Modals/SupervisorReportModal';
|
||||||
|
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||||
|
import { AUTH_USER } from '../../../constants/common';
|
||||||
|
|
||||||
type RoomDetailsProps = {
|
type RoomDetailsProps = {
|
||||||
locale: string;
|
locale: string;
|
||||||
|
@ -16,9 +19,13 @@ type RoomDetailsProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoomDetails = ({ roomId, locale, activeType }: RoomDetailsProps) => {
|
export const RoomDetails = ({ roomId, locale, activeType }: RoomDetailsProps) => {
|
||||||
const { room, errorData, loading, fetchData } = useRoomDetails(locale, roomId);
|
const { room, errorData, loading, fetchData, isStarted } = useRoomDetails(locale, roomId);
|
||||||
const tracking = useSessionTracking(locale, roomId);
|
const tracking = useSessionTracking(locale, roomId);
|
||||||
const [isCalling, setIsCalling] = useState<boolean>(false);
|
const [isCalling, setIsCalling] = useState<boolean>(false);
|
||||||
|
const [isOpenReport, setIsOpenReport] = useState<boolean>(false);
|
||||||
|
const [userData] = useLocalStorage(AUTH_USER, '');
|
||||||
|
const { id: userId = 0 } = userData ? JSON.parse(userData) : {};
|
||||||
|
const isSupervisor = room?.supervisor && room.supervisor.id === +userId || false;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCalling) {
|
if (isCalling) {
|
||||||
|
@ -28,6 +35,12 @@ export const RoomDetails = ({ roomId, locale, activeType }: RoomDetailsProps) =>
|
||||||
}
|
}
|
||||||
}, [isCalling]);
|
}, [isCalling]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSupervisor && isStarted) {
|
||||||
|
setIsOpenReport(true);
|
||||||
|
}
|
||||||
|
}, [isStarted]);
|
||||||
|
|
||||||
const stopCalling = () => {
|
const stopCalling = () => {
|
||||||
setIsCalling(false);
|
setIsCalling(false);
|
||||||
fetchData();
|
fetchData();
|
||||||
|
@ -60,6 +73,15 @@ export const RoomDetails = ({ roomId, locale, activeType }: RoomDetailsProps) =>
|
||||||
/>
|
/>
|
||||||
</Loader>
|
</Loader>
|
||||||
</div>
|
</div>
|
||||||
|
{isSupervisor && room?.id && (
|
||||||
|
<SupervisorReportModal
|
||||||
|
open={isOpenReport}
|
||||||
|
handleCancel={() => setIsOpenReport(false)}
|
||||||
|
locale={locale}
|
||||||
|
refresh={fetchData}
|
||||||
|
roomId={room.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Button, notification, Tag } from 'antd';
|
import { Button, notification, Tag } from 'antd';
|
||||||
import { DeleteOutlined, LeftOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, LeftOutlined } from '@ant-design/icons';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from '../../../navigation';
|
import { useRouter } from '../../../navigation';
|
||||||
import { Room, RoomsType } from '../../../types/rooms';
|
import { Report, Room, RoomsType } from '../../../types/rooms';
|
||||||
import { i18nText } from '../../../i18nKeys';
|
import { i18nText } from '../../../i18nKeys';
|
||||||
import { LinkButton } from '../../view/LinkButton';
|
import { LinkButton } from '../../view/LinkButton';
|
||||||
import {
|
import {
|
||||||
|
@ -15,8 +15,9 @@ import {
|
||||||
becomeRoomClient,
|
becomeRoomClient,
|
||||||
becomeRoomSupervisor,
|
becomeRoomSupervisor,
|
||||||
deleteRoomClient,
|
deleteRoomClient,
|
||||||
deleteRoomSupervisor
|
deleteRoomSupervisor,
|
||||||
} from '../../../actions/rooms';
|
getReport
|
||||||
|
} from '../../../actions/rooms';
|
||||||
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../constants/common';
|
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../constants/common';
|
||||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||||
import { UserListModal } from '../../Modals/UsersListModal';
|
import { UserListModal } from '../../Modals/UsersListModal';
|
||||||
|
@ -46,6 +47,16 @@ export const RoomDetailsContent = ({ room, startRoom, locale, activeType, refres
|
||||||
const isClient = room?.clients && room.clients.length > 0 && room.clients.map(({ id }) => id).includes(+userId) || false;
|
const isClient = room?.clients && room.clients.length > 0 && room.clients.map(({ id }) => id).includes(+userId) || false;
|
||||||
const isTimeBeforeStart = room?.scheduledStartAtUtc ? dayjs() < dayjs(room.scheduledStartAtUtc) : false;
|
const isTimeBeforeStart = room?.scheduledStartAtUtc ? dayjs() < dayjs(room.scheduledStartAtUtc) : false;
|
||||||
const [isEdit, setIsEdit] = useState<boolean>(false);
|
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||||||
|
const [report, setReport] = useState<Report[] | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (room?.id && room?.supervisor && activeType === RoomsType.RECENT) {
|
||||||
|
getReport(locale, jwt, room.id)
|
||||||
|
.then((data) => {
|
||||||
|
setReport(data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [room])
|
||||||
|
|
||||||
const goBack = () => router.push(`/account/rooms/${activeType}`);
|
const goBack = () => router.push(`/account/rooms/${activeType}`);
|
||||||
|
|
||||||
|
@ -248,6 +259,17 @@ export const RoomDetailsContent = ({ room, startRoom, locale, activeType, refres
|
||||||
{room?.supervisorComment && (
|
{room?.supervisorComment && (
|
||||||
<div className="card-detail__supervisor-comment">{room.supervisorComment}</div>
|
<div className="card-detail__supervisor-comment">{room.supervisorComment}</div>
|
||||||
)}
|
)}
|
||||||
|
{report && report.length > 0 && (
|
||||||
|
<div className="card-detail__report-list">
|
||||||
|
{report.map(({ key, score }) => (
|
||||||
|
<div key={key}>
|
||||||
|
<div>{i18nText(`room.rating_${key?.toLowerCase()}`, locale)}</div>
|
||||||
|
<div className="card-detail__report-list_divider" />
|
||||||
|
<div>{score || 0}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isTimeBeforeStart && room?.state === SessionState.COACH_APPROVED && !room?.supervisor && isCreator && activeType === RoomsType.UPCOMING && (
|
{isTimeBeforeStart && room?.state === SessionState.COACH_APPROVED && !room?.supervisor && isCreator && activeType === RoomsType.UPCOMING && (
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
'use client'
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {AUTH_TOKEN_KEY} from '../../constants/common';
|
||||||
|
import {getChatList, getChatMessages} from "../../actions/chat/groups";
|
||||||
|
import {useLocalStorage} from "../../hooks/useLocalStorage";
|
||||||
|
import {Link} from "../../navigation";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
import {message} from "antd";
|
||||||
|
import {Loader} from "../view/Loader";
|
||||||
|
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
type CompProps = {
|
||||||
|
locale: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChatList = ({ locale }: CompProps) => {
|
||||||
|
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||||
|
const [chats, setСhats] = useState<any | undefined>();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (jwt) {
|
||||||
|
setLoading(true);
|
||||||
|
Promise.all([
|
||||||
|
getChatList(locale, jwt),
|
||||||
|
])
|
||||||
|
.then(([_groups]) => {
|
||||||
|
setСhats(_groups)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
message.error('Не удалось загрузить данные');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},[jwt])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="messages-session">
|
||||||
|
<Loader isLoading={loading}>
|
||||||
|
{chats?.directs.map((item: any, i: number) => (
|
||||||
|
<Link
|
||||||
|
key={'chat'+i}
|
||||||
|
className="card-profile"
|
||||||
|
href={'messages/'+item.group.id as any}
|
||||||
|
>
|
||||||
|
<div className="card-profile__header">
|
||||||
|
<div className="card-profile__header__portrait">
|
||||||
|
<img src={item.faceImageUrl} className="" alt="" />
|
||||||
|
</div>
|
||||||
|
<div className="card-profile__header__inner">
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<div className="card-profile__header__name">
|
||||||
|
{item.firstName}
|
||||||
|
{item.newMessagesCount && (
|
||||||
|
<span className="count">{item.newMessagesCount}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="card-profile__header__title">
|
||||||
|
{item?.lastMessage?.text}
|
||||||
|
</div>
|
||||||
|
<div className="card-profile__header__date ">
|
||||||
|
{dayjs(item?.lastMessage?.sentAt).fromNow()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</Loader>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,200 @@
|
||||||
|
'use client'
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {AUTH_TOKEN_KEY} from '../../constants/common';
|
||||||
|
import {getChatList, getChatMessages} from "../../actions/chat/groups";
|
||||||
|
import {useLocalStorage} from "../../hooks/useLocalStorage";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
|
||||||
|
import {message} from "antd";
|
||||||
|
import {Loader} from "../view/Loader";
|
||||||
|
import SignalrConnection from "../../lib/signalr-connection";
|
||||||
|
import {CheckOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
type CompProps = {
|
||||||
|
locale: string;
|
||||||
|
groupId: number
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChatMessages = ({ locale, groupId }: CompProps) => {
|
||||||
|
const { newMessage, joinChat, readMessages, addListener } = SignalrConnection();
|
||||||
|
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||||
|
//const messages = await getChatMessages(locale, jwt, groupId)
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
const [me, setMe] = useState<any | undefined>();
|
||||||
|
const [notMe, setNotMe] = useState<any | undefined>();
|
||||||
|
const [messages, setMessages] = useState<any[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (jwt) {
|
||||||
|
setLoading(true);
|
||||||
|
Promise.all([
|
||||||
|
getChatList(locale, jwt),
|
||||||
|
getChatMessages(locale, jwt, groupId),
|
||||||
|
])
|
||||||
|
.then(([_groups, _messages]) => {
|
||||||
|
//
|
||||||
|
const _group = _groups.directs.find( (el: any) => el.group.id === groupId)
|
||||||
|
if (_group.group.members[0].userId === parseInt(_group.userId)){
|
||||||
|
setMe(_group.group.members[0].user)
|
||||||
|
setNotMe(_group.group.members[1].user)
|
||||||
|
} else {
|
||||||
|
setMe(_group.group.members[1].user)
|
||||||
|
setNotMe(_group.group.members[0].user)
|
||||||
|
}
|
||||||
|
setMessages(_messages.messages);
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
message.error('Не удалось загрузить данные');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},[jwt])
|
||||||
|
|
||||||
|
|
||||||
|
const onConnected = (flag: boolean) =>{
|
||||||
|
if (flag) {
|
||||||
|
joinChat(groupId)
|
||||||
|
readUreaded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const readUreaded = () => {
|
||||||
|
const msgs = [] as number[];
|
||||||
|
messages.forEach((message: any) => {
|
||||||
|
if (!message.seen && message.sentByMe === false){
|
||||||
|
msgs.push(message.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
readMessages(msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReceiveMessage = (payload: any) => {
|
||||||
|
const _messages = [... messages];
|
||||||
|
let flag = false;
|
||||||
|
const msg = {
|
||||||
|
data : payload.data,
|
||||||
|
dataType: null,
|
||||||
|
id: payload.id,
|
||||||
|
receiverId:null,
|
||||||
|
seen: false,
|
||||||
|
senderId: payload.creatorId,
|
||||||
|
sentAt: payload.createdUtc+'.000Z',
|
||||||
|
sentByMe: payload.creatorId === me.id,
|
||||||
|
text: payload.content,
|
||||||
|
type:"text"
|
||||||
|
}
|
||||||
|
_messages.forEach((item: any, i) => {
|
||||||
|
if (item.id === msg.id){
|
||||||
|
_messages[i] = msg
|
||||||
|
flag = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//console.log(payload, flag)
|
||||||
|
if (flag){
|
||||||
|
setMessages([..._messages]);
|
||||||
|
} else {
|
||||||
|
setMessages([msg, ..._messages]);
|
||||||
|
}
|
||||||
|
readMessages([msg.id])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOpponentRead = (payload: any) => {
|
||||||
|
console.log('onOpponentRead', payload)
|
||||||
|
const _messages = [... messages] as any[];
|
||||||
|
_messages.forEach((item: any, i) => {
|
||||||
|
if (item.id === payload.messageId){
|
||||||
|
_messages[i].seen = true
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setMessages([..._messages])
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addListener('onConnected', onConnected)
|
||||||
|
addListener('ReceiveMessage', onReceiveMessage)
|
||||||
|
addListener('MessageWasRead', onOpponentRead)
|
||||||
|
}, [messages, me, setMessages]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleSendMessages = () => {
|
||||||
|
newMessage({
|
||||||
|
GroupId: groupId.toString(),
|
||||||
|
CreatorId: me.id,
|
||||||
|
Content: text
|
||||||
|
})
|
||||||
|
setText('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEnterPress = (e: any) => {
|
||||||
|
if(e.keyCode == 13 && e.shiftKey == false) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSendMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChangeMessage = (e: any) =>{
|
||||||
|
const { value } = e.target;
|
||||||
|
e.preventDefault();
|
||||||
|
setText(value);
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const messageRender = (message: any, i:number) => {
|
||||||
|
const item = message.sentByMe ? me : notMe
|
||||||
|
const date = dayjs(message.sentAt).fromNow()
|
||||||
|
const imgSrc = item?.faceImage?.descriptor ? 'http://static.bbuddy.expert/' + item.faceImage.descriptor.split(':')[1] : ''
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={message.sentByMe ? 'b-message__list b-message__list--me' : 'b-message__list'}
|
||||||
|
key={'message'+i}
|
||||||
|
>
|
||||||
|
<div className="b-message__item ">
|
||||||
|
<div className="b-message__avatar">
|
||||||
|
{imgSrc && (<img src={imgSrc} className="" alt=""/>)}
|
||||||
|
</div>
|
||||||
|
<div className="b-message__text" style={{minWidth: '150px'}}>
|
||||||
|
{message.text}
|
||||||
|
<span className="date">
|
||||||
|
<span className="checks" style={{color: message.seen ? 'green' : 'blue'}}>
|
||||||
|
<CheckOutlined />
|
||||||
|
{ message?.seen && (<CheckOutlined style={{marginLeft: '-10px'}} />)}
|
||||||
|
</span>
|
||||||
|
{date}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className="b-message">
|
||||||
|
<Loader isLoading={loading}>
|
||||||
|
<div className="b-message__inner">
|
||||||
|
{messages.map((el, i)=> (messageRender(el, i)))}
|
||||||
|
</div>
|
||||||
|
</Loader>
|
||||||
|
<div className="b-message__form">
|
||||||
|
<textarea placeholder="Type your message here" onKeyDown={onEnterPress}
|
||||||
|
onChange={handleChangeMessage} value={text}/>
|
||||||
|
<button className="b-message__btn" type="submit" onClick={handleSendMessages}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
|
@ -15,6 +15,9 @@ import { getStorageValue } from '../../hooks/useLocalStorage';
|
||||||
import { AUTH_TOKEN_KEY, SESSION_DATA } from '../../constants/common';
|
import { AUTH_TOKEN_KEY, SESSION_DATA } from '../../constants/common';
|
||||||
import { ScheduleModal } from '../Modals/ScheduleModal';
|
import { ScheduleModal } from '../Modals/ScheduleModal';
|
||||||
import { ScheduleModalResult } from '../Modals/ScheduleModalResult';
|
import { ScheduleModalResult } from '../Modals/ScheduleModalResult';
|
||||||
|
import SignalrConnection from '../../lib/signalr-connection';
|
||||||
|
import { useRouter } from '../../navigation';
|
||||||
|
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||||
|
|
||||||
type ExpertDetailsProps = {
|
type ExpertDetailsProps = {
|
||||||
expert: ExpertDetails;
|
expert: ExpertDetails;
|
||||||
|
@ -32,8 +35,27 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
|
||||||
const { publicCoachDetails } = expert || {};
|
const { publicCoachDetails } = expert || {};
|
||||||
const [showSchedulerModal, setShowSchedulerModal] = useState<boolean>(false);
|
const [showSchedulerModal, setShowSchedulerModal] = useState<boolean>(false);
|
||||||
const [mode, setMode] = useState<'data' | 'time' | 'pay' | 'finish'>('data');
|
const [mode, setMode] = useState<'data' | 'time' | 'pay' | 'finish'>('data');
|
||||||
const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] } } = expert || {};
|
|
||||||
const isRus = locale === Locale.ru;
|
const isRus = locale === Locale.ru;
|
||||||
|
const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] , id, botUserId} } = expert || {};
|
||||||
|
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||||
|
const { joinChatPerson, closeConnection } = SignalrConnection();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document?.addEventListener('show_pay_form', handleShowPayForm);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
closeConnection();
|
||||||
|
document?.removeEventListener('show_pay_form', handleShowPayForm);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleJoinChat = (id: number) => {
|
||||||
|
joinChatPerson(id).then((res: any) => {
|
||||||
|
router.push(`/account/messages/${res.id}` as string);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const checkSession = (data?: SignupSessionData) => {
|
const checkSession = (data?: SignupSessionData) => {
|
||||||
if (data?.startAtUtc && data?.tagId) {
|
if (data?.startAtUtc && data?.tagId) {
|
||||||
|
@ -44,7 +66,7 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
|
||||||
} else {
|
} else {
|
||||||
setShowSchedulerModal(false);
|
setShowSchedulerModal(false);
|
||||||
const showAuth = new Event('show_auth_enter');
|
const showAuth = new Event('show_auth_enter');
|
||||||
document.dispatchEvent(showAuth);
|
document?.dispatchEvent(showAuth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,13 +76,6 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
|
||||||
setMode('pay');
|
setMode('pay');
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('show_pay_form', handleShowPayForm);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('show_pay_form', handleShowPayForm);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onSchedulerHandle = () => {
|
const onSchedulerHandle = () => {
|
||||||
setMode('data');
|
setMode('data');
|
||||||
setShowSchedulerModal(true);
|
setShowSchedulerModal(true);
|
||||||
|
@ -86,18 +101,24 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="expert-card__wrap-btn">
|
{jwt && (
|
||||||
<Button className="btn-apply" onClick={onSchedulerHandle}>
|
<div className="expert-card__wrap-btn">
|
||||||
<img src="/images/calendar-outline.svg" className="" alt="" />
|
<Button className="btn-apply" onClick={() => handleJoinChat(id)}>
|
||||||
{i18nText('schedule', locale)}
|
{i18nText('chat.join', locale)}
|
||||||
</Button>
|
</Button>
|
||||||
{/*
|
{botUserId && (
|
||||||
<a href="#" className="btn-video">
|
<Button className="btn-apply" onClick={() => handleJoinChat(botUserId)}>
|
||||||
<img src="/images/videocam-outline.svg" className="" alt=""/>
|
{i18nText('chat.joinAI', locale)}
|
||||||
Video
|
</Button>
|
||||||
</a>
|
)}
|
||||||
*/}
|
{/*
|
||||||
</div>
|
<a href="#" className="btn-video">
|
||||||
|
<img src="/images/videocam-outline.svg" className="" alt=""/>
|
||||||
|
Video
|
||||||
|
</a>
|
||||||
|
*/}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="expert-info">
|
<div className="expert-info">
|
||||||
{/* <h2 className="title-h2">{}</h2> */}
|
{/* <h2 className="title-h2">{}</h2> */}
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { FC, useEffect, useRef, useState } from 'react';
|
||||||
|
import { Modal, Button, message, Input } from 'antd';
|
||||||
|
import { CloseOutlined, StarFilled } from '@ant-design/icons';
|
||||||
|
import { i18nText } from '../../i18nKeys';
|
||||||
|
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||||
|
import { AUTH_TOKEN_KEY } from '../../constants/common';
|
||||||
|
import { getReport, saveReport } from '../../actions/rooms';
|
||||||
|
import { Report, ReportData } from '../../types/rooms';
|
||||||
|
import { CustomRate } from '../view/CustomRate';
|
||||||
|
|
||||||
|
type SupervisorReportModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
handleCancel: () => void;
|
||||||
|
locale: string;
|
||||||
|
refresh: () => void;
|
||||||
|
roomId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SupervisorReportModal: FC<SupervisorReportModalProps> = ({
|
||||||
|
open,
|
||||||
|
handleCancel,
|
||||||
|
locale,
|
||||||
|
roomId,
|
||||||
|
refresh
|
||||||
|
}) => {
|
||||||
|
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [report, setReport] = useState<Report[] | undefined>();
|
||||||
|
const reasonRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getReport(locale, jwt, roomId)
|
||||||
|
.then((data) => {
|
||||||
|
setReport(data);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message.error('Не удалось получить отчет');
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSaveRate = () => {
|
||||||
|
const result: ReportData = {
|
||||||
|
sessionId: roomId,
|
||||||
|
sessionSupervisorScores: report || [],
|
||||||
|
supervisorComment: reasonRef?.current?.resizableTextArea?.textArea?.value || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
saveReport(locale, jwt, result)
|
||||||
|
.then(() => {
|
||||||
|
handleCancel();
|
||||||
|
refresh();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message.error('Не удалось сохранить отчет');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChangeRate = (val: number, id: number) => {
|
||||||
|
setReport(report ? report.map((item) => {
|
||||||
|
if (item.evaluationCriteriaId === id) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
score: val
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}) : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
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__report__content">
|
||||||
|
<div className="b-rate-list">
|
||||||
|
{report && report.length > 0 && report.map(({ key, evaluationCriteriaId, score }) => (
|
||||||
|
<div key={evaluationCriteriaId} className="b-rate-list__item">
|
||||||
|
<div className="b-rate-list__item_title">{i18nText(`room.rating_${key?.toLowerCase()}`, locale)}</div>
|
||||||
|
<CustomRate
|
||||||
|
defaultValue={score || 0}
|
||||||
|
character={<StarFilled style={{ fontSize: 32 }} />}
|
||||||
|
onChange={(val: number) => onChangeRate(val, evaluationCriteriaId)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Input.TextArea
|
||||||
|
ref={reasonRef}
|
||||||
|
className="b-textarea"
|
||||||
|
rows={1}
|
||||||
|
placeholder={i18nText('room.tellAboutReason', locale)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
className="btn-apply"
|
||||||
|
onClick={onSaveRate}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{i18nText('room.rate', locale)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
|
@ -65,7 +65,25 @@ export default {
|
||||||
joinParticipant: 'Als Teilnehmer beitreten',
|
joinParticipant: 'Als Teilnehmer beitreten',
|
||||||
rapport: 'Rapport',
|
rapport: 'Rapport',
|
||||||
invite: 'Invite',
|
invite: 'Invite',
|
||||||
save: 'Raum speichern'
|
save: 'Raum speichern',
|
||||||
|
rate: 'Bewerten',
|
||||||
|
tellAboutReason: 'Sag uns, was passiert ist',
|
||||||
|
rating_raport: 'Rapport',
|
||||||
|
rating_position_and_presence: 'Position oder Präsenz eines Coaches',
|
||||||
|
rating_balance_and_frustration: 'Balance zwischen Unterstützung und Frustration',
|
||||||
|
rating_agreement: 'Erstellung einer Coaching-Vereinbarung (Sitzungsvertrag)',
|
||||||
|
rating_planning_and_goals: 'Planung und Zielsetzung',
|
||||||
|
rating_reality: 'Klärung der Realität',
|
||||||
|
rating_opportunities: 'Neue Möglichkeiten gefunden',
|
||||||
|
rating_action_plan: 'Es wurde ein Aktionsplan erstellt',
|
||||||
|
rating_motivation: 'Motivationsquellen gefunden',
|
||||||
|
rating_next_session_stretch: 'Es ist noch Zeit bis zur nächsten Sitzung',
|
||||||
|
rating_relationship: 'Aufbau einer vertrauensvollen Beziehung zum Klienten',
|
||||||
|
rating_listening: 'Tiefes, aktives Zuhören',
|
||||||
|
rating_questions: 'Verwendung „starker“ Fragen',
|
||||||
|
rating_communication: 'Direkte Kommunikation',
|
||||||
|
rating_awareness: 'Entwicklung und Stimulierung des Bewusstseins',
|
||||||
|
rating_progress: 'Fortschritts- und Verantwortungsmanagement'
|
||||||
},
|
},
|
||||||
agreementText: 'Folgendes habe ich gelesen und erkläre mich damit einverstanden: Benutzervereinbarung,',
|
agreementText: 'Folgendes habe ich gelesen und erkläre mich damit einverstanden: Benutzervereinbarung,',
|
||||||
userAgreement: 'Benutzervereinbarung',
|
userAgreement: 'Benutzervereinbarung',
|
||||||
|
@ -167,6 +185,10 @@ export default {
|
||||||
sessionWishes: 'Schreiben Sie Ihre Wünsche zur Sitzung',
|
sessionWishes: 'Schreiben Sie Ihre Wünsche zur Sitzung',
|
||||||
successPayment: 'Erfolgreiche Zahlung',
|
successPayment: 'Erfolgreiche Zahlung',
|
||||||
errorPayment: 'Zahlungsfehler',
|
errorPayment: 'Zahlungsfehler',
|
||||||
|
chat: {
|
||||||
|
join: 'Chat starten',
|
||||||
|
joinAI: 'Start AI chat'
|
||||||
|
},
|
||||||
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',
|
||||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
||||||
joinParticipant: 'Join as a participant',
|
joinParticipant: 'Join as a participant',
|
||||||
rapport: 'Rapport',
|
rapport: 'Rapport',
|
||||||
invite: 'Invite',
|
invite: 'Invite',
|
||||||
save: 'Save room'
|
save: 'Save room',
|
||||||
|
rate: 'Rate',
|
||||||
|
tellAboutReason: 'Tell us what happened',
|
||||||
|
rating_raport: 'Rapport',
|
||||||
|
rating_position_and_presence: 'Coaching position or coaching presence',
|
||||||
|
rating_balance_and_frustration: 'Balance of support and frustration',
|
||||||
|
rating_agreement: 'Creating a coaching agreement (session contract)',
|
||||||
|
rating_planning_and_goals: 'Planning and goal setting',
|
||||||
|
rating_reality: 'Clarifying reality',
|
||||||
|
rating_opportunities: 'New opportunities found',
|
||||||
|
rating_action_plan: 'An action plan has been drawn up',
|
||||||
|
rating_motivation: 'Sources of motivation found',
|
||||||
|
rating_next_session_stretch: 'There is a stretch for the next session',
|
||||||
|
rating_relationship: 'Establishing a trusting relationship with the client',
|
||||||
|
rating_listening: 'Deep, active listening',
|
||||||
|
rating_questions: 'Using "strong" questions',
|
||||||
|
rating_communication: 'Direct communication',
|
||||||
|
rating_awareness: 'Developing and stimulating awareness',
|
||||||
|
rating_progress: 'Progress and Responsibility Management'
|
||||||
},
|
},
|
||||||
agreementText: 'I have read and agree with the terms of the User Agreement,',
|
agreementText: 'I have read and agree with the terms of the User Agreement,',
|
||||||
userAgreement: 'User Agreement',
|
userAgreement: 'User Agreement',
|
||||||
|
@ -167,6 +185,10 @@ export default {
|
||||||
sessionWishes: 'Write your wishes about the session',
|
sessionWishes: 'Write your wishes about the session',
|
||||||
successPayment: 'Successful Payment',
|
successPayment: 'Successful Payment',
|
||||||
errorPayment: 'Payment Error',
|
errorPayment: 'Payment Error',
|
||||||
|
chat: {
|
||||||
|
join: 'Start chat',
|
||||||
|
joinAI: 'Start AI chat'
|
||||||
|
},
|
||||||
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',
|
||||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
||||||
joinParticipant: 'Unirse como participante',
|
joinParticipant: 'Unirse como participante',
|
||||||
rapport: 'Buena relación',
|
rapport: 'Buena relación',
|
||||||
invite: 'Invitar',
|
invite: 'Invitar',
|
||||||
save: 'Guardar sala'
|
save: 'Guardar sala',
|
||||||
|
rate: 'Valorar',
|
||||||
|
tellAboutReason: 'Cuéntanos qué ha pasado',
|
||||||
|
rating_raport: 'Buena relación',
|
||||||
|
rating_position_and_presence: 'Puesto de coach o presencia de coach',
|
||||||
|
rating_balance_and_frustration: 'Equilibrio entre apoyo y frustración',
|
||||||
|
rating_agreement: 'Crear un acuerdo de coaching (contrato de sesión)',
|
||||||
|
rating_planning_and_goals: 'Planear y establecer los objetivos',
|
||||||
|
rating_reality: 'Clarificar la realidad',
|
||||||
|
rating_opportunities: 'Nuevas oportunidades encontradas',
|
||||||
|
rating_action_plan: 'Se ha diseñado un plan de acción',
|
||||||
|
rating_motivation: 'Fuentes de motivación encontradas',
|
||||||
|
rating_next_session_stretch: 'Queda un poco para la siguiente sesión',
|
||||||
|
rating_relationship: 'Establecer una relación de confianza con el cliente',
|
||||||
|
rating_listening: 'Escucha activa y profunda',
|
||||||
|
rating_questions: 'Usar preguntas "contundentes"',
|
||||||
|
rating_communication: 'Comunicación directa',
|
||||||
|
rating_awareness: 'Desarrollar y estimular la conciencia',
|
||||||
|
rating_progress: 'Progreso y gestión de la responsabilidad'
|
||||||
},
|
},
|
||||||
agreementText: 'He leído y acepto las condiciones del Acuerdo de usuario,',
|
agreementText: 'He leído y acepto las condiciones del Acuerdo de usuario,',
|
||||||
userAgreement: 'Acuerdo de usuario',
|
userAgreement: 'Acuerdo de usuario',
|
||||||
|
@ -167,6 +185,10 @@ export default {
|
||||||
sessionWishes: 'Escribe tus deseos sobre la sesión',
|
sessionWishes: 'Escribe tus deseos sobre la sesión',
|
||||||
successPayment: 'Pago Exitoso',
|
successPayment: 'Pago Exitoso',
|
||||||
errorPayment: 'Error de Pago',
|
errorPayment: 'Error de Pago',
|
||||||
|
chat: {
|
||||||
|
join: 'Empezar un chat',
|
||||||
|
joinAI: 'Start AI chat'
|
||||||
|
},
|
||||||
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',
|
||||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
||||||
joinParticipant: 'Rejoindre en tant que participant',
|
joinParticipant: 'Rejoindre en tant que participant',
|
||||||
rapport: 'Rapport',
|
rapport: 'Rapport',
|
||||||
invite: 'Inviter',
|
invite: 'Inviter',
|
||||||
save: 'Sauvegarder la salle'
|
save: 'Sauvegarder la salle',
|
||||||
|
rate: 'Noter',
|
||||||
|
tellAboutReason: 'Dites-nous ce qui s\'est passé',
|
||||||
|
rating_raport: 'Rapport',
|
||||||
|
rating_position_and_presence: 'Poste de coach ou présence de coach',
|
||||||
|
rating_balance_and_frustration: 'Équilibre entre assistance et frustration',
|
||||||
|
rating_agreement: 'Création d\'un contrat de coaching (contrat de séance)',
|
||||||
|
rating_planning_and_goals: 'Planification et définition des objectifs',
|
||||||
|
rating_reality: 'Clarification de la réalité',
|
||||||
|
rating_opportunities: 'Nouvelles opportunités trouvées',
|
||||||
|
rating_action_plan: 'Un plan d\'action a été établi',
|
||||||
|
rating_motivation: 'Sources de motivation trouvées',
|
||||||
|
rating_next_session_stretch: 'Une période est présente pour la prochaine session',
|
||||||
|
rating_relationship: 'Établissement d\'une relation de confiance avec le client',
|
||||||
|
rating_listening: 'Écoute approfondie et active',
|
||||||
|
rating_questions: 'Utilisation de questions «fortes»',
|
||||||
|
rating_communication: 'Communication directe',
|
||||||
|
rating_awareness: 'Développement et stimulation de la prise de conscience',
|
||||||
|
rating_progress: 'Gestion de la progression et de la responsabilité'
|
||||||
},
|
},
|
||||||
agreementText: 'J\'ai lu et j\'accepte les dispositions de l\'Accord Utilisateur et de la',
|
agreementText: 'J\'ai lu et j\'accepte les dispositions de l\'Accord Utilisateur et de la',
|
||||||
userAgreement: '',
|
userAgreement: '',
|
||||||
|
@ -167,6 +185,10 @@ export default {
|
||||||
sessionWishes: 'Écrivez vos souhaits concernant la session',
|
sessionWishes: 'Écrivez vos souhaits concernant la session',
|
||||||
successPayment: 'Paiement Réussi',
|
successPayment: 'Paiement Réussi',
|
||||||
errorPayment: 'Erreur de Paiement',
|
errorPayment: 'Erreur de Paiement',
|
||||||
|
chat: {
|
||||||
|
join: 'Commencer la discussion',
|
||||||
|
joinAI: 'Start AI chat'
|
||||||
|
},
|
||||||
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',
|
||||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
||||||
joinParticipant: 'Partecipa come partecipante',
|
joinParticipant: 'Partecipa come partecipante',
|
||||||
rapport: 'Rapporto',
|
rapport: 'Rapporto',
|
||||||
invite: 'Invita',
|
invite: 'Invita',
|
||||||
save: 'Salva sala'
|
save: 'Salva sala',
|
||||||
|
rate: 'Valuta',
|
||||||
|
tellAboutReason: 'Descrivi cosa è successo',
|
||||||
|
rating_raport: 'Rapporto',
|
||||||
|
rating_position_and_presence: 'Posizione di coaching o presenza di coaching',
|
||||||
|
rating_balance_and_frustration: 'Equilibrio tra sostegno e frustrazione',
|
||||||
|
rating_agreement: 'Creazione di un accordo di coaching (contratto di sessione)',
|
||||||
|
rating_planning_and_goals: 'Pianificazione e definizione di obiettivi',
|
||||||
|
rating_reality: 'Chiarimento della realtà',
|
||||||
|
rating_opportunities: 'Nuove opportunità trovate',
|
||||||
|
rating_action_plan: 'È stato elaborato un piano d\'azione',
|
||||||
|
rating_motivation: 'Fonti di motivazione trovate',
|
||||||
|
rating_next_session_stretch: 'Esiste un\'estensione per la prossima sessione',
|
||||||
|
rating_relationship: 'Instaurazione di un rapporto di fiducia con il cliente',
|
||||||
|
rating_listening: 'Ascolto profondo e attivo',
|
||||||
|
rating_questions: 'Utilizzo di domande "forti"',
|
||||||
|
rating_communication: 'Comunicazione diretta',
|
||||||
|
rating_awareness: 'Sviluppo e stimolo della consapevolezza',
|
||||||
|
rating_progress: 'Gestione dei progressi e delle responsabilità'
|
||||||
},
|
},
|
||||||
agreementText: 'Ho letto e accetto i termini dell\'Accordo con l\'utente,',
|
agreementText: 'Ho letto e accetto i termini dell\'Accordo con l\'utente,',
|
||||||
userAgreement: '',
|
userAgreement: '',
|
||||||
|
@ -167,6 +185,10 @@ export default {
|
||||||
sessionWishes: 'Scrivi i tuoi desideri riguardo alla sessione',
|
sessionWishes: 'Scrivi i tuoi desideri riguardo alla sessione',
|
||||||
successPayment: 'Pagamento Riuscito',
|
successPayment: 'Pagamento Riuscito',
|
||||||
errorPayment: 'Errore di Pagamento',
|
errorPayment: 'Errore di Pagamento',
|
||||||
|
chat: {
|
||||||
|
join: 'Avvia chat',
|
||||||
|
joinAI: 'Start AI chat'
|
||||||
|
},
|
||||||
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',
|
||||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
||||||
joinParticipant: 'Присоединиться как участник',
|
joinParticipant: 'Присоединиться как участник',
|
||||||
rapport: 'Раппорт',
|
rapport: 'Раппорт',
|
||||||
invite: 'Пригласить',
|
invite: 'Пригласить',
|
||||||
save: 'Сохранить комнату'
|
save: 'Сохранить комнату',
|
||||||
|
rate: 'Оценить',
|
||||||
|
tellAboutReason: 'Расскажите, что произошло',
|
||||||
|
rating_raport: 'Раппорт',
|
||||||
|
rating_position_and_presence: 'Коуч-позиция или коучинговое присутствие',
|
||||||
|
rating_balance_and_frustration: 'Баланс поддержки и фрустрации',
|
||||||
|
rating_agreement: 'Создание коучингового соглашения (контракт на сессию)',
|
||||||
|
rating_planning_and_goals: 'Планирование и постановка целей',
|
||||||
|
rating_reality: 'Прояснение реальности',
|
||||||
|
rating_opportunities: 'Найдены новые возможности',
|
||||||
|
rating_action_plan: 'Составлен план действий',
|
||||||
|
rating_motivation: 'Найдены источники мотивации',
|
||||||
|
rating_next_session_stretch: 'Есть "протяжка" на следующую сессию',
|
||||||
|
rating_relationship: 'Установление доверительных отношений с клиентом',
|
||||||
|
rating_listening: 'Глубокое активное слушание',
|
||||||
|
rating_questions: 'Использование сильных вопросов',
|
||||||
|
rating_communication: 'Прямая коммуникация',
|
||||||
|
rating_awareness: 'Развитие и стимулирование осознанности',
|
||||||
|
rating_progress: 'Управление прогрессом и ответственностью'
|
||||||
},
|
},
|
||||||
agreementText: 'Я прочитал и согласен с условиями Пользовательского соглашения,',
|
agreementText: 'Я прочитал и согласен с условиями Пользовательского соглашения,',
|
||||||
userAgreement: 'Пользовательского соглашения',
|
userAgreement: 'Пользовательского соглашения',
|
||||||
|
@ -167,6 +185,10 @@ export default {
|
||||||
sessionWishes: 'Напишите свои пожелания по поводу сессии',
|
sessionWishes: 'Напишите свои пожелания по поводу сессии',
|
||||||
successPayment: 'Успешная оплата',
|
successPayment: 'Успешная оплата',
|
||||||
errorPayment: 'Ошибка оплаты',
|
errorPayment: 'Ошибка оплаты',
|
||||||
|
chat: {
|
||||||
|
join: 'Начать чат',
|
||||||
|
joinAI: 'Начать чат с ИИ'
|
||||||
|
},
|
||||||
errors: {
|
errors: {
|
||||||
invalidEmail: 'Адрес электронной почты недействителен',
|
invalidEmail: 'Адрес электронной почты недействителен',
|
||||||
emptyEmail: 'Пожалуйста, введите ваш E-mail',
|
emptyEmail: 'Пожалуйста, введите ваш E-mail',
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
|
||||||
|
import { IHttpConnectionOptions } from "@microsoft/signalr/src/IHttpConnectionOptions";
|
||||||
|
import { AUTH_TOKEN_KEY, BASE_URL } from '../constants/common';
|
||||||
|
import { IChatMessage } from '../types/chat';
|
||||||
|
|
||||||
|
const chatMessageMethodName = 'ReceiveMessage';
|
||||||
|
const chatUserStatusChangeMethodName = 'chatUserStatusChange';
|
||||||
|
const chatSeenMethodName = 'MessageWasRead';
|
||||||
|
const chatTypingMethodName = 'directTyping';
|
||||||
|
const sendChatTextMethodName = 'SendMessage';
|
||||||
|
const sendChatTypingMethodName = 'DirectIsTyping';
|
||||||
|
const serverError = 'Error';
|
||||||
|
|
||||||
|
const sendChatSeenMethodName = 'ConfirmMessageRead';
|
||||||
|
const sendChatMessagesSeenMethodName = 'ConfirmMessagesRead';
|
||||||
|
|
||||||
|
|
||||||
|
const joinChatMethodName = 'JoinChat';
|
||||||
|
const leaveChatMethodName = 'LeaveChat';
|
||||||
|
|
||||||
|
const joinChatsMethodName = 'JoinChats';
|
||||||
|
const leaveChatsMethodName = 'LeaveChats';
|
||||||
|
const chatsReceiveMessageMethodName = 'ChatsMessageCreated';
|
||||||
|
|
||||||
|
class SignalConnector {
|
||||||
|
private connection: HubConnection;
|
||||||
|
private events ={} as any;
|
||||||
|
static instance: SignalConnector;
|
||||||
|
constructor() {
|
||||||
|
const options = {
|
||||||
|
accessTokenFactory: () => localStorage.getItem(AUTH_TOKEN_KEY)
|
||||||
|
} as IHttpConnectionOptions;
|
||||||
|
this.connection = new HubConnectionBuilder()
|
||||||
|
.withUrl(`${BASE_URL}/hubs/chat`, options)
|
||||||
|
.withAutomaticReconnect()
|
||||||
|
.configureLogging(LogLevel.Debug)
|
||||||
|
.build();
|
||||||
|
this.connection.start().then(()=>{
|
||||||
|
this.events?.onConnected(true)
|
||||||
|
for (const k in this.events) {
|
||||||
|
if (k != 'onConnected'){
|
||||||
|
this.connection.on(k, (payload: any) => {
|
||||||
|
this.events[k](payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(err => console.log('SignalR Error', err));
|
||||||
|
}
|
||||||
|
public addListener = (name: string, func: any)=> {
|
||||||
|
this.events[name] = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeConnection = () => {
|
||||||
|
this.connection.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public newMessage = (message: IChatMessage) => {
|
||||||
|
this.connection.invoke(sendChatTextMethodName, message).then(x => console.log('NewMsg',x))
|
||||||
|
}
|
||||||
|
public joinChat = (groupId: number) => {
|
||||||
|
this.connection.invoke(joinChatMethodName, groupId, null).then(x => console.log(joinChatMethodName, x))
|
||||||
|
}
|
||||||
|
|
||||||
|
public joinChatPerson = (accId: number) => {
|
||||||
|
return this.connection.invoke(joinChatMethodName, 0, accId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public readMessages = (messagesId: number[]) => {
|
||||||
|
this.connection.invoke(sendChatMessagesSeenMethodName, messagesId).then(x => console.log(sendChatMessagesSeenMethodName, x))
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): SignalConnector {
|
||||||
|
if (!SignalConnector.instance)
|
||||||
|
SignalConnector.instance = new SignalConnector();
|
||||||
|
return SignalConnector.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SignalConnector.getInstance;
|
|
@ -823,6 +823,7 @@ a {
|
||||||
flex: 0 0 100%;
|
flex: 0 0 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.btn-apply,
|
.btn-apply,
|
||||||
.btn-video {
|
.btn-video {
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__inner {
|
&__inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
height: 0;
|
height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -94,7 +96,8 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: calc(100% - 136px);
|
resize: none;
|
||||||
|
width: calc(100% - 40px);
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__comment__content {
|
&__comment__content, &__report__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 44px 40px;
|
padding: 44px 40px;
|
||||||
|
|
|
@ -57,6 +57,30 @@
|
||||||
line-height: 120%;
|
line-height: 120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__report-list {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
width: 100%;
|
||||||
|
color: #4E7C86;
|
||||||
|
@include rem(13);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 120%;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_divider {
|
||||||
|
flex: 1;
|
||||||
|
border-bottom: 1px solid #E4F5FA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__comments {
|
&__comments {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -14,4 +14,25 @@
|
||||||
color: #c4dfe6 !important;
|
color: #c4dfe6 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&_title {
|
||||||
|
color: #2C7873;
|
||||||
|
@include rem(13);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 120%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
export interface IChatMessage {
|
||||||
|
GroupId: string;
|
||||||
|
CreatorId: number;
|
||||||
|
Content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChatJoin {
|
||||||
|
GroupId: string;
|
||||||
|
CreatorId: number;
|
||||||
|
Content: string;
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ export type ThemeGroup = {
|
||||||
|
|
||||||
export interface ExpertItem {
|
export interface ExpertItem {
|
||||||
id: number;
|
id: number;
|
||||||
|
botUserId?: number;
|
||||||
name: string;
|
name: string;
|
||||||
surname?: string;
|
surname?: string;
|
||||||
faceImageUrl?: string;
|
faceImageUrl?: string;
|
||||||
|
|
|
@ -42,3 +42,16 @@ export type RoomEditDTO = {
|
||||||
tags?: Tag[];
|
tags?: Tag[];
|
||||||
availableSlots: Slot[];
|
availableSlots: Slot[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Report = {
|
||||||
|
evaluationCriteriaId: number,
|
||||||
|
evaluationCriteriaName?: string,
|
||||||
|
score?: number,
|
||||||
|
key?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReportData = {
|
||||||
|
sessionId: number,
|
||||||
|
sessionSupervisorScores: Report[],
|
||||||
|
supervisorComment?: string
|
||||||
|
};
|
||||||
|
|
|
@ -5,8 +5,7 @@ import { i18nText } from '../i18nKeys';
|
||||||
const ROUTES = ['sessions', 'rooms', 'notifications', 'support', 'information', 'settings', 'messages', 'expert-profile'];
|
const ROUTES = ['sessions', 'rooms', 'notifications', 'support', 'information', 'settings', 'messages', 'expert-profile'];
|
||||||
const COUNTS: Record<string, number> = {
|
const COUNTS: Record<string, number> = {
|
||||||
sessions: 12,
|
sessions: 12,
|
||||||
notifications: 5,
|
notifications: 5
|
||||||
messages: 113
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMenuConfig = (locale: string) => ROUTES.map((path) => ({
|
export const getMenuConfig = (locale: string) => ROUTES.map((path) => ({
|
||||||
|
|
Loading…
Reference in New Issue