From eb4d7495793d33ddc8792e1b56958c7cedee4017 Mon Sep 17 00:00:00 2001 From: "mateusz.bieniek" Date: Fri, 20 Mar 2026 09:56:11 +0100 Subject: [PATCH 1/8] REK-155 Add effectiveness analysis component with simple data --- .../EffectivenessAnalysis/ModalForm.tsx | 183 +++++++ .../EffectivenessAnalysis/index.tsx | 229 +++++++++ src/locales/en-US.ts | 6 + src/locales/fr-FR.ts | 6 + src/locales/pl-PL.ts | 7 + src/pages/Consultations/index.tsx | 7 + src/utils/utils.ts | 16 + yarn.lock | 483 +++++++++++++++++- 8 files changed, 934 insertions(+), 3 deletions(-) create mode 100644 src/components/EffectivenessAnalysis/ModalForm.tsx create mode 100644 src/components/EffectivenessAnalysis/index.tsx diff --git a/src/components/EffectivenessAnalysis/ModalForm.tsx b/src/components/EffectivenessAnalysis/ModalForm.tsx new file mode 100644 index 000000000..f6122ee4a --- /dev/null +++ b/src/components/EffectivenessAnalysis/ModalForm.tsx @@ -0,0 +1,183 @@ +import ProForm, { ModalForm, ProFormDigit, ProFormRadio, ProFormText } from '@ant-design/pro-form'; +import { Form } from 'antd'; +import React, { useCallback, useEffect } from 'react'; +import { FormattedMessage, useIntl } from 'umi'; + +import { createOrUpdateField } from '@/services/escola-lms/fields'; +import JsonEditor from '../JsonEditor'; + +enum ModelFieldType { + BOOLEAN = 'boolean', + NUMBER = 'number', + VARCHAR = 'varchar', + TEXT = 'text', + JSON = 'json', +} + +export const ModelFieldsModalForm: React.FC<{ + class_type: string; + name: string | boolean; + visible: boolean; + onVisibleChange: (visible: boolean) => void; + onResponse: (metaField: EscolaLms.ModelFields.Models.Metadata) => void; + fields: EscolaLms.ModelFields.Models.Metadata[]; +}> = ({ visible, onVisibleChange, onResponse, class_type, name = 'new', fields = [] }) => { + const intl = useIntl(); + + const [form] = Form.useForm(); + + const onFinish = useCallback( + async (data: EscolaLms.ModelFields.Http.Requests.MetadataCreateOrUpdateRequest) => { + await createOrUpdateField({ + ...data, + class_type, + rules: JSON.stringify(data.rules), + extra: JSON.stringify(data.extra), + }).then((response) => { + if (response.success) { + onResponse(response.data); + } + }); + return false; + }, + [form], + ); + + useEffect(() => { + if (name !== 'new') { + const field = fields.find((record) => record.name === name); + if (field) { + form.setFieldsValue(field); + } + } else { + form.resetFields(); + } + }, [name, form, fields]); + + return ( + + } + tooltip={} + rules={[ + { + required: true, + }, + ]} + width="md" + name="name" + /> + + } + tooltip={} + options={(Object.keys(ModelFieldType) as (keyof typeof ModelFieldType)[]).map((key) => ({ + label: intl.formatMessage({ + id: `model.${ModelFieldType[key]}`, + }), + value: ModelFieldType[key], + }))} + /> + + } + tooltip={} + placeholder={intl.formatMessage({ + id: 'default', + })} + /> + + + {' '} + + + + + } + tooltip={} + valuePropName="value" + > + + + + {/** + * this int must be power of 2 + * example :// + const PUBLIC = 1 << 0; // 1 + const AUTHORIZED = 1 << 1; // 2 + const ADMIN = 1 << 2; // 4 + etc... + */} + } + tooltip={} + placeholder={intl.formatMessage({ + id: 'visibility', + defaultMessage: 'visibility', + })} + min={1} + max={1024} + fieldProps={{ step: 1 }} + rules={[ + { + validator: async (_, value) => { + if (Math.pow(2, Math.ceil(Math.log2(value))) - value) { + return Promise.reject( + new Error( + intl.formatMessage({ + id: 'notPowerOfTwo', + defaultMessage: 'notPowerOfTwo', + }), + ), + ); + } + return Promise.resolve(); + }, + }, + ]} + /> + + } + tooltip={} + valuePropName="value" + > + + + + ); +}; + +export default ModelFieldsModalForm; diff --git a/src/components/EffectivenessAnalysis/index.tsx b/src/components/EffectivenessAnalysis/index.tsx new file mode 100644 index 000000000..7913d743d --- /dev/null +++ b/src/components/EffectivenessAnalysis/index.tsx @@ -0,0 +1,229 @@ +import CategoryTree from '@/components/CategoryTree'; +import { DATETIME_FORMAT } from '@/consts/dates'; +import { consultations } from '@/services/escola-lms/consultations'; +import { createTableOrderObject, EMOTION_POOL, getLabelColorByValue } from '@/utils/utils'; +import { Link } from '@@/exports'; +import { + DownloadOutlined, + PlusOutlined, + ReloadOutlined, + SettingOutlined, + VerticalAlignMiddleOutlined, +} from '@ant-design/icons'; +import type { ActionType, ProColumns } from '@ant-design/pro-table'; +import ProTable from '@ant-design/pro-table'; +import { Button, Tag, Tooltip } from 'antd'; +import { format } from 'date-fns'; +import React, { Fragment, useRef, useState } from 'react'; +import styled from 'styled-components'; +import { FormattedMessage } from 'umi'; + +const StyledProTable = styled(ProTable)` + .ant-table-thead > tr > th::before { + display: none; + } +` as typeof ProTable; + +const StyledValueTag = styled(Tag)<{ $color: string }>` + color: ${(props) => props.$color}; + background-color: ${(props) => `${props.$color}33`}; + border: 1px solid ${(props) => props.$color}; + border-radius: 4px; + font-weight: bold; +`; + +const AttentionWrapper = styled.div` + display: flex; + flex-direction: row; + gap: 6px; + align-items: center; +`; + +const RangeText = styled.span` + font-size: 14px; + color: #bfbfbf; +`; + +const EmojiHeader = styled.span` + font-size: 18px; +`; + +const TableLink = styled(Link)` + color: #000; +`; + +const ActionIcon = styled(({ component: Component, ...props }) => )` + font-size: 16px; + color: #8c8c8c; + cursor: ${(props) => (props.onClick ? 'pointer' : 'default')}; +`; + +const ValueTag = ({ value, suffix = '' }: { value: any; suffix?: string }) => { + const num = parseFloat(value); + const color = getLabelColorByValue(num); + return ( + + {value} + {suffix} + + ); +}; + +const createEmotionColumn = (emoji: string, dataKey: string): ProColumns => ({ + title: {emoji}, + dataIndex: ['emotions', dataKey], + hideInSearch: true, + align: 'center', + width: 30, + render: (val) => `${val || 0}`, +}); + +export const EffectivenessAnalysis: React.FC<{ + class_type: string; +}> = () => { + const actionRef = useRef(); + const [loading, setLoading] = useState(false); + + const columns: ProColumns[] = [ + { + title: , + dataIndex: 'id', + hideInSearch: true, + sorter: true, + width: 40, + render: (dom, record) => ( + {dom} + ), + }, + { + title: , + dataIndex: 'name', + sorter: true, + width: 250, + render: (dom, record) => ( + {dom} + ), + }, + { + title: , + dataIndex: 'dateRange', + hideInTable: true, + valueType: 'dateRange', + }, + { + title: , + dataIndex: 'category_id', + hideInTable: true, + renderFormItem: (item, { type, ...rest }) => , + }, + { + title: , + dataIndex: 'averange_attention', + hideInSearch: true, + width: 150, + render: (_, record) => ( + + + + od {record.min_attention || '56%'} do {record.max_attention || '78%'} + + + ), + }, + { + title: , + dataIndex: 'emotions_label', + hideInSearch: true, + width: 5, + render: () => null, + }, + ...EMOTION_POOL.map((e) => createEmotionColumn(e.icon, e.key)), + { + title: , + dataIndex: 'rating', + hideInSearch: true, + width: 60, + render: (_, record) => , + }, + { + title: , + dataIndex: 'recording_short', + hideInSearch: true, + width: 60, + render: (_, record) => ( + + } + > + , + actionRef.current?.reload()} + />, + , + , + ]} + request={async ({ name, dateRange, category_id, pageSize, current }, sort) => { + setLoading(true); + const date_from = dateRange?.[0] + ? format(new Date(dateRange[0]), DATETIME_FORMAT) + : undefined; + const date_to = dateRange?.[1] + ? format(new Date(dateRange[1]), DATETIME_FORMAT) + : undefined; + + const response = await consultations({ + name, + 'categories[]': category_id, + per_page: pageSize, + page: current, + date_from, + date_to, + ...createTableOrderObject(sort, 'created_at'), + }); + setLoading(false); + return { + data: response.data, + total: response.meta.total, + success: response.success, + }; + }} + columns={columns} + pagination={{ pageSize: 10 }} + /> + + ); +}; + +export default EffectivenessAnalysis; diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 4cde9ad49..eb422068c 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -239,6 +239,12 @@ export default { slug: 'Slug', name: 'Name', ID: 'ID', + effectiveness_analysis: 'Effectiveness analysis', + averange_attention: 'Average attention', + emotions: 'Emotions', + rating: 'Rating', + recording_short: 'Rec.', + download_video_btn_tooltip: 'Download recording {duration}', newCategory: 'Create new Category', editCategory: 'Edit Category', parent_category: 'Parent category', diff --git a/src/locales/fr-FR.ts b/src/locales/fr-FR.ts index 439983be9..844bb096e 100644 --- a/src/locales/fr-FR.ts +++ b/src/locales/fr-FR.ts @@ -129,6 +129,12 @@ export default { slug: 'Slug', name: 'Nom', ID: 'ID', + effectiveness_analysis: 'Analyse d’efficacité', + averange_attention: 'Attention moyenne', + emotions: 'Émotions', + rating: 'Évaluation', + recording_short: 'Enreg.', + download_video_btn_tooltip: 'Télécharger l’enregistrement {duration}', newCategory: 'Créer une nouvelle catégorie', editCategory: 'Modifier la catégorie', parent_category: 'Catégorie parente', diff --git a/src/locales/pl-PL.ts b/src/locales/pl-PL.ts index c55608dda..0168599e4 100644 --- a/src/locales/pl-PL.ts +++ b/src/locales/pl-PL.ts @@ -216,6 +216,13 @@ export default { slug: 'Slug', name: 'Nazwa', ID: 'ID', + effectiveness_analysis: 'Analiza skuteczności', + averange_attention: 'Średnia atencja', + emotions: 'Emocje', + rating: 'Ocena', + recording_short: 'Nagr.', + download_video_btn_tooltip: 'Pobierz nagranie {duration}', + newCategory: 'Utwórz nową kategorię', editCategory: 'Edytuj kategorię', parent_category: 'Nadrzędna kategoria', diff --git a/src/pages/Consultations/index.tsx b/src/pages/Consultations/index.tsx index 59c5fa277..bd8c5bc1b 100644 --- a/src/pages/Consultations/index.tsx +++ b/src/pages/Consultations/index.tsx @@ -1,4 +1,5 @@ import CategoryTree from '@/components/CategoryTree'; +import EffectivenessAnalysis from '@/components/EffectivenessAnalysis'; import ModelFields from '@/components/ModelFields'; import { DATETIME_FORMAT, DAY_FORMAT } from '@/consts/dates'; import useModelFields from '@/hooks/useModelFields'; @@ -356,6 +357,12 @@ const Consultations: React.FC = () => { }> + } + > + + ); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4699c8484..075c64971 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -575,3 +575,19 @@ export const redirectPrefix = () => { // return routerType() === "HashRouter" ? "/#" : ""; return ''; }; + +export const getLabelColorByValue = (val: number) => { + if (val < 35) return '#FF4D4D'; + if (val < 70) return '#FFC107'; + return '#32cc1e'; +}; + +export const EMOTION_POOL = [ + { icon: '😳', key: 'surprised' }, + { icon: '🤢', key: 'disgusted' }, + { icon: '🙁', key: 'sad' }, + { icon: '😨', key: 'fearful' }, + { icon: '😆', key: 'happy' }, + { icon: '😡', key: 'angry' }, + { icon: '😐', key: 'neutral' }, +] as const; diff --git a/yarn.lock b/yarn.lock index 2b74443c2..de67a5f40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2803,6 +2803,16 @@ slugify "^1.4.0" smooth-scroll-into-view-if-needed "^1.1.29" +"@escolalms/scorm-player@^0.0.0": + version "0.0.0" + resolved "https://registry.yarnpkg.com/@escolalms/scorm-player/-/scorm-player-0.0.0.tgz#e3d8a883a7c53189f22c52aa4b15025d1ff008dd" + integrity sha512-zzhvoaKSoUGEbNIV05cq5pmEa0mo0eGWi5BVkzYvrjVGSqngH8vDziS2UnqiTGlbQfmVFIUMNZzDTRkcISEGyg== + dependencies: + rollup-plugin-peer-deps-external "^2.2.4" + rollup-plugin-postcss "^4.0.2" + scorm-again "^2.6.0" + tslib "^2.8.1" + "@escolalms/ts-models@^0.0.35": version "0.0.35" resolved "https://registry.yarnpkg.com/@escolalms/ts-models/-/ts-models-0.0.35.tgz#78deec70a35652e69531b5fee73da818855785ad" @@ -6765,6 +6775,11 @@ base64-js@^1.0.2, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +baseline-browser-mapping@^2.9.0: + version "2.10.8" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz#23d1cea1a85b181c2b8660b6cfe626dc2fb15630" + integrity sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ== + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -6991,6 +7006,17 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" +browserslist@^4.0.0, browserslist@^4.21.4: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== + dependencies: + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" + browserslist@^4.12.0, browserslist@^4.18.1, browserslist@^4.20.3, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.2, browserslist@^4.6.0: version "4.24.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" @@ -7258,6 +7284,21 @@ camelize@^1.0.0: resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001759: + version "1.0.30001780" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz#0e413de292808868a62ed9118822683fa120a110" + integrity sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ== + caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669: version "1.0.30001677" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz#27c2e2c637e007cfa864a16f7dfe7cde66b38b5f" @@ -7675,7 +7716,7 @@ color-support@^1.1.2, color-support@^1.1.3: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -colord@^2.9.2, colord@^2.9.3: +colord@^2.9.1, colord@^2.9.2, colord@^2.9.3: version "2.9.3" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== @@ -7819,6 +7860,13 @@ concat-stream@^1.5.2, concat-stream@^1.6.2: readable-stream "^2.2.2" typedarray "^0.0.6" +concat-with-sourcemaps@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" + integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== + dependencies: + source-map "^0.6.1" + conditional-wrap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/conditional-wrap/-/conditional-wrap-1.0.2.tgz#a993fd402e7747d83d1e36c675175f89852c25e5" @@ -8183,6 +8231,11 @@ css-color-keywords@^1.0.0: resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== +css-declaration-sorter@^6.3.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" + integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== + css-functions-list@^3.0.1, css-functions-list@^3.2.1: version "3.2.3" resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.3.tgz#95652b0c24f0f59b291a9fc386041a19d4f40dbe" @@ -8289,6 +8342,55 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssnano-preset-default@^5.2.14: + version "5.2.14" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" + integrity sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A== + dependencies: + css-declaration-sorter "^6.3.1" + cssnano-utils "^3.1.0" + postcss-calc "^8.2.3" + postcss-colormin "^5.3.1" + postcss-convert-values "^5.1.3" + postcss-discard-comments "^5.1.2" + postcss-discard-duplicates "^5.1.0" + postcss-discard-empty "^5.1.1" + postcss-discard-overridden "^5.1.0" + postcss-merge-longhand "^5.1.7" + postcss-merge-rules "^5.1.4" + postcss-minify-font-values "^5.1.0" + postcss-minify-gradients "^5.1.1" + postcss-minify-params "^5.1.4" + postcss-minify-selectors "^5.2.1" + postcss-normalize-charset "^5.1.0" + postcss-normalize-display-values "^5.1.0" + postcss-normalize-positions "^5.1.1" + postcss-normalize-repeat-style "^5.1.1" + postcss-normalize-string "^5.1.0" + postcss-normalize-timing-functions "^5.1.0" + postcss-normalize-unicode "^5.1.1" + postcss-normalize-url "^5.1.0" + postcss-normalize-whitespace "^5.1.1" + postcss-ordered-values "^5.1.3" + postcss-reduce-initial "^5.1.2" + postcss-reduce-transforms "^5.1.0" + postcss-svgo "^5.1.0" + postcss-unique-selectors "^5.1.1" + +cssnano-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" + integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== + +cssnano@^5.0.1: + version "5.1.15" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" + integrity sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw== + dependencies: + cssnano-preset-default "^5.2.14" + lilconfig "^2.0.3" + yaml "^1.10.2" + csso@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" @@ -9272,6 +9374,11 @@ ejs@^3.1.8: dependencies: jake "^10.8.5" +electron-to-chromium@^1.5.263: + version "1.5.321" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz#57a80554e2e7fd65e3689d320f52a64723472d5d" + integrity sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ== + electron-to-chromium@^1.5.41: version "1.5.51" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.51.tgz#bb99216fed4892d131a8585a8593b00739310163" @@ -10176,6 +10283,11 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -11051,6 +11163,13 @@ gauge@^4.0.3: strip-ansi "^6.0.1" wide-align "^1.1.5" +generic-names@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-4.0.0.tgz#0bd8a2fd23fe8ea16cbd0a279acd69c06933d9a3" + integrity sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A== + dependencies: + loader-utils "^3.2.0" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -11863,6 +11982,11 @@ iconv-lite@0.6, iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + integrity sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg== + icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" @@ -11919,6 +12043,13 @@ immer@^8.0.4, immer@^9.0.6, immer@^9.0.7: resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== +import-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92" + integrity sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg== + dependencies: + import-from "^3.0.0" + import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -11935,6 +12066,13 @@ import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3 parent-module "^1.0.0" resolve-from "^4.0.0" +import-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" + integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== + dependencies: + resolve-from "^5.0.0" + import-html-entry@^1.15.1: version "1.17.0" resolved "https://registry.yarnpkg.com/import-html-entry/-/import-html-entry-1.17.0.tgz#65211779b4ebf5a25200308bf0cbc6f831d5e4e1" @@ -13663,6 +13801,11 @@ lightningcss@1.22.1: lightningcss-linux-x64-musl "1.22.1" lightningcss-win32-x64-msvc "1.22.1" +lilconfig@^2.0.3, lilconfig@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -13789,6 +13932,11 @@ lodash-es@^4.17.21: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + lodash.curry@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" @@ -13829,6 +13977,11 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -13844,6 +13997,11 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + lodash.zip@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020" @@ -14953,6 +15111,11 @@ node-releases@^2.0.18: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== +node-releases@^2.0.27: + version "2.0.36" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d" + integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA== + node-source-walk@^4.0.0, node-source-walk@^4.2.0, node-source-walk@^4.2.2: version "4.3.0" resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-4.3.0.tgz#8336b56cfed23ac5180fe98f1e3bb6b11fd5317c" @@ -15031,6 +15194,11 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + normalize.css@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-7.0.0.tgz#abfb1dd82470674e0322b53ceb1aaf412938e4bf" @@ -15953,7 +16121,7 @@ picocolors@^0.2.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== -picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0: +picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -15983,6 +16151,11 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" + integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -16150,6 +16323,14 @@ postcss-attribute-case-insensitive@^5.0.0: dependencies: postcss-selector-parser "^6.0.10" +postcss-calc@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== + dependencies: + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + postcss-clamp@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-clamp/-/postcss-clamp-4.1.0.tgz#7263e95abadd8c2ba1bd911b0b5a5c9c93e02363" @@ -16178,6 +16359,24 @@ postcss-color-rebeccapurple@^7.0.2: dependencies: postcss-value-parser "^4.2.0" +postcss-colormin@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" + integrity sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" + integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== + dependencies: + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" + postcss-custom-media@^8.0.0: version "8.0.2" resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz#c8f9637edf45fef761b014c024cee013f80529ea" @@ -16206,6 +16405,26 @@ postcss-dir-pseudo-class@^6.0.4: dependencies: postcss-selector-parser "^6.0.10" +postcss-discard-comments@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" + integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== + +postcss-discard-duplicates@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== + +postcss-discard-empty@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" + integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== + +postcss-discard-overridden@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" + integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== + postcss-double-position-gradients@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz#b96318fdb477be95997e86edd29c6e3557a49b91" @@ -16296,6 +16515,14 @@ postcss-less@^6.0.0: resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-6.0.0.tgz#463b34c60f53b648c237f569aeb2e09149d85af4" integrity sha512-FPX16mQLyEjLzEuuJtxA8X3ejDLNGGEG503d2YGZR5Ask1SpDN8KmZUMpzCvyalWRywAn1n1VOA5dcqfCLo5rg== +postcss-load-config@^3.0.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" + integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== + dependencies: + lilconfig "^2.0.5" + yaml "^1.10.2" + postcss-logical@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" @@ -16311,6 +16538,56 @@ postcss-media-query-parser@^0.2.3: resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== +postcss-merge-longhand@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" + integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^5.1.1" + +postcss-merge-rules@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" + integrity sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + cssnano-utils "^3.1.0" + postcss-selector-parser "^6.0.5" + +postcss-minify-font-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" + integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" + integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== + dependencies: + colord "^2.9.1" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" + integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== + dependencies: + browserslist "^4.21.4" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" + integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== + dependencies: + postcss-selector-parser "^6.0.5" + postcss-modules-extract-imports@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" @@ -16339,6 +16616,20 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" +postcss-modules@^4.0.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/postcss-modules/-/postcss-modules-4.3.1.tgz#517c06c09eab07d133ae0effca2c510abba18048" + integrity sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q== + dependencies: + generic-names "^4.0.0" + icss-replace-symbols "^1.1.0" + lodash.camelcase "^4.3.0" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + string-hash "^1.1.1" + postcss-nesting@^10.1.4: version "10.2.0" resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.2.0.tgz#0b12ce0db8edfd2d8ae0aaf86427370b898890be" @@ -16347,11 +16638,82 @@ postcss-nesting@^10.1.4: "@csstools/selector-specificity" "^2.0.0" postcss-selector-parser "^6.0.10" +postcss-normalize-charset@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" + integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== + +postcss-normalize-display-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" + integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" + integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" + integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" + integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" + integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" + integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== + dependencies: + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" + integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== + dependencies: + normalize-url "^6.0.1" + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" + integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== + dependencies: + postcss-value-parser "^4.2.0" + postcss-opacity-percentage@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz#5b89b35551a556e20c5d23eb5260fbfcf5245da6" integrity sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A== +postcss-ordered-values@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" + integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== + dependencies: + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + postcss-overflow-shorthand@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz#7ed6486fec44b76f0eab15aa4866cda5d55d893e" @@ -16434,6 +16796,21 @@ postcss-pseudo-class-any-link@^7.1.2: dependencies: postcss-selector-parser "^6.0.10" +postcss-reduce-initial@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" + integrity sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" + integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== + dependencies: + postcss-value-parser "^4.2.0" + postcss-replace-overflow-wrap@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" @@ -16493,11 +16870,26 @@ postcss-sorting@6.0.0: dependencies: lodash "^4.17.20" +postcss-svgo@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" + integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^2.7.0" + postcss-syntax@0.36.2, postcss-syntax@^0.36.2: version "0.36.2" resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== +postcss-unique-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" + integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== + dependencies: + postcss-selector-parser "^6.0.5" + postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" @@ -16763,6 +17155,11 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" +promise.series@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" + integrity sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ== + promise@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/promise/-/promise-6.0.0.tgz#456538dd4afdd25dc7d0f52a5201ed242b7c109d" @@ -19147,6 +19544,30 @@ rmc-trigger@1.x: rc-util "4.x" rmc-align "~1.0.0" +rollup-plugin-peer-deps-external@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz#8a420bbfd6dccc30aeb68c9bf57011f2f109570d" + integrity sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g== + +rollup-plugin-postcss@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz#15e9462f39475059b368ce0e49c800fa4b1f7050" + integrity sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w== + dependencies: + chalk "^4.1.0" + concat-with-sourcemaps "^1.1.0" + cssnano "^5.0.1" + import-cwd "^3.0.0" + p-queue "^6.6.2" + pify "^5.0.0" + postcss-load-config "^3.0.0" + postcss-modules "^4.0.0" + promise.series "^0.2.0" + resolve "^1.19.0" + rollup-pluginutils "^2.8.2" + safe-identifier "^0.4.2" + style-inject "^0.3.0" + rollup-plugin-visualizer@5.9.0: version "5.9.0" resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz#013ac54fb6a9d7c9019e7eb77eced673399e5a0b" @@ -19157,6 +19578,13 @@ rollup-plugin-visualizer@5.9.0: source-map "^0.7.4" yargs "^17.5.1" +rollup-pluginutils@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + rollup@^0.25.8, rollup@^2.79.2, rollup@^3.27.1: version "2.79.2" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.2.tgz#f150e4a5db4b121a21a747d762f701e5e9f49090" @@ -19228,6 +19656,11 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-identifier@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" + integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w== + safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" @@ -19266,6 +19699,11 @@ sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== +sax@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.6.0.tgz#da59637629307b97e7c4cb28e080a7bc38560d5b" + integrity sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA== + saxes@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" @@ -20007,6 +20445,11 @@ string-convert@^0.2.0: resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== +string-hash@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -20261,6 +20704,11 @@ strip-outer@^1.0.1: dependencies: escape-string-regexp "^1.0.2" +style-inject@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3" + integrity sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw== + style-search@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" @@ -20281,6 +20729,14 @@ styled-components@6.1.1: stylis "^4.3.0" tslib "^2.5.0" +stylehacks@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" + integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== + dependencies: + browserslist "^4.21.4" + postcss-selector-parser "^6.0.4" + stylelint-config-css-modules@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/stylelint-config-css-modules/-/stylelint-config-css-modules-2.3.0.tgz#bf79aaae09ca9fdac37fdd518d1a40a779aa0767" @@ -20602,6 +21058,19 @@ svg-tags@^1.0.0: resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== +svgo@^2.7.0: + version "2.8.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.2.tgz#8e99b7ba5ac9ed7e3a446063865f61e03223fe6b" + integrity sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA== + dependencies: + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + sax "^1.5.0" + stable "^0.1.8" + svgo@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" @@ -20997,7 +21466,7 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.5.0, tslib@^2.5.3, tslib@^2.6.0: +tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.5.0, tslib@^2.5.3, tslib@^2.6.0, tslib@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -21499,6 +21968,14 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.0" +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + update-notifier@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-3.0.0.tgz#e9bbf8f0f5b7a2ce6666ca46334fdb29492e8fab" From 5ff64252392fdd6b9b6b566a2f46cc6f2cd6354d Mon Sep 17 00:00:00 2001 From: "mateusz.bieniek" Date: Tue, 24 Mar 2026 08:20:47 +0100 Subject: [PATCH 2/8] REK-155 Add effectiveness analysis details page --- config/routes.ts | 6 + package.json | 1 + .../EffectivenessAnalysis/ModalForm.tsx | 183 -------- src/locales/pl-PL.ts | 21 +- .../components/EffectivenessAnalysis.tsx} | 21 +- .../EffectivenessAnalysisDetails.tsx | 399 ++++++++++++++++++ src/pages/Consultations/index.tsx | 2 +- yarn.lock | 202 ++++++++- 8 files changed, 628 insertions(+), 207 deletions(-) delete mode 100644 src/components/EffectivenessAnalysis/ModalForm.tsx rename src/{components/EffectivenessAnalysis/index.tsx => pages/Consultations/components/EffectivenessAnalysis.tsx} (91%) create mode 100644 src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx diff --git a/config/routes.ts b/config/routes.ts index dc8812d45..21bd1d2a2 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -535,6 +535,12 @@ component: './Consultations/form', hideInMenu: true, }, + { + path: '/other/consultations/effectiveness-analysis/:id', + access: 'consultationDetailsPermission', + component: './Consultations/components/EffectivenessAnalysisDetails', + hideInMenu: true, + }, { name: 'StationaryEvents', icon: 'global', diff --git a/package.json b/package.json index 4b7a00cd5..2838c561e 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "react-player": "^2.14.1", "react-sortable-hoc": "^2.0.0", "react-tiny-oembed": "^1.1.0", + "recharts": "^3.8.0", "reportbro-designer": "^3.6.0" }, "devDependencies": { diff --git a/src/components/EffectivenessAnalysis/ModalForm.tsx b/src/components/EffectivenessAnalysis/ModalForm.tsx deleted file mode 100644 index f6122ee4a..000000000 --- a/src/components/EffectivenessAnalysis/ModalForm.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import ProForm, { ModalForm, ProFormDigit, ProFormRadio, ProFormText } from '@ant-design/pro-form'; -import { Form } from 'antd'; -import React, { useCallback, useEffect } from 'react'; -import { FormattedMessage, useIntl } from 'umi'; - -import { createOrUpdateField } from '@/services/escola-lms/fields'; -import JsonEditor from '../JsonEditor'; - -enum ModelFieldType { - BOOLEAN = 'boolean', - NUMBER = 'number', - VARCHAR = 'varchar', - TEXT = 'text', - JSON = 'json', -} - -export const ModelFieldsModalForm: React.FC<{ - class_type: string; - name: string | boolean; - visible: boolean; - onVisibleChange: (visible: boolean) => void; - onResponse: (metaField: EscolaLms.ModelFields.Models.Metadata) => void; - fields: EscolaLms.ModelFields.Models.Metadata[]; -}> = ({ visible, onVisibleChange, onResponse, class_type, name = 'new', fields = [] }) => { - const intl = useIntl(); - - const [form] = Form.useForm(); - - const onFinish = useCallback( - async (data: EscolaLms.ModelFields.Http.Requests.MetadataCreateOrUpdateRequest) => { - await createOrUpdateField({ - ...data, - class_type, - rules: JSON.stringify(data.rules), - extra: JSON.stringify(data.extra), - }).then((response) => { - if (response.success) { - onResponse(response.data); - } - }); - return false; - }, - [form], - ); - - useEffect(() => { - if (name !== 'new') { - const field = fields.find((record) => record.name === name); - if (field) { - form.setFieldsValue(field); - } - } else { - form.resetFields(); - } - }, [name, form, fields]); - - return ( - - } - tooltip={} - rules={[ - { - required: true, - }, - ]} - width="md" - name="name" - /> - - } - tooltip={} - options={(Object.keys(ModelFieldType) as (keyof typeof ModelFieldType)[]).map((key) => ({ - label: intl.formatMessage({ - id: `model.${ModelFieldType[key]}`, - }), - value: ModelFieldType[key], - }))} - /> - - } - tooltip={} - placeholder={intl.formatMessage({ - id: 'default', - })} - /> - - - {' '} - - - - - } - tooltip={} - valuePropName="value" - > - - - - {/** - * this int must be power of 2 - * example :// - const PUBLIC = 1 << 0; // 1 - const AUTHORIZED = 1 << 1; // 2 - const ADMIN = 1 << 2; // 4 - etc... - */} - } - tooltip={} - placeholder={intl.formatMessage({ - id: 'visibility', - defaultMessage: 'visibility', - })} - min={1} - max={1024} - fieldProps={{ step: 1 }} - rules={[ - { - validator: async (_, value) => { - if (Math.pow(2, Math.ceil(Math.log2(value))) - value) { - return Promise.reject( - new Error( - intl.formatMessage({ - id: 'notPowerOfTwo', - defaultMessage: 'notPowerOfTwo', - }), - ), - ); - } - return Promise.resolve(); - }, - }, - ]} - /> - - } - tooltip={} - valuePropName="value" - > - - - - ); -}; - -export default ModelFieldsModalForm; diff --git a/src/locales/pl-PL.ts b/src/locales/pl-PL.ts index 0168599e4..b1d526fa5 100644 --- a/src/locales/pl-PL.ts +++ b/src/locales/pl-PL.ts @@ -222,7 +222,26 @@ export default { rating: 'Ocena', recording_short: 'Nagr.', download_video_btn_tooltip: 'Pobierz nagranie {duration}', - + other_activities: 'Inne aktywności', + consultations: 'Konsultacje', + detailed_analysis: 'Analiza szczegółowa', + download_recording: 'Pobierz nagranie', + recording_available_until: 'Nagranie dostępne jeszcze przez: {time}', + 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 konsultacji.', + resolution: 'Rozdzielczość', + listeners_engagement: 'Zaangażowanie słuchaczy (%)', + detected_emotions: 'Wykryte emocje', + ai_segment_analysis: 'Analiza segmentu AI...', + time: 'Czas', + emotion_surprised: 'Zaskoczenie', + emotion_disgusted: 'Zniesmaczenie', + emotion_sad: 'Smutek', + emotion_fearful: 'Strach', + emotion_happy: 'Radość', + emotion_angry: 'Złość', + emotion_neutral: 'Neutralny', newCategory: 'Utwórz nową kategorię', editCategory: 'Edytuj kategorię', parent_category: 'Nadrzędna kategoria', diff --git a/src/components/EffectivenessAnalysis/index.tsx b/src/pages/Consultations/components/EffectivenessAnalysis.tsx similarity index 91% rename from src/components/EffectivenessAnalysis/index.tsx rename to src/pages/Consultations/components/EffectivenessAnalysis.tsx index 7913d743d..4953cbaab 100644 --- a/src/components/EffectivenessAnalysis/index.tsx +++ b/src/pages/Consultations/components/EffectivenessAnalysis.tsx @@ -37,11 +37,7 @@ const AttentionWrapper = styled.div` flex-direction: row; gap: 6px; align-items: center; -`; - -const RangeText = styled.span` - font-size: 14px; - color: #bfbfbf; + justify-content: center; `; const EmojiHeader = styled.span` @@ -92,7 +88,7 @@ export const EffectivenessAnalysis: React.FC<{ sorter: true, width: 40, render: (dom, record) => ( - {dom} + {dom} ), }, { @@ -101,7 +97,7 @@ export const EffectivenessAnalysis: React.FC<{ sorter: true, width: 250, render: (dom, record) => ( - {dom} + {dom} ), }, { @@ -114,19 +110,16 @@ export const EffectivenessAnalysis: React.FC<{ title: , dataIndex: 'category_id', hideInTable: true, - renderFormItem: (item, { type, ...rest }) => , + renderFormItem: ({ type, ...rest }) => , }, { title: , dataIndex: 'averange_attention', hideInSearch: true, - width: 150, + width: 50, render: (_, record) => ( - - od {record.min_attention || '56%'} do {record.max_attention || '78%'} - ), }, @@ -175,7 +168,6 @@ export const EffectivenessAnalysis: React.FC<{ //TODO: Connect with backend, add correct types return ( - actionRef={actionRef} rowKey="id" @@ -184,7 +176,7 @@ export const EffectivenessAnalysis: React.FC<{ columnEmptyText="0%" toolBarRender={() => [ , - ); }; diff --git a/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx b/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx new file mode 100644 index 000000000..5373926bc --- /dev/null +++ b/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx @@ -0,0 +1,399 @@ +import React, { useState, useMemo, Fragment } from 'react'; +import { + Breadcrumb, + Button, + Card, + Col, + Select, + Space, + Typography, +} from 'antd'; +import { DownloadOutlined } from '@ant-design/icons'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip as RechartsTooltip, + ResponsiveContainer, +} from 'recharts'; +import styled from 'styled-components'; +import { FormattedMessage } from 'umi'; + +const { Title, Text } = Typography; + +const COLORS = { + green: '#52c41a', + orange: '#faad14', + red: '#f5222d', + border: '#f0f0f0', + textSecondary: '#8c8c8c', + bgLight: '#fbfbfb', + white: '#ffffff', + darkText: '#434343', +}; + +// --- Styled Components --- + +const PageWrapper = styled.div` + padding: 24px; + background: ${COLORS.bgLight}; + min-height: 100vh; + border-radius: 8px; +`; + +const HeaderWrapper = styled.div` + padding: 16px 24px 0; +`; + +const StyledBreadcrumb = styled(Breadcrumb)` + margin-bottom: 20px; +`; + +const TitleSection = styled.div` + display: flex; + gap: 24px; + margin-bottom: 32px; + align-items: center; +`; + +const StyledDownloadButton = styled(Button)` + border-radius: 8px; +`; + +const ExpiryText = styled(Text)` + font-size: 13px; + margin-left: 4px; +`; + +const ExpiryTime = styled.b` + color: ${COLORS.darkText}; +`; + +const StyledCard = styled(Card)` + background: transparent; + box-shadow: none; + .ant-card-body { + padding: 0; + } +`; + +const ControlsRow = styled(Col)` + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 24px; +`; + +const RatingValue = styled.div` + font-size: 32px; + font-weight: 800; + color: ${COLORS.green}; + padding: 4px 16px; + border: 1px solid #b7eb8f; + border-radius: 6px; + background: #f6ffed; +`; + +const RatingDescription = styled(Text)` + max-width: 250px; +`; + +const ResolutionPicker = styled(Space)` + background: ${COLORS.white}; + padding: 8px 16px; + border-radius: 8px; + border: 1px solid ${COLORS.border}; +`; + +const ResolutionLabel = styled(Text)` + font-size: 13px; +`; + +const ScrollContainer = styled.div` + width: 100%; + overflow-x: auto; + overflow-y: hidden; + background: transparent; + touch-action: pan-x; + + &::-webkit-scrollbar { height: 8px; } + &::-webkit-scrollbar-thumb { background: #e8e8e8; border-radius: 4px; } +`; + +const ChartWrapper = styled.div<{ $width: string }>` + width: ${(props) => props.$width}; + min-width: 100%; + display: flex; + flex-direction: column; + padding: 20px 0 100px 0; +`; + +const ChartHeader = styled.div<{ $paddingLeft: number }>` + margin-bottom: 16px; + padding-left: ${(props) => props.$paddingLeft}px; +`; + +const ChartContainer = styled.div<{ $height: number }>` + height: ${(props) => props.$height}px; + width: 100%; + position: relative; +`; + +const EmotionGridLine = styled.div<{ $left: number }>` + position: absolute; + top: 55px; + left: ${(props) => props.$left}px; + right: 30px; + height: 1px; + background: ${COLORS.border}; + z-index: 0; +`; + +const EmotionIconWrapper = styled.div` + font-size: 26px; + text-align: center; +`; + +const CustomTooltipWrapper = styled.div` + background: white; + border: 1px solid ${COLORS.border}; + padding: 16px; + border-radius: 10px; + width: 260px; + z-index: 1000; + box-shadow: 0 10px 30px rgba(0,0,0,0.1); + + .tooltip-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; + } + .percentage-badge { + padding: 2px 10px; + border-radius: 4px; + font-weight: 700; + } + .preview-img { + width: 100%; + height: 120px; + object-fit: cover; + border-radius: 6px; + background: #262626; + } +`; + +// --- Utils & Constants --- + +export const EMOTION_POOL = [ + { icon: '😳', key: 'surprised', labelId: 'emotion_surprised' }, + { icon: '🤢', key: 'disgusted', labelId: 'emotion_disgusted' }, + { icon: '🙁', key: 'sad', labelId: 'emotion_sad' }, + { icon: '😨', key: 'fearful', labelId: 'emotion_fearful' }, + { icon: '😆', key: 'happy', labelId: 'emotion_happy' }, + { icon: '😡', key: 'angry', labelId: 'emotion_angry' }, + { icon: '😐', key: 'neutral', labelId: 'emotion_neutral' }, +] as const; + +const getColorByValue = (val: number) => { + if (val < 35) return COLORS.red; + if (val < 70) return COLORS.orange; + return COLORS.green; +}; + +// --- Child Components --- + +const CustomTooltip = ({ active, payload }: any) => { + if (!active || !payload?.length) return null; + const data = payload[0].payload; + const emotion = EMOTION_POOL.find(e => e.key === data.emotionKey) || EMOTION_POOL[6]; + const valColor = getColorByValue(data.attention); + + return ( + +
+ + {emotion.icon} + + + {data.attention}% + +
+
+ : {data.time} +
+
+ +
+ preview +
+ ); +}; + +// --- Main Component --- + +export const EffectivenessAnalysisDetails = () => { + const [res, setRes] = useState(15); + const Y_AXIS_WIDTH = 45; + const TOTAL_DURATION_SEC = 1800; + + const data = useMemo(() => { + const pointsCount = Math.floor(TOTAL_DURATION_SEC / res); + const getAttention = (s: number) => [92, 85, 78, 45, 30, 25, 40, 65, 88, 95][Math.floor(s / 15) % 10]; + + return Array.from({ length: pointsCount + 1 }, (_, i) => { + const sec = i * res; + return { + time: `${Math.floor(sec / 60)}:${(sec % 60).toString().padStart(2, '0')}`, + attention: getAttention(sec), + emotionKey: EMOTION_POOL[Math.floor(sec / 15) % EMOTION_POOL.length].key, + }; + }); + }, [res]); + + const chartWidth = useMemo(() => { + if (res >= 300) return '100%'; + const baseWidth = res <= 30 ? 85 : 150; + return `${data.length * baseWidth}px`; + }, [data.length, res]); + + const renderDynamicGradient = (id: string, isArea: boolean) => ( + + {data.map((point, index) => ( + + ))} + + ); + + return ( + + + + + + + + + + + + Konsultacja Testowa + + } size="large"> + + + + 9h 35m }} + />. + + + + + + + + + + + + 6.75 + + + + + + + + : + + + + + + + + {/* Chart 1: Attention */} + + + + + + + + + + {renderDynamicGradient("dynamicFill", true)} + {renderDynamicGradient("dynamicStroke", false)} + + + = 300 ? 0 : 'preserveStartEnd'} /> + `${v}%`} axisLine={false} tickLine={false} tick={{ fontSize: 11, fill: COLORS.textSecondary }} /> + } cursor={{ stroke: '#40a9ff', strokeWidth: 1.5 }} /> + + + + + + {/* Chart 2: Emotions */} + + + + + + + + + + = 300 ? 0 : 'preserveStartEnd'} /> + + } /> + { + const { cx, payload } = props; + const emotion = EMOTION_POOL.find(e => e.key === payload.emotionKey) || EMOTION_POOL[6]; + return ( + + {emotion.icon} + + ); + }} + /> + + + + + + + + + + ); +}; + +export default EffectivenessAnalysisDetails; diff --git a/src/pages/Consultations/index.tsx b/src/pages/Consultations/index.tsx index bd8c5bc1b..cd15b39bb 100644 --- a/src/pages/Consultations/index.tsx +++ b/src/pages/Consultations/index.tsx @@ -1,5 +1,5 @@ import CategoryTree from '@/components/CategoryTree'; -import EffectivenessAnalysis from '@/components/EffectivenessAnalysis'; +import EffectivenessAnalysis from './components/EffectivenessAnalysis'; import ModelFields from '@/components/ModelFields'; import { DATETIME_FORMAT, DAY_FORMAT } from '@/consts/dates'; import useModelFields from '@/hooks/useModelFields'; diff --git a/yarn.lock b/yarn.lock index de67a5f40..56ba6b9e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3931,6 +3931,18 @@ rc-resize-observer "^1.3.1" rc-util "^5.38.0" +"@reduxjs/toolkit@^1.9.0 || 2.x.x": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.11.2.tgz#582225acea567329ca6848583e7dd72580d38e82" + integrity sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ== + dependencies: + "@standard-schema/spec" "^1.0.0" + "@standard-schema/utils" "^0.3.0" + immer "^11.0.0" + redux "^5.0.1" + redux-thunk "^3.1.0" + reselect "^5.1.0" + "@remix-run/router@1.20.0": version "1.20.0" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.20.0.tgz#03554155b45d8b529adf635b2f6ad1165d70d8b4" @@ -4212,6 +4224,16 @@ resolved "https://registry.yarnpkg.com/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz#03ecc29279e3c0c832f6185a5bfa3497858ac8ca" integrity sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw== +"@standard-schema/spec@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + +"@standard-schema/utils@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b" + integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== + "@stylelint/postcss-css-in-js@^0.37.2": version "0.37.3" resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.3.tgz#d149a385e07ae365b0107314c084cb6c11adbf49" @@ -4562,6 +4584,57 @@ dependencies: "@types/node" "*" +"@types/d3-array@^3.0.3": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.2.tgz#e02151464d02d4a1b44646d0fcdb93faf88fde8c" + integrity sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-ease@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-interpolate@^3.0.1": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a" + integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== + +"@types/d3-scale@^4.0.2": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb" + integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== + dependencies: + "@types/d3-time" "*" + +"@types/d3-shape@^3.1.0": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.8.tgz#d1516cc508753be06852cd06758e3bb54a22b0e3" + integrity sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time@*", "@types/d3-time@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f" + integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== + +"@types/d3-timer@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + "@types/eslint@^7.2.13": version "7.29.0" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" @@ -4924,6 +4997,11 @@ resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== +"@types/use-sync-external-store@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" + integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== + "@types/uuid@^9.0.8": version "9.0.8" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" @@ -7657,7 +7735,7 @@ clsx@^1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== -clsx@^2.0.0: +clsx@^2.0.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== @@ -8430,7 +8508,7 @@ custom-event@^1.0.1: resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== -"d3-array@2.5.0 - 3", d3-array@^3.2.4: +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@^3.1.6, d3-array@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== @@ -8456,6 +8534,11 @@ d3-dsv@^3.0.1: iconv-lite "0.6" rw "1" +d3-ease@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + d3-force@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" @@ -8465,6 +8548,11 @@ d3-force@^3.0.0: d3-quadtree "1 - 3" d3-timer "1 - 3" +"d3-format@1 - 3": + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.2.tgz#01fdb46b58beb1f55b10b42ad70b6e344d5eb2ae" + integrity sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg== + d3-format@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" @@ -8482,7 +8570,7 @@ d3-hierarchy@^3.1.2: resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== -"d3-interpolate@1 - 3": +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== @@ -8507,14 +8595,39 @@ d3-scale-chromatic@^3.0.0: d3-color "1 - 3" d3-interpolate "1 - 3" -d3-shape@^3.2.0: +d3-scale@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +d3-shape@^3.1.0, d3-shape@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== dependencies: d3-path "^3.1.0" -"d3-timer@1 - 3": +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== @@ -8656,6 +8769,11 @@ decamelize@^5.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== +decimal.js-light@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -9715,6 +9833,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es-toolkit@^1.39.3: + version "1.45.1" + resolved "https://registry.yarnpkg.com/es-toolkit/-/es-toolkit-1.45.1.tgz#21b28b2bd43178fd4c9c937c445d5bcaccce907b" + integrity sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw== + es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14, es5-ext@~0.10.2: version "0.10.64" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" @@ -12038,7 +12161,7 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== -immer@^8.0.4, immer@^9.0.6, immer@^9.0.7: +immer@^10.1.1, immer@^11.0.0, immer@^8.0.4, immer@^9.0.6, immer@^9.0.7: version "9.0.21" resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== @@ -18658,6 +18781,14 @@ react-portal@^4.2.1: dependencies: prop-types "^15.5.8" +"react-redux@8.x.x || 9.x.x": + version "9.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.2.0.tgz#96c3ab23fb9a3af2cb4654be4b51c989e32366f5" + integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g== + dependencies: + "@types/use-sync-external-store" "^0.0.6" + use-sync-external-store "^1.4.0" + react-redux@^7.0.3, react-redux@^7.2.6: version "7.2.9" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" @@ -18930,6 +19061,23 @@ recast@^0.21.5: source-map "~0.6.1" tslib "^2.0.1" +recharts@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-3.8.0.tgz#461025818cbb858e7ff2e5820b67c6143e9b418d" + integrity sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ== + dependencies: + "@reduxjs/toolkit" "^1.9.0 || 2.x.x" + clsx "^2.1.1" + decimal.js-light "^2.5.1" + es-toolkit "^1.39.3" + eventemitter3 "^5.0.1" + immer "^10.1.1" + react-redux "8.x.x || 9.x.x" + reselect "5.1.1" + tiny-invariant "^1.3.3" + use-sync-external-store "^1.2.2" + victory-vendor "^37.0.2" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -18972,6 +19120,11 @@ redux-saga@^0.16.0: resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.16.2.tgz#993662e86bc945d8509ac2b8daba3a8c615cc971" integrity sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w== +redux-thunk@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" + integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== + redux@^4.0.0, redux@^4.0.1, redux@^4.1.2, redux@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" @@ -18979,6 +19132,11 @@ redux@^4.0.0, redux@^4.0.1, redux@^4.1.2, redux@^4.2.1: dependencies: "@babel/runtime" "^7.9.2" +redux@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== + reflect.getprototypeof@^1.0.4, reflect.getprototypeof@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" @@ -19244,6 +19402,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +reselect@5.1.1, reselect@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e" + integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w== + reselect@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147" @@ -21291,7 +21454,7 @@ timers-ext@^0.1.7: es5-ext "^0.10.64" next-tick "^1.1.0" -tiny-invariant@^1.0.0, tiny-invariant@^1.0.4, tiny-invariant@^1.0.6: +tiny-invariant@^1.0.0, tiny-invariant@^1.0.4, tiny-invariant@^1.0.6, tiny-invariant@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== @@ -22069,6 +22232,11 @@ use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== +use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -22208,6 +22376,26 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" +victory-vendor@^37.0.2: + version "37.3.6" + resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-37.3.6.tgz#401ac4b029a0b3d33e0cba8e8a1d765c487254da" + integrity sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ== + dependencies: + "@types/d3-array" "^3.0.3" + "@types/d3-ease" "^3.0.0" + "@types/d3-interpolate" "^3.0.1" + "@types/d3-scale" "^4.0.2" + "@types/d3-shape" "^3.1.0" + "@types/d3-time" "^3.0.0" + "@types/d3-timer" "^3.0.0" + d3-array "^3.1.6" + d3-ease "^3.0.1" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + d3-shape "^3.1.0" + d3-time "^3.0.0" + d3-timer "^3.0.1" + vinyl-file@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-3.0.0.tgz#b104d9e4409ffa325faadd520642d0a3b488b365" From ba74b4e7e9c20793d66dbaf0013fd0ca52df901d Mon Sep 17 00:00:00 2001 From: "mateusz.bieniek" Date: Tue, 24 Mar 2026 12:58:11 +0100 Subject: [PATCH 3/8] REK-155 Add effectiveness analysis list api connection --- .../components/EffectivenessAnalysis.tsx | 361 +++++++++++------- .../EffectivenessAnalysisDetails.tsx | 120 ++++-- src/pages/Consultations/consultations.ts | 30 ++ src/pages/Consultations/index.tsx | 4 +- src/services/escola-lms/consultations.ts | 16 + 5 files changed, 347 insertions(+), 184 deletions(-) create mode 100644 src/pages/Consultations/consultations.ts diff --git a/src/pages/Consultations/components/EffectivenessAnalysis.tsx b/src/pages/Consultations/components/EffectivenessAnalysis.tsx index 4953cbaab..ad5946daa 100644 --- a/src/pages/Consultations/components/EffectivenessAnalysis.tsx +++ b/src/pages/Consultations/components/EffectivenessAnalysis.tsx @@ -1,6 +1,6 @@ import CategoryTree from '@/components/CategoryTree'; import { DATETIME_FORMAT } from '@/consts/dates'; -import { consultations } from '@/services/escola-lms/consultations'; +import type { RecommenderParams, RecommenderTerm } from '@/pages/Consultations/consultations'; import { createTableOrderObject, EMOTION_POOL, getLabelColorByValue } from '@/utils/utils'; import { Link } from '@@/exports'; import { @@ -14,9 +14,27 @@ import type { ActionType, ProColumns } from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table'; import { Button, Tag, Tooltip } from 'antd'; import { format } from 'date-fns'; -import React, { Fragment, useRef, useState } from 'react'; +import type { ElementType } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; -import { FormattedMessage } from 'umi'; +import { FormattedMessage, request } from 'umi'; + +interface ActionIconProps { + component: ElementType; + onClick?: () => void; + className?: string; + key?: string | number; +} + +export async function getRecommenderTerms(modelType: string, params?: RecommenderParams) { + return request>( + `/api/admin/recommender/terms/${modelType}`, + { + method: 'GET', + params, + }, + ); +} const StyledProTable = styled(ProTable)` .ant-table-thead > tr > th::before { @@ -48,172 +66,225 @@ const TableLink = styled(Link)` color: #000; `; -const ActionIcon = styled(({ component: Component, ...props }) => )` +const ActionIcon = styled(({ component: Component, ...props }: ActionIconProps) => ( + +))` font-size: 16px; color: #8c8c8c; cursor: ${(props) => (props.onClick ? 'pointer' : 'default')}; `; -const ValueTag = ({ value, suffix = '' }: { value: any; suffix?: string }) => { - const num = parseFloat(value); - const color = getLabelColorByValue(num); - return ( - - {value} - {suffix} - - ); +const formatPercent = (val: string | number) => { + const num = typeof val === 'string' ? parseFloat(val) : val; + return isNaN(num) ? '0' : Math.round(num * 100).toString(); }; -const createEmotionColumn = (emoji: string, dataKey: string): ProColumns => ({ +const ValueTag = React.memo( + ({ + value, + suffix = '', + isRaw = false, + }: { + value: string | number; + suffix?: string; + isRaw?: boolean; + }) => { + const displayValue = useMemo(() => { + if (isRaw) return formatPercent(value); + const num = typeof value === 'string' ? parseFloat(value) : value; + return isNaN(num) ? '0.00' : num.toFixed(2); + }, [value, isRaw]); + + const color = useMemo(() => getLabelColorByValue(parseFloat(displayValue)), [displayValue]); + + return ( + + {displayValue} + {suffix} + + ); + }, +); +const createEmotionColumn = (emoji: string, dataKey: string): ProColumns => ({ title: {emoji}, - dataIndex: ['emotions', dataKey], + dataIndex: `avg_emotions_${dataKey}` as keyof RecommenderTerm, hideInSearch: true, align: 'center', - width: 30, - render: (val) => `${val || 0}`, + width: 45, + render: (val) => `${formatPercent(val as string)}%`, }); -export const EffectivenessAnalysis: React.FC<{ - class_type: string; -}> = () => { +export const EffectivenessAnalysis = () => { const actionRef = useRef(); const [loading, setLoading] = useState(false); - const columns: ProColumns[] = [ - { - title: , - dataIndex: 'id', - hideInSearch: true, - sorter: true, - width: 40, - render: (dom, record) => ( - {dom} - ), - }, - { - title: , - dataIndex: 'name', - sorter: true, - width: 250, - render: (dom, record) => ( - {dom} - ), - }, - { - title: , - dataIndex: 'dateRange', - hideInTable: true, - valueType: 'dateRange', - }, - { - title: , - dataIndex: 'category_id', - hideInTable: true, - renderFormItem: ({ type, ...rest }) => , - }, - { - title: , - dataIndex: 'averange_attention', - hideInSearch: true, - width: 50, - render: (_, record) => ( - - - - ), - }, - { - title: , - dataIndex: 'emotions_label', - hideInSearch: true, - width: 5, - render: () => null, - }, - ...EMOTION_POOL.map((e) => createEmotionColumn(e.icon, e.key)), - { - title: , - dataIndex: 'rating', - hideInSearch: true, - width: 60, - render: (_, record) => , - }, - { - title: , - dataIndex: 'recording_short', - hideInSearch: true, - width: 60, - render: (_, record) => ( - - } - > - , - actionRef.current?.reload()} - />, - , - , - ]} - request={async ({ name, dateRange, category_id, pageSize, current }, sort) => { - setLoading(true); - const date_from = dateRange?.[0] - ? format(new Date(dateRange[0]), DATETIME_FORMAT) - : undefined; - const date_to = dateRange?.[1] - ? format(new Date(dateRange[1]), DATETIME_FORMAT) - : undefined; - - const response = await consultations({ - name, + + actionRef={actionRef} + rowKey="id" + loading={loading} + search={{ + layout: 'horizontal', + labelWidth: 'auto', + }} + columnEmptyText="0%" + toolBarRender={() => [ + , + actionRef.current?.reload()} + />, + , + , + ]} + request={async ({ name, dateRange, category_id, pageSize, current }, sort) => { + setLoading(true); + + const date_from = dateRange?.[0] + ? format(new Date(dateRange[0]), DATETIME_FORMAT) + : undefined; + const date_to = dateRange?.[1] + ? format(new Date(dateRange[1]), DATETIME_FORMAT) + : undefined; + + try { + const response = await getRecommenderTerms('consultation', { + name: name || undefined, 'categories[]': category_id, per_page: pageSize, page: current, date_from, date_to, - ...createTableOrderObject(sort, 'created_at'), + ...createTableOrderObject(sort, 'term'), }); + + setLoading(false); + + if (response.success) { + return { + data: response.data, + total: response.meta.total, + success: true, + }; + } + + return { data: [], total: 0, success: false }; + } catch (error) { setLoading(false); - return { - data: response.data, - total: response.meta.total, - success: response.success, - }; - }} - columns={columns} - pagination={{ pageSize: 10 }} - /> + return { data: [], total: 0, success: false }; + } + }} + columns={columns} + pagination={{ + pageSize: 10, + showSizeChanger: true, + }} + /> ); }; diff --git a/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx b/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx index 5373926bc..ffdca2dfb 100644 --- a/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx +++ b/src/pages/Consultations/components/EffectivenessAnalysisDetails.tsx @@ -1,22 +1,14 @@ -import React, { useState, useMemo, Fragment } from 'react'; -import { - Breadcrumb, - Button, - Card, - Col, - Select, - Space, - Typography, -} from 'antd'; import { DownloadOutlined } from '@ant-design/icons'; +import { Breadcrumb, Button, Card, Col, Select, Space, Typography } from 'antd'; +import { Fragment, useMemo, useState } from 'react'; import { - AreaChart, Area, - XAxis, - YAxis, + AreaChart, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, + XAxis, + YAxis, } from 'recharts'; import styled from 'styled-components'; import { FormattedMessage } from 'umi'; @@ -118,8 +110,13 @@ const ScrollContainer = styled.div` background: transparent; touch-action: pan-x; - &::-webkit-scrollbar { height: 8px; } - &::-webkit-scrollbar-thumb { background: #e8e8e8; border-radius: 4px; } + &::-webkit-scrollbar { + height: 8px; + } + &::-webkit-scrollbar-thumb { + background: #e8e8e8; + border-radius: 4px; + } `; const ChartWrapper = styled.div<{ $width: string }>` @@ -163,25 +160,25 @@ const CustomTooltipWrapper = styled.div` border-radius: 10px; width: 260px; z-index: 1000; - box-shadow: 0 10px 30px rgba(0,0,0,0.1); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); .tooltip-header { display: flex; - justify-content: space-between; align-items: center; + justify-content: space-between; margin-bottom: 6px; } .percentage-badge { padding: 2px 10px; - border-radius: 4px; font-weight: 700; + border-radius: 4px; } .preview-img { width: 100%; height: 120px; object-fit: cover; - border-radius: 6px; background: #262626; + border-radius: 6px; } `; @@ -208,7 +205,7 @@ const getColorByValue = (val: number) => { const CustomTooltip = ({ active, payload }: any) => { if (!active || !payload?.length) return null; const data = payload[0].payload; - const emotion = EMOTION_POOL.find(e => e.key === data.emotionKey) || EMOTION_POOL[6]; + const emotion = EMOTION_POOL.find((e) => e.key === data.emotionKey) || EMOTION_POOL[6]; const valColor = getColorByValue(data.attention); return ( @@ -217,7 +214,14 @@ const CustomTooltip = ({ active, payload }: any) => { {emotion.icon} - + {data.attention}% @@ -227,7 +231,11 @@ const CustomTooltip = ({ active, payload }: any) => {
- preview + preview ); }; @@ -241,7 +249,8 @@ export const EffectivenessAnalysisDetails = () => { const data = useMemo(() => { const pointsCount = Math.floor(TOTAL_DURATION_SEC / res); - const getAttention = (s: number) => [92, 85, 78, 45, 30, 25, 40, 65, 88, 95][Math.floor(s / 15) % 10]; + const getAttention = (s: number) => + [92, 85, 78, 45, 30, 25, 40, 65, 88, 95][Math.floor(s / 15) % 10]; return Array.from({ length: pointsCount + 1 }, (_, i) => { const sec = i * res; @@ -263,7 +272,7 @@ export const EffectivenessAnalysisDetails = () => { {data.map((point, index) => ( { - - - + + + + + + + + + - Konsultacja Testowa + + Konsultacja Testowa + } size="large"> @@ -293,7 +310,8 @@ export const EffectivenessAnalysisDetails = () => { 9h 35m }} - />. + /> + . @@ -313,7 +331,9 @@ export const EffectivenessAnalysisDetails = () => { - : + + : + - 15 s - 30 s - 1 m - 5 m + {timeOptions.map(opt => ( + + {opt.label} + + ))} @@ -346,7 +252,6 @@ export const EffectivenessAnalysisDetails = () => { - {/* Chart 1: Attention */} @@ -362,8 +267,8 @@ export const EffectivenessAnalysisDetails = () => { = 300 ? 0 : 'preserveStartEnd'} /> @@ -374,10 +279,10 @@ export const EffectivenessAnalysisDetails = () => { tickFormatter={(v) => `${v}%`} axisLine={false} tickLine={false} - tick={{ fontSize: 11, fill: COLORS.textSecondary }} + tick={{ fontSize: 11, fill: ANALYSIS_COLORS.textSecondary }} /> } + content={} cursor={{ stroke: '#40a9ff', strokeWidth: 1.5 }} /> { - - {/* Chart 2: Emotions */} @@ -404,29 +307,31 @@ export const EffectivenessAnalysisDetails = () => { = 300 ? 0 : 'preserveStartEnd'} /> - } /> + } /> { + dot={(props) => { const { cx, payload } = props; const emotion = EMOTION_POOL.find((e) => e.key === payload.emotionKey) || EMOTION_POOL[6]; return ( - + <> + {cx && {emotion.icon} - + } + ); }} /> diff --git a/src/pages/Webinars/form.tsx b/src/pages/Webinars/form.tsx index 34daaf7b8..9fa355184 100644 --- a/src/pages/Webinars/form.tsx +++ b/src/pages/Webinars/form.tsx @@ -100,13 +100,13 @@ const WebinarForm = () => { if (isNew) { response = await createWebinar(postData); if (response.success) { - history.push(`/courses/webinars/${response.data.id}`); + history.push(`/courses/webinars/webinar/${response.data.id}`); } } else { response = await updateWebinar(Number(webinar), postData); if (response.success) { validateCourseEdit(response.data); - history.push(`/courses/webinars/${response.data.id}/${tab}`); + history.push(`/courses/webinars/webinar/${response.data.id}/${tab}`); } } message.success(response.message); @@ -131,7 +131,7 @@ const WebinarForm = () => { return ( : + isNew ? : } header={{ breadcrumb: { @@ -175,7 +175,7 @@ const WebinarForm = () => { showConfirmModal: true, }); } else { - history.push(`/courses/webinars/${webinar}/${key}`); + history.push(`/courses/webinars/webinar/${webinar}/${key}`); } }, }} @@ -369,7 +369,7 @@ const WebinarForm = () => { info.file.response.data.image_url} @@ -414,7 +414,7 @@ const WebinarForm = () => { info.file.response.data.logotype_url} diff --git a/src/pages/Webinars/index.tsx b/src/pages/Webinars/index.tsx index e4a41d011..c9c0f76ce 100644 --- a/src/pages/Webinars/index.tsx +++ b/src/pages/Webinars/index.tsx @@ -1,8 +1,9 @@ import { PageContainer } from '@ant-design/pro-layout'; import type { ActionType, ProColumns } from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table'; +import ProCard from '@ant-design/pro-card'; import React, { useCallback, useRef, useState } from 'react'; -import { FormattedMessage, Link, useIntl } from 'umi'; +import { FormattedMessage, Link, useIntl, history, useParams } from 'umi'; import { deleteWebinar, generateYoutubeToken, webinars } from '@/services/escola-lms/webinars'; import { roundTo } from '@/utils/utils'; @@ -13,170 +14,42 @@ import { FireOutlined, PlusOutlined, } from '@ant-design/icons'; -import { Button, Popconfirm, Select, Tag, Tooltip, Typography, message } from 'antd'; -import { format } from 'date-fns'; - -import Tags from '@/components/Tags'; -import { DATETIME_FORMAT, DAY_FORMAT } from '@/consts/dates'; - +import { Button, Popconfirm, Tag, Typography, message } from 'antd'; import { useShowNotification } from '@/hooks/useMessage'; import TokenForm from './components/TokenForm'; +import EffectivenessAnalysis from '../Consultations/components/EffectivenessAnalysis'; export const TableColumns: ProColumns[] = [ { - title: , + title: , dataIndex: 'id', hideInSearch: true, - sorter: true, width: '80px', }, { - title: , - dataIndex: 'dateRange', - hideInSearch: false, - hideInForm: true, - hideInTable: true, - valueType: 'dateRange', - fieldProps: { - allowEmpty: [true, true], - }, - }, - { - title: , + title: , dataIndex: 'name', sorter: true, }, { - title: , + title: , dataIndex: 'status', - hideInSearch: true, - sorter: true, - renderFormItem: ({ type }) => { - if (type === 'form') { - return null; - } - return ( - - ); - }, - valueEnum: { - draft: { - text: ( - - - - ), - status: 'draft', - }, - archived: { - text: ( - - - - ), - status: 'archived', - }, - published: { - text: ( - - - - ), - status: 'published', - }, + draft: { text: }, + archived: { text: }, + published: { text: }, }, }, { - title: , + title: , dataIndex: 'product', - sorter: false, - valueType: 'textarea', - search: false, - render: (_, record) => { - if (record.product && record.product.price) { - return ( - - - - ); - } - return ( - - - - ); - }, - }, - { - title: , - dataIndex: 'duration', - hideInSearch: true, - sorter: true, - }, - { - title: , - dataIndex: 'active_from', - hideInSearch: true, - sorter: true, - render: (_, record) => format(new Date(record.active_from), DAY_FORMAT), - }, - { - title: , - dataIndex: 'active_to', - hideInSearch: true, - sorter: true, - render: (_, record) => format(new Date(record.active_to), DAY_FORMAT), - }, - { - title: , - dataIndex: 'tag', - key: 'tag', - sorter: false, - renderFormItem: (item, { type, defaultRender, ...rest }, form) => { - if (type === 'form') { - return null; - } - const stateType = form.getFieldValue('state'); - return ( - - ); - }, - render: (_, record) => ( - - {record.tags?.map((tag) => - typeof tag === 'object' ? ( - {tag.title} - ) : ( - {tag} - ), - )} - - ), + render: (_, record) => record.product?.price ? ( + + + + ) : , }, ]; @@ -186,127 +59,108 @@ const Webinars: React.FC = () => { const [loading, setLoading] = useState(false); const intl = useIntl(); const { showNotification } = useShowNotification(); + const { tab } = useParams<{ tab?: string }>(); + const activeTab = tab || 'list'; const handleRemove = useCallback( async (id: number) => { setLoading(true); - const hide = message.loading(); try { - await deleteWebinar(id).then((response) => { - setLoading(false); - if (response.success) { - showNotification(response); - } - }); - hide(); - setLoading(false); + const response = await deleteWebinar(id); + if (response.success) showNotification(response); actionRef.current?.reload(); - return true; } catch (error) { - hide(); - message.error(); + message.error(); + } finally { setLoading(false); - return false; } }, - [actionRef], + [showNotification], ); return ( - - headerTitle={intl.formatMessage({ - id: 'menu.Courses.Webinars', - defaultMessage: 'Webinars', - })} - loading={loading} - actionRef={actionRef} - rowKey="id" - search={{ - layout: 'vertical', + { + history.push(`/courses/webinars/${key}`); + }, }} - toolBarRender={() => [ - - - , - ]} - request={({ name, status, dateRange, pageSize, current, tag }, sort) => { - setLoading(true); - const sortArr = sort && Object.entries(sort)[0]; - const date_from = - dateRange && dateRange[0] ? format(new Date(dateRange[0]), DATETIME_FORMAT) : undefined; - const date_to = - dateRange && dateRange[1] ? format(new Date(dateRange[1]), DATETIME_FORMAT) : undefined; + > + }> + + headerTitle={intl.formatMessage({ id: 'menu.Courses.Webinars' })} + loading={loading} + actionRef={actionRef} + rowKey="id" + search={{ layout: 'vertical' }} + toolBarRender={() => [ + + + , + ]} + request={(params, sort) => { + const sortArr = sort ? Object.entries(sort)[0] : null; + return webinars({ + name: params.name, + per_page: params.pageSize, + page: params.current, + status: params.status, + order_by: sortArr?.[0], + order: sortArr ? (sortArr[1] === 'ascend' ? 'ASC' : 'DESC') : undefined, + }).then((response) => { + const res = response as API.DefaultMetaResponse; + + if (res && 'success' in response && response.success) { + return { + data: response.data, + total: response.meta.total, + success: true, + }; + } - return webinars({ - name, - per_page: pageSize, - page: current, - date_from, - date_to, - status, - tags: tag, - order_by: sortArr && sortArr[0], - order: sortArr ? (sortArr[1] === 'ascend' ? 'ASC' : 'DESC') : undefined, - }) - .then((response) => { - if (response.success) { return { - data: response.data, - total: response.meta.total, - success: true, + data: [], + total: 0, + success: false, }; - } - return []; - }) - .catch(async (error) => { - const err = await error.response.json(); - console.log(err); - if (err.data.code === 400 && err.data.message.includes('Youtube')) { - message.error(err.data.message); - setGenarateToken(true); - } - return []; - }) - .finally(() => { - setLoading(false); - }); - }} - columns={[ - ...TableColumns, - { - title: , - dataIndex: 'option', - valueType: 'option', - width: '10%', - render: (_, record) => [ - - }> - , ]} - request={(params, sort) => { + request={async (params, sort) => { + setLoading(true); + const { name, status, dateRange, pageSize, current, tag } = params; const sortArr = sort ? Object.entries(sort)[0] : null; - return webinars({ - name: params.name, - per_page: params.pageSize, - page: params.current, - status: params.status, - order_by: sortArr?.[0], - order: sortArr ? (sortArr[1] === 'ascend' ? 'ASC' : 'DESC') : undefined, - }).then((response) => { - const res = response as API.DefaultMetaResponse; + const date_from = + dateRange && dateRange[0] ? format(new Date(dateRange[0]), DAY_FORMAT) : undefined; + const date_to = + dateRange && dateRange[1] ? format(new Date(dateRange[1]), DAY_FORMAT) : undefined; - if (res && 'success' in response && response.success) { + try { + const response = await webinars({ + name, + per_page: pageSize, + page: current, + date_from, + date_to, + status, + tags: tag, + order_by: sortArr?.[0], + order: sortArr ? (sortArr[1] === 'ascend' ? 'ASC' : 'DESC') : undefined, + }); + + if (response && response.success) { return { data: response.data, total: response.meta.total, @@ -146,12 +248,37 @@ const Webinars: React.FC = () => { }; } - return { - data: [], - total: 0, - success: false, - }; - }); + return { data: [], total: 0, success: false }; + } catch (error) { + try { + if (error && typeof error === 'object' && 'response' in error) { + const response = (error as any).response; + const err = await response?.json(); + + console.error('API Error:', err); + + if (err?.data?.code === 400 && err?.data?.message?.includes('Youtube')) { + message.error(err.data.message); + setGenarateToken(true); + } else { + message.error( + intl.formatMessage({ + id: 'error.fetch', + defaultMessage: 'Failed to fetch webinars', + }), + ); + } + } else { + message.error(intl.formatMessage({ id: 'error.fetch' })); + } + } catch (parseError) { + console.error('Failed to parse error response', parseError); + } + + return { data: [], total: 0, success: false }; + } finally { + setLoading(false); + } }} columns={[ ...TableColumns, diff --git a/src/services/escola-lms/consultations.ts b/src/services/escola-lms/consultations.ts index d8000f15d..90ce6a195 100644 --- a/src/services/escola-lms/consultations.ts +++ b/src/services/escola-lms/consultations.ts @@ -1,3 +1,4 @@ +import type { RecommenderParams } from '@/pages/Consultations/consultations'; import type { AxiosRequestConfig } from '@umijs/max'; import { request } from 'umi'; @@ -94,17 +95,12 @@ export async function changeTermDate( } /** GET /api/admin/recommender/terms/{modelType} */ -export async function getRecommenderTerms( - modelType: string, - params?: API.ConsultationsParams & { date_from?: string; date_to?: string }, - options?: AxiosRequestConfig, -) { +export async function getRecommenderTerms(modelType: string, params?: RecommenderParams) { return request>( `/api/admin/recommender/terms/${modelType}`, { method: 'GET', params, - ...(options || {}), }, ); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index d72a65541..bc02a32e5 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -633,3 +633,13 @@ export const formatTime = (seconds: number) => { const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; }; + +export const formatExpirationTime = (ms: number | null) => { + if (!ms || ms <= 0) return '0s'; + const totalSeconds = Math.floor(ms / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + 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`; +};