Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
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_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc
|
||||
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",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 4200",
|
||||
|
@ -13,6 +13,7 @@
|
|||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/nextjs-registry": "^1.0.0",
|
||||
"@contentful/rich-text-react-renderer": "^15.22.9",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@stripe/react-stripe-js": "^2.7.3",
|
||||
"@stripe/stripe-js": "^4.1.0",
|
||||
"agora-rtc-react": "2.1.0",
|
||||
|
@ -28,6 +29,7 @@
|
|||
"next-intl": "^3.3.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-signalr": "^0.2.24",
|
||||
"react-slick": "^0.29.0",
|
||||
"react-stripe-js": "^1.1.5",
|
||||
"slick-carousel": "^1.8.1",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"package_name": "com.bbuddy.whistle",
|
||||
"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",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
} catch (err) {
|
||||
// const {
|
||||
// response: {
|
||||
// status: responseCode = null,
|
||||
// statusText = '',
|
||||
// data: { message = '', status: errorKey = '' } = {},
|
||||
// } = {},
|
||||
// code: statusCode = '',
|
||||
// } = err as AxiosError;
|
||||
//
|
||||
// throw new Error(
|
||||
// JSON.stringify({
|
||||
// statusCode,
|
||||
// statusMessage: message || statusText,
|
||||
// responseCode,
|
||||
// errorKey,
|
||||
// }),
|
||||
// );
|
||||
const {
|
||||
response: {
|
||||
status: responseCode = null,
|
||||
statusText = '',
|
||||
data,
|
||||
} = {},
|
||||
code: statusCode = '',
|
||||
} = err as AxiosError;
|
||||
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
statusCode,
|
||||
statusMessage: statusText,
|
||||
responseCode,
|
||||
details: data
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,12 +5,14 @@ import { useLocalStorage } from '../../hooks/useLocalStorage';
|
|||
import {AUTH_TOKEN_KEY} from '../../constants/common';
|
||||
import {Room} from '../../types/rooms';
|
||||
import {getRoomDetails} from '../rooms';
|
||||
import {SessionState} from "../../types/sessions";
|
||||
|
||||
export const useRoomDetails = (locale: string, roomId: number) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const [room, setRoom] = useState<Room>();
|
||||
const [errorData, setErrorData] = useState<any>();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [isStarted, setIsStarted] = useState(false);
|
||||
|
||||
const fetchData = useCallback(() => {
|
||||
setLoading(true);
|
||||
|
@ -33,10 +35,17 @@ export const useRoomDetails = (locale: string, roomId: number) => {
|
|||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (room?.state === SessionState.STARTED) {
|
||||
setIsStarted(true);
|
||||
}
|
||||
}, [room?.state])
|
||||
|
||||
return {
|
||||
fetchData,
|
||||
loading,
|
||||
room,
|
||||
errorData
|
||||
errorData,
|
||||
isStarted
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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({
|
||||
url: '/home/upcomingsessionsall',
|
||||
|
@ -107,3 +107,19 @@ export const getRoomById = (locale: string, token: string, id: number): Promise<
|
|||
locale,
|
||||
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 { unstable_setRequestLocale } from 'next-intl/server';
|
||||
import { Link } from '../../../../../../navigation';
|
||||
import { i18nText } from '../../../../../../i18nKeys';
|
||||
|
||||
import {ChatMessages} from "../../../../../../components/Chat/ChatMessages";
|
||||
/*
|
||||
export function generateStaticParams({
|
||||
params: { locale },
|
||||
}: { params: { locale: string } }) {
|
||||
|
@ -15,56 +16,22 @@ export function generateStaticParams({
|
|||
|
||||
return result;
|
||||
}
|
||||
*
|
||||
*/
|
||||
|
||||
export default function Message({ params }: { params: { locale: string, textId: string } }) {
|
||||
unstable_setRequestLocale(params.locale);
|
||||
export default function Message({ params: { locale, textId } }: { params: { locale: string, textId: string } }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link href={'/account/messages' as any}>
|
||||
{i18nText('accountMenu.messages', params.locale)}
|
||||
{i18nText('accountMenu.messages', locale)}
|
||||
</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active" aria-current="page">{`Person ${params.textId}`}</li>
|
||||
</ol>
|
||||
|
||||
<div className="b-message">
|
||||
<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>
|
||||
<ChatMessages locale={locale} groupId={parseInt(textId)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
'use client'
|
||||
import React, { Suspense } from 'react';
|
||||
import { unstable_setRequestLocale } from 'next-intl/server';
|
||||
import { Link } from '../../../../../navigation';
|
||||
import { CustomInput } from '../../../../../components/view/CustomInput';
|
||||
import { i18nText } from '../../../../../i18nKeys';
|
||||
import {ChatList} from "../../../../../components/Chat/ChatList";
|
||||
|
||||
export default function Messages({ params: { locale } }: { params: { locale: string } }) {
|
||||
unstable_setRequestLocale(locale);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ol className="breadcrumb">
|
||||
|
@ -15,74 +13,7 @@ export default function Messages({ params: { locale } }: { params: { locale: str
|
|||
<Suspense>
|
||||
<CustomInput placeholder={i18nText('name', locale)} />
|
||||
</Suspense>
|
||||
<div className="messages-session">
|
||||
<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>
|
||||
<ChatList locale={locale}/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,21 +4,44 @@ import { Button } from 'antd';
|
|||
import { useSelectedLayoutSegment, usePathname } from 'next/navigation';
|
||||
import { Link } from '../../navigation';
|
||||
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 { getMenuConfig } from '../../utils/account';
|
||||
import {useEffect, useState} from "react";
|
||||
import {getChatList} from "../../actions/chat/groups";
|
||||
|
||||
export const AccountMenu = ({ locale }: { locale: string }) => {
|
||||
const selectedLayoutSegment = useSelectedLayoutSegment();
|
||||
const pathname = selectedLayoutSegment || '';
|
||||
const paths = usePathname();
|
||||
const menu: { path: string, title: string, count?: number }[] = getMenuConfig(locale);
|
||||
const [counts, setCounts] = useState<any>({});
|
||||
|
||||
const onLogout = () => {
|
||||
deleteStorageKey(AUTH_TOKEN_KEY);
|
||||
deleteStorageKey(AUTH_USER);
|
||||
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 (
|
||||
<ul className="list-sidebar">
|
||||
|
@ -26,8 +49,8 @@ export const AccountMenu = ({ locale }: { locale: string }) => {
|
|||
<li key={path} className="list-sidebar__item">
|
||||
<Link href={`/account/${path}` as any} className={path === pathname ? 'active' : ''}>
|
||||
{title}
|
||||
{count ? (
|
||||
<span className="count">{count}</span>
|
||||
{getterCount(path, count) ? (
|
||||
<span className="count">{getterCount(path, count)}</span>
|
||||
) : null}
|
||||
</Link>
|
||||
</li>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { RoomsType } from '../../../types/rooms';
|
||||
import { useSessionTracking } from '../../../actions/hooks/useSessionTracking';
|
||||
import { AccountMenu } from '../AccountMenu';
|
||||
|
@ -8,6 +8,9 @@ import { Loader } from '../../view/Loader';
|
|||
import { RoomDetailsContent } from './RoomDetailsContent';
|
||||
import { useRoomDetails } from '../../../actions/hooks/useRoomDetails';
|
||||
import { AgoraClientGroup } from '../agora';
|
||||
import { SupervisorReportModal } from '../../Modals/SupervisorReportModal';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
import { AUTH_USER } from '../../../constants/common';
|
||||
|
||||
type RoomDetailsProps = {
|
||||
locale: string;
|
||||
|
@ -16,9 +19,13 @@ type 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 [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(() => {
|
||||
if (isCalling) {
|
||||
|
@ -28,6 +35,12 @@ export const RoomDetails = ({ roomId, locale, activeType }: RoomDetailsProps) =>
|
|||
}
|
||||
}, [isCalling]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSupervisor && isStarted) {
|
||||
setIsOpenReport(true);
|
||||
}
|
||||
}, [isStarted]);
|
||||
|
||||
const stopCalling = () => {
|
||||
setIsCalling(false);
|
||||
fetchData();
|
||||
|
@ -60,6 +73,15 @@ export const RoomDetails = ({ roomId, locale, activeType }: RoomDetailsProps) =>
|
|||
/>
|
||||
</Loader>
|
||||
</div>
|
||||
{isSupervisor && room?.id && (
|
||||
<SupervisorReportModal
|
||||
open={isOpenReport}
|
||||
handleCancel={() => setIsOpenReport(false)}
|
||||
locale={locale}
|
||||
refresh={fetchData}
|
||||
roomId={room.id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
'use client'
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button, notification, Tag } from 'antd';
|
||||
import { DeleteOutlined, LeftOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from '../../../navigation';
|
||||
import { Room, RoomsType } from '../../../types/rooms';
|
||||
import { Report, Room, RoomsType } from '../../../types/rooms';
|
||||
import { i18nText } from '../../../i18nKeys';
|
||||
import { LinkButton } from '../../view/LinkButton';
|
||||
import {
|
||||
|
@ -15,7 +15,8 @@ import {
|
|||
becomeRoomClient,
|
||||
becomeRoomSupervisor,
|
||||
deleteRoomClient,
|
||||
deleteRoomSupervisor
|
||||
deleteRoomSupervisor,
|
||||
getReport
|
||||
} from '../../../actions/rooms';
|
||||
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../constants/common';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
|
@ -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 isTimeBeforeStart = room?.scheduledStartAtUtc ? dayjs() < dayjs(room.scheduledStartAtUtc) : 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}`);
|
||||
|
||||
|
@ -248,6 +259,17 @@ export const RoomDetailsContent = ({ room, startRoom, locale, activeType, refres
|
|||
{room?.supervisorComment && (
|
||||
<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 && (
|
||||
|
|
|
@ -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 { ScheduleModal } from '../Modals/ScheduleModal';
|
||||
import { ScheduleModalResult } from '../Modals/ScheduleModalResult';
|
||||
import SignalrConnection from '../../lib/signalr-connection';
|
||||
import { useRouter } from '../../navigation';
|
||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||
|
||||
type ExpertDetailsProps = {
|
||||
expert: ExpertDetails;
|
||||
|
@ -32,8 +35,33 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
|
|||
const { publicCoachDetails } = expert || {};
|
||||
const [showSchedulerModal, setShowSchedulerModal] = useState<boolean>(false);
|
||||
const [mode, setMode] = useState<'data' | 'time' | 'pay' | 'finish'>('data');
|
||||
const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] } } = expert || {};
|
||||
const isRus = locale === Locale.ru;
|
||||
const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] , id, botUserId} } = expert || {};
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const { joinChatPerson, addListener, closeConnection } = SignalrConnection();
|
||||
const router = useRouter();
|
||||
|
||||
const onConnected = (flag: boolean)=>{
|
||||
if (flag){
|
||||
console.log('here');
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
addListener('onConnected', onConnected);
|
||||
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) => {
|
||||
if (data?.startAtUtc && data?.tagId) {
|
||||
|
@ -44,7 +72,7 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
|
|||
} else {
|
||||
setShowSchedulerModal(false);
|
||||
const showAuth = new Event('show_auth_enter');
|
||||
document.dispatchEvent(showAuth);
|
||||
document?.dispatchEvent(showAuth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,13 +82,6 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
|
|||
setMode('pay');
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('show_pay_form', handleShowPayForm);
|
||||
return () => {
|
||||
document.removeEventListener('show_pay_form', handleShowPayForm);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onSchedulerHandle = () => {
|
||||
setMode('data');
|
||||
setShowSchedulerModal(true);
|
||||
|
@ -86,11 +107,16 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{jwt && (
|
||||
<div className="expert-card__wrap-btn">
|
||||
<Button className="btn-apply" onClick={onSchedulerHandle}>
|
||||
<img src="/images/calendar-outline.svg" className="" alt="" />
|
||||
{i18nText('schedule', locale)}
|
||||
<Button className="btn-apply" onClick={() => handleJoinChat(id)}>
|
||||
{i18nText('chat.join', locale)}
|
||||
</Button>
|
||||
{botUserId && (
|
||||
<Button className="btn-apply" onClick={() => handleJoinChat(botUserId)}>
|
||||
{i18nText('chat.joinAI', locale)}
|
||||
</Button>
|
||||
)}
|
||||
{/*
|
||||
<a href="#" className="btn-video">
|
||||
<img src="/images/videocam-outline.svg" className="" alt=""/>
|
||||
|
@ -98,6 +124,7 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
|
|||
</a>
|
||||
*/}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="expert-info">
|
||||
{/* <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',
|
||||
rapport: 'Rapport',
|
||||
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,',
|
||||
userAgreement: 'Benutzervereinbarung',
|
||||
|
@ -167,6 +185,10 @@ export default {
|
|||
sessionWishes: 'Schreiben Sie Ihre Wünsche zur Sitzung',
|
||||
successPayment: 'Erfolgreiche Zahlung',
|
||||
errorPayment: 'Zahlungsfehler',
|
||||
chat: {
|
||||
join: 'Chat starten',
|
||||
joinAI: 'Start AI chat'
|
||||
},
|
||||
errors: {
|
||||
invalidEmail: 'Die E-Mail-Adresse ist ungültig',
|
||||
emptyEmail: 'Bitte geben Sie Ihre E-Mail ein',
|
||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
|||
joinParticipant: 'Join as a participant',
|
||||
rapport: 'Rapport',
|
||||
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,',
|
||||
userAgreement: 'User Agreement',
|
||||
|
@ -167,6 +185,10 @@ export default {
|
|||
sessionWishes: 'Write your wishes about the session',
|
||||
successPayment: 'Successful Payment',
|
||||
errorPayment: 'Payment Error',
|
||||
chat: {
|
||||
join: 'Start chat',
|
||||
joinAI: 'Start AI chat'
|
||||
},
|
||||
errors: {
|
||||
invalidEmail: 'The email address is not valid',
|
||||
emptyEmail: 'Please enter your E-mail',
|
||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
|||
joinParticipant: 'Unirse como participante',
|
||||
rapport: 'Buena relación',
|
||||
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,',
|
||||
userAgreement: 'Acuerdo de usuario',
|
||||
|
@ -167,6 +185,10 @@ export default {
|
|||
sessionWishes: 'Escribe tus deseos sobre la sesión',
|
||||
successPayment: 'Pago Exitoso',
|
||||
errorPayment: 'Error de Pago',
|
||||
chat: {
|
||||
join: 'Empezar un chat',
|
||||
joinAI: 'Start AI chat'
|
||||
},
|
||||
errors: {
|
||||
invalidEmail: 'La dirección de correo electrónico no es válida',
|
||||
emptyEmail: 'Introduce tu correo electrónico',
|
||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
|||
joinParticipant: 'Rejoindre en tant que participant',
|
||||
rapport: 'Rapport',
|
||||
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',
|
||||
userAgreement: '',
|
||||
|
@ -167,6 +185,10 @@ export default {
|
|||
sessionWishes: 'Écrivez vos souhaits concernant la session',
|
||||
successPayment: 'Paiement Réussi',
|
||||
errorPayment: 'Erreur de Paiement',
|
||||
chat: {
|
||||
join: 'Commencer la discussion',
|
||||
joinAI: 'Start AI chat'
|
||||
},
|
||||
errors: {
|
||||
invalidEmail: 'L\'adresse e-mail n\'est pas valide',
|
||||
emptyEmail: 'Veuillez saisir votre e-mail',
|
||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
|||
joinParticipant: 'Partecipa come partecipante',
|
||||
rapport: 'Rapporto',
|
||||
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,',
|
||||
userAgreement: '',
|
||||
|
@ -167,6 +185,10 @@ export default {
|
|||
sessionWishes: 'Scrivi i tuoi desideri riguardo alla sessione',
|
||||
successPayment: 'Pagamento Riuscito',
|
||||
errorPayment: 'Errore di Pagamento',
|
||||
chat: {
|
||||
join: 'Avvia chat',
|
||||
joinAI: 'Start AI chat'
|
||||
},
|
||||
errors: {
|
||||
invalidEmail: 'L\'indirizzo e-mail non è valido',
|
||||
emptyEmail: 'Inserisci l\'e-mail',
|
||||
|
|
|
@ -65,7 +65,25 @@ export default {
|
|||
joinParticipant: 'Присоединиться как участник',
|
||||
rapport: 'Раппорт',
|
||||
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: 'Я прочитал и согласен с условиями Пользовательского соглашения,',
|
||||
userAgreement: 'Пользовательского соглашения',
|
||||
|
@ -167,6 +185,10 @@ export default {
|
|||
sessionWishes: 'Напишите свои пожелания по поводу сессии',
|
||||
successPayment: 'Успешная оплата',
|
||||
errorPayment: 'Ошибка оплаты',
|
||||
chat: {
|
||||
join: 'Начать чат',
|
||||
joinAI: 'Начать чат с ИИ'
|
||||
},
|
||||
errors: {
|
||||
invalidEmail: 'Адрес электронной почты недействителен',
|
||||
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%;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-direction: column;
|
||||
|
||||
.btn-apply,
|
||||
.btn-video {
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
}
|
||||
|
||||
&__inner {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
flex-grow: 1;
|
||||
height: 0;
|
||||
position: relative;
|
||||
|
@ -94,7 +96,8 @@
|
|||
align-items: center;
|
||||
|
||||
textarea {
|
||||
width: calc(100% - 136px);
|
||||
resize: none;
|
||||
width: calc(100% - 40px);
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border-radius: 0;
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__comment__content {
|
||||
&__comment__content, &__report__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 44px 40px;
|
||||
|
|
|
@ -57,6 +57,30 @@
|
|||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -14,4 +14,25 @@
|
|||
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 {
|
||||
id: number;
|
||||
botUserId?: number;
|
||||
name: string;
|
||||
surname?: string;
|
||||
faceImageUrl?: string;
|
||||
|
|
|
@ -42,3 +42,16 @@ export type RoomEditDTO = {
|
|||
tags?: Tag[];
|
||||
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 COUNTS: Record<string, number> = {
|
||||
sessions: 12,
|
||||
notifications: 5,
|
||||
messages: 113
|
||||
notifications: 5
|
||||
};
|
||||
|
||||
export const getMenuConfig = (locale: string) => ROUTES.map((path) => ({
|
||||
|
|
Loading…
Reference in New Issue