diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 2512de6b..41a05e5e 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -160,7 +160,8 @@ export default { access: 'Access', answers: 'Answers', answer: 'Answer', - webinars: "Webinars", + webinars: 'Webinars', + screensaves: 'Screen saves', 'webinars.screenSaves': 'Screen saves', 'menu.Webinars': 'Webinars', 'menu.NewWebinar': 'New webinar', @@ -401,7 +402,8 @@ export default { description_tooltip: 'The editor is WYSIWYG and includes formatting tools whilst retaining the ability to write markdown shortcuts inline and output plain Markdown.', ai_analysis_enable_label: 'AI Recording Analysis', - ai_analysis_enable_tooltip: 'Check this option to enable AI-powered analysis of user attention and satisfaction during the meeting', + ai_analysis_enable_tooltip: + 'Check this option to enable AI-powered analysis of user attention and satisfaction during the meeting', attributes: 'Attributes', new_course: 'New course', new_questionnaire: 'New Questionnaire', diff --git a/src/locales/fr-FR.ts b/src/locales/fr-FR.ts index ba1430b7..98ed27ed 100644 --- a/src/locales/fr-FR.ts +++ b/src/locales/fr-FR.ts @@ -257,7 +257,8 @@ export default { 'The editor is WYSIWYG and includes formatting tools whilst retaining the ability to write markdown shortcuts inline and output plain Markdown.', short_description: 'Short description', ai_analysis_enable_label: 'Analyse de l’enregistrement par IA', - ai_analysis_enable_tooltip: 'Cochez cette option pour activer l’analyse de l’attention et de la satisfaction des utilisateurs pendant la réunion', + ai_analysis_enable_tooltip: + 'Cochez cette option pour activer l’analyse de l’attention et de la satisfaction des utilisateurs pendant la réunion', description: 'Description', description_tooltip: 'The editor is WYSIWYG and includes formatting tools whilst retaining the ability to write markdown shortcuts inline and output plain Markdown.', diff --git a/src/locales/pl-PL.ts b/src/locales/pl-PL.ts index 190d2a8d..bc12dc14 100644 --- a/src/locales/pl-PL.ts +++ b/src/locales/pl-PL.ts @@ -171,8 +171,9 @@ export default { 'stationary_event.edit': 'Formularz Wydarzenia stacjonarne', 'menu.My Profile': 'Mój profil', 'menu.My Profile.My Profile': 'Mój profil', - Webinars: "Webinary", + Webinars: 'Webinary', 'webinars.screenSaves': 'Zapis Ekranu', + screensaves: 'Zapis Ekranu', stationary_event: 'Wydarzenie stacjonarne', 'menu.reset': 'reset', finished_at: 'Data zakończenia', @@ -240,9 +241,10 @@ export default { recording_will_be_deleted: 'Po tym czasie zostanie ono skasowane', engagement_rating: 'Ocena zaangażowania', ai_analysis_average: 'Średni wynik analizy AI dla całego nagrania {modelType}.', - ai_warn_video_buffer: "Video nagrania jest obecnie przetwarzane, zdjęcia podglądu nagrania na wykresie w poszczególnych przedziałach czasowych mogą być obecnie niedostępne. Spróbuj ponowanie za kilka minut.", - consultationFragment: "konsultacji", - webinarFragment: "webinaru", + ai_warn_video_buffer: + 'Video nagrania jest obecnie przetwarzane, zdjęcia podglądu nagrania na wykresie w poszczególnych przedziałach czasowych mogą być obecnie niedostępne. Spróbuj ponowanie za kilka minut.', + consultationFragment: 'konsultacji', + webinarFragment: 'webinaru', resolution: 'Rozdzielczość danych', listeners_engagement: 'Zaangażowanie słuchaczy (%)', detected_emotions: 'Wykryte emocje', @@ -386,7 +388,8 @@ export default { author_tutor: 'Autor / Nauczyciel', short_description: 'Krótki opis', ai_analysis_enable_label: 'Analiza nagrania AI', - ai_analysis_enable_tooltip: 'Zaznacz tę opcję aby włączyć analizę badania atencji oraz satystakcji użytkowników na spotkaniu', + ai_analysis_enable_tooltip: + 'Zaznacz tę opcję aby włączyć analizę badania atencji oraz satystakcji użytkowników na spotkaniu', summary: 'Podsumowanie', summary_tooltip: 'Dany edytor WYSIWYG zawiera narzędzia do formatowania, zachowując jednocześnie możliwość pisania Markdown z klawiatury oraz wyświetlania zwykłego Markdown.', diff --git a/src/pages/Consultations/components/EffectivenessAnalysis.tsx b/src/pages/Consultations/components/EffectivenessAnalysis.tsx index e7692465..c5320f02 100644 --- a/src/pages/Consultations/components/EffectivenessAnalysis.tsx +++ b/src/pages/Consultations/components/EffectivenessAnalysis.tsx @@ -6,7 +6,8 @@ import { createTableOrderObject, EMOTION_POOL, formatPercent, - getLabelColorByValue, getRatingLabelColorByValue, + getLabelColorByValue, + getRatingLabelColorByValue, } from '@/utils/utils'; import { Link } from '@@/exports'; import { @@ -91,7 +92,13 @@ const ValueTag = React.memo( return isNaN(num) ? '0.00' : num.toFixed(2); }, [value, isRaw]); - const color = useMemo(() => rating ? getRatingLabelColorByValue(parseFloat(displayValue)) : getLabelColorByValue(parseFloat(displayValue)), [displayValue]); + const color = useMemo( + () => + rating + ? getRatingLabelColorByValue(parseFloat(displayValue)) + : getLabelColorByValue(parseFloat(displayValue)), + [displayValue], + ); return ( @@ -127,8 +134,6 @@ export const EffectivenessAnalysis = ({ [modelType], ); - - const columns: ProColumns[] = useMemo( () => [ { diff --git a/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx b/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx index 4345e7cc..b55886e1 100644 --- a/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx +++ b/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx @@ -7,15 +7,17 @@ import type { } from '@/pages/Consultations/components/types'; import { getAnalyticsChartFrames, getModelAnalytics } from '@/services/escola-lms/consultations'; import { - ANALYSIS_COLORS, EmotionKey, formatRating, - getRatingLabelColorByValue + ANALYSIS_COLORS, + EmotionKey, + formatRating, + getRatingLabelColorByValue, } from '@/utils/utils'; +import { WarningOutlined } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import { Card, Col, Select, Space, Typography, message } from 'antd'; -import React, { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { FormattedMessage, Link, useParams, useSelectedRoutes } from 'umi'; -import {WarningOutlined} from "@ant-design/icons"; const { Text } = Typography; @@ -69,19 +71,18 @@ const SectionTitle = styled(Text)` `; const VideoScreenWarning = styled.div` - display: flex; - flex-direction: row; - align-items: center; - gap: 10px; + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; color: ${ANALYSIS_COLORS.orange}; `; const VideoScreenWarningText = styled(Text)` - margin: 0; - color: ${ANALYSIS_COLORS.orange}; + margin: 0; + color: ${ANALYSIS_COLORS.orange}; `; - const TIME_OPTIONS = [ { value: 15, label: }, { value: 30, label: }, @@ -207,12 +208,18 @@ const EffectivenessAnalysisDetails = () => { - {formatRating(analysisMeta?.rating || 0)} + + {formatRating(analysisMeta?.rating || 0)} + , + modelType: ( + + ), }} /> @@ -233,10 +240,10 @@ const EffectivenessAnalysisDetails = () => { {analysisMeta?.processing_video && ( - + - - + + )} {chartData && ( diff --git a/src/pages/Consultations/components/ScreenSaves.tsx b/src/pages/Consultations/components/ScreenSaves.tsx index 4d27ba2f..cde0d016 100644 --- a/src/pages/Consultations/components/ScreenSaves.tsx +++ b/src/pages/Consultations/components/ScreenSaves.tsx @@ -8,7 +8,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { FormattedMessage, useIntl } from 'umi'; import FilesBrowser from '@/components/FilesBrowser'; -import { sortArrayByKey } from '@/utils/utils'; +import type { SortAccessors } from '@/utils/utils'; +import { applySort } from '@/utils/utils'; const statusMap = { reported: 'warning', @@ -29,6 +30,24 @@ const ScreenSaves: React.FC = ({ consultation, webinar, webinarTimestamp const intl = useIntl(); const resourceId = consultation || webinar; + const consultationSorters: SortAccessors = useMemo( + () => ({ + consultation_term_id: (i) => i.consultation_term_id ?? 0, + user: (i) => `${i.user?.first_name ?? ''} ${i.user?.last_name ?? ''}`.toLowerCase(), + date: (i) => new Date(i.date).getTime(), + status: (i) => i.status ?? '', + }), + [], + ); + + const webinarSorters: SortAccessors = useMemo( + () => ({ + id: (i) => i.id ?? 0, + user: (i) => `${i.first_name ?? ''} ${i.last_name ?? ''}`.toLowerCase(), + }), + [], + ); + const consultationColumns = useMemo( (): ProColumns[] => [ { @@ -93,16 +112,13 @@ const ScreenSaves: React.FC = ({ consultation, webinar, webinarTimestamp title: , dataIndex: 'user', sorter: true, - render: (_, item) => - `${item.first_name ?? ''} ${item.last_name ?? ''} ${item.email ?? ''}`, + render: (_, item) => `${item.first_name ?? ''} ${item.last_name ?? ''} ${item.email ?? ''}`, }, { title: , dataIndex: 'active_to', render: () => - webinarTimestamp - ? moment.unix(webinarTimestamp).format('YYYY-MM-DD HH:mm') - : '-', + webinarTimestamp ? moment.unix(webinarTimestamp).format('YYYY-MM-DD HH:mm') : '-', }, { title: 'Analiza obrazu', @@ -164,23 +180,10 @@ const ScreenSaves: React.FC = ({ consultation, webinar, webinarTimestamp options={{ reload: false, }} - dataSource={appointments} request={async (_params, sort) => { - const sortArr = sort && Object.entries(sort)[0]; - let filteredData = appointments.filter((item) => item.status === 'approved'); - - if (sortArr) { - filteredData = sortArrayByKey( - filteredData, - sortArr[0], - sortArr[1] !== 'ascend', - ); - } - return { - data: filteredData, - total: filteredData.length, - success: true, - }; + const filtered = appointments.filter((item) => item.status === 'approved'); + const data = applySort(filtered, sort as any, consultationSorters); + return { data, total: data.length, success: true }; }} columns={consultationColumns} /> @@ -200,23 +203,9 @@ const ScreenSaves: React.FC = ({ consultation, webinar, webinarTimestamp options={{ reload: false, }} - dataSource={webinarUsers} request={async (_params, sort) => { - const sortArr = sort && Object.entries(sort)[0]; - let filteredData = [...webinarUsers]; - - if (sortArr) { - filteredData = sortArrayByKey( - filteredData, - sortArr[0], - sortArr[1] !== 'ascend', - ); - } - return { - data: filteredData, - total: filteredData.length, - success: true, - }; + const data = applySort(webinarUsers, sort as any, webinarSorters); + return { data, total: data.length, success: true }; }} columns={webinarColumns} /> diff --git a/src/pages/Consultations/form.tsx b/src/pages/Consultations/form.tsx index 2d6ecc90..57a75460 100644 --- a/src/pages/Consultations/form.tsx +++ b/src/pages/Consultations/form.tsx @@ -2,7 +2,8 @@ import ProCard from '@ant-design/pro-card'; import ProForm, { ProFormDatePicker, ProFormDigit, - ProFormSelect, ProFormSwitch, + ProFormSelect, + ProFormSwitch, ProFormText, } from '@ant-design/pro-form'; import { Alert, Button, Col, Row, Spin } from 'antd'; diff --git a/src/pages/Webinars/form.tsx b/src/pages/Webinars/form.tsx index b44601bb..f6069af6 100644 --- a/src/pages/Webinars/form.tsx +++ b/src/pages/Webinars/form.tsx @@ -7,22 +7,23 @@ import UserSelect from '@/components/UserSelect'; import UserSubmissions from '@/components/UsersSubmissions'; import WysiwygMarkdown from '@/components/WysiwygMarkdown'; import useValidateFormEdit from '@/hooks/useValidateFormEdit'; +import ScreenSaves from '@/pages/Consultations/components/ScreenSaves'; +import { settings } from '@/services/escola-lms/settings'; import { createWebinar, getWebinar, updateWebinar } from '@/services/escola-lms/webinars'; import { splitImagePath, tagsArrToIds } from '@/utils/utils'; import ProCard from '@ant-design/pro-card'; import ProForm, { ProFormDateTimePicker, - ProFormSelect, ProFormSwitch, + ProFormSelect, + ProFormSwitch, ProFormText, ProFormTextArea, } from '@ant-design/pro-form'; import { PageContainer } from '@ant-design/pro-layout'; import { Alert, Button, Col, Row, Spin, message } from 'antd'; +import moment from 'moment'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { FormattedMessage, history, useIntl, useParams } from 'umi'; -import ScreenSaves from "@/pages/Consultations/components/ScreenSaves"; -import {settings} from "@/services/escola-lms/settings"; -import moment from "moment"; enum TabNames { ATTRIBUTES = 'attributes', @@ -159,9 +160,7 @@ const WebinarForm = () => { }, { path: '/', - breadcrumbName: intl.formatMessage({ - id: String(data.name), - }), + breadcrumbName: String(data.name), }, { path: String(tab), @@ -189,6 +188,7 @@ const WebinarForm = () => { }, }} > + }> {manageCourseEdit.disableEdit && ( { key={TabNames.SCREENSAVES} tab={} > - + )} diff --git a/src/services/escola-lms/webinars.ts b/src/services/escola-lms/webinars.ts index 2a5c2c67..d52ed1bb 100644 --- a/src/services/escola-lms/webinars.ts +++ b/src/services/escola-lms/webinars.ts @@ -70,15 +70,9 @@ export async function generateYoutubeToken(body?: { email: string }, options?: A } /** GET /api/admin/webinars/:webinarId/users */ -export async function getWebinarUsers( - webinarId: number, - options?: AxiosRequestConfig, -) { - return request>( - `/api/admin/webinars/${webinarId}/users`, - { - method: 'GET', - ...(options || {}), - }, - ); +export async function getWebinarUsers(webinarId: number, options?: AxiosRequestConfig) { + return request>(`/api/admin/webinars/${webinarId}/users`, { + method: 'GET', + ...(options || {}), + }); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 77cef396..ab4e80c1 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -649,3 +649,29 @@ export const formatExpirationTime = (ms: number | null) => { const pad = (num: number) => num.toString().padStart(2, '0'); return hours > 0 ? `${hours}h ${pad(minutes)}m ${pad(seconds)}s` : `${minutes}m ${pad(seconds)}s`; }; + +export type SortableValue = string | number; +export type SortAccessors = Record SortableValue>; + +export function applySort( + data: T[], + sort: Record | undefined, + accessors: SortAccessors, +): T[] { + const sortArr = sort && Object.entries(sort)[0]; + if (!sortArr) return data; + + const [key, order] = sortArr; + const accessor = accessors[key]; + if (!accessor) return data; + + const asc = order === 'ascend'; + + return [...data].sort((a, b) => { + const valA = accessor(a); + const valB = accessor(b); + if (valA < valB) return asc ? -1 : 1; + if (valA > valB) return asc ? 1 : -1; + return 0; + }); +}