From bead4c56ca81544655867b5bb39bb8e6595279fb Mon Sep 17 00:00:00 2001 From: toximu Date: Fri, 26 Jun 2026 14:12:08 +0300 Subject: [PATCH 01/16] add getting match options --- src/pages/WorkspacePage.jsx | 30 +++++++++++++++++-- .../components/workspace/RightWorkspace.jsx | 20 +++++++++---- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/pages/WorkspacePage.jsx b/src/pages/WorkspacePage.jsx index d532e30..d1d239a 100644 --- a/src/pages/WorkspacePage.jsx +++ b/src/pages/WorkspacePage.jsx @@ -11,6 +11,7 @@ import './WorkspacePage.css'; import MatchResultOverlay from './MatchResultOverlay'; import { useMatchStore } from "./useMatchStore.js"; import {useMatchSession} from "./useMatchSession.js"; +import {api} from "../api/axiosConfig.js"; function WorkspacePage() { const {matchId} = useParams(); @@ -20,7 +21,7 @@ function WorkspacePage() { const [isSwapped, setIsSwapped] = useState(false); const [showSettings, setShowSettings] = useState(false); const { userSubmissions } = useMatchSession(); - + const [matchOptions, setMatchOptions] = useState(null); const matchResult = useMatchStore((state) => state.matchResult); useEffect(() => { @@ -31,6 +32,29 @@ function WorkspacePage() { } }, [isDarkMode]); + useEffect(() => { + let ignore = false; + + const fetchOptions = async () => { + try { + const response = await api.get(`/match/${matchId}/options`); + if (!ignore) { + setMatchOptions(response.data); + } + } catch (error) { + console.error("Ошибка при загрузке опций матча:", error); + } + }; + + if (matchId) { + fetchOptions(); + } + + return () => { + ignore = true; + }; + }, [matchId]); + const handleLogout = () => { logout(); navigate('/login'); @@ -43,9 +67,9 @@ function WorkspacePage() {
{isSwapped ? ( - + ) : ( - + )} diff --git a/src/pages/components/workspace/RightWorkspace.jsx b/src/pages/components/workspace/RightWorkspace.jsx index cea51a7..8263bf5 100644 --- a/src/pages/components/workspace/RightWorkspace.jsx +++ b/src/pages/components/workspace/RightWorkspace.jsx @@ -9,7 +9,7 @@ const LANGUAGE_MAP = { 63: 'JavaScript' }; -export default function RightWorkspace({ position = 'right', submissions = [] }) { +export default function RightWorkspace({ position = 'right', submissions = [], matchOptions = {} }) { const [isCollapsed, setIsCollapsed] = useState(false); const [activeTab, setActiveTab] = useState('description'); const panelRef = useRef(null); @@ -70,10 +70,20 @@ export default function RightWorkspace({ position = 'right', submissions = [] })
{activeTab === 'description' ? ( -
-

Заголовок задачи

-

Текст условия задачи будет загружаться сюда...

-
+
+ {/* Выводим переменную с названием */} +

+ {matchOptions.title || "Без названия"} +

+ + {/* Выводим переменную с описанием. whiteSpace сохранит переносы строк */} +
+ {matchOptions.statement || "Описание отсутствует"} +
+
) : (
{(!submissions || !Array.isArray(submissions) || submissions.length === 0) ? ( From 2c2291c75a45082c16f83a533f0f06401799c74a Mon Sep 17 00:00:00 2001 From: toximu Date: Sat, 27 Jun 2026 00:28:34 +0300 Subject: [PATCH 02/16] fix workspace --- src/pages/WorkspacePage.jsx | 127 ++++++------- .../components/workspace/LeftWorkspace.jsx | 23 +-- .../components/workspace/RightWorkspace.jsx | 170 +++++++++--------- 3 files changed, 154 insertions(+), 166 deletions(-) diff --git a/src/pages/WorkspacePage.jsx b/src/pages/WorkspacePage.jsx index d1d239a..54ea1be 100644 --- a/src/pages/WorkspacePage.jsx +++ b/src/pages/WorkspacePage.jsx @@ -14,23 +14,23 @@ import {useMatchSession} from "./useMatchSession.js"; import {api} from "../api/axiosConfig.js"; function WorkspacePage() { - const {matchId} = useParams(); - const { logout } = useUser(); - const navigate = useNavigate(); - const [isDarkMode, setIsDarkMode] = useState(true); - const [isSwapped, setIsSwapped] = useState(false); - const [showSettings, setShowSettings] = useState(false); - const { userSubmissions } = useMatchSession(); - const [matchOptions, setMatchOptions] = useState(null); - const matchResult = useMatchStore((state) => state.matchResult); - - useEffect(() => { - if (isDarkMode) { - document.body.classList.remove('light-theme'); - } else { - document.body.classList.add('light-theme'); - } - }, [isDarkMode]); + const {matchId} = useParams(); + const { logout } = useUser(); + const navigate = useNavigate(); + const [isDarkMode, setIsDarkMode] = useState(true); + const [isSwapped, setIsSwapped] = useState(false); + const [showSettings, setShowSettings] = useState(false); + const { userSubmissions } = useMatchSession(); + const [matchOptions, setMatchOptions] = useState(null); + const matchResult = useMatchStore((state) => state.matchResult); + + useEffect(() => { + if (isDarkMode) { + document.body.classList.remove('light-theme'); + } else { + document.body.classList.add('light-theme'); + } + }, [isDarkMode]); useEffect(() => { let ignore = false; @@ -39,6 +39,7 @@ function WorkspacePage() { try { const response = await api.get(`/match/${matchId}/options`); if (!ignore) { + console.log(`options : ${response.data.statement}`); setMatchOptions(response.data); } } catch (error) { @@ -55,55 +56,55 @@ function WorkspacePage() { }; }, [matchId]); - const handleLogout = () => { - logout(); - navigate('/login'); - }; - - return ( -
-
setShowSettings(true)} /> - -
- - {isSwapped ? ( - - ) : ( - - )} - - -
-
+ const handleLogout = () => { + logout(); + navigate('/login'); + }; + + return ( +
+
setShowSettings(true)} /> + +
+ + {isSwapped ? ( + + ) : ( + + )} + + +
+
+ + {isSwapped ? ( + + ) : ( + + )} +
+
+ +
+ + setShowSettings(false)} + onLogout={handleLogout} + themeConfig={{ isDarkMode, setIsDarkMode }} + workspaceConfig={{ isSwapped, setIsSwapped }} + /> - {isSwapped ? ( - - ) : ( - + {matchResult && ( + )} - -
- -
- - setShowSettings(false)} - onLogout={handleLogout} - themeConfig={{ isDarkMode, setIsDarkMode }} - workspaceConfig={{ isSwapped, setIsSwapped }} - /> - - {matchResult && ( - - )} -
- ); +
+ ); } export default WorkspacePage; \ No newline at end of file diff --git a/src/pages/components/workspace/LeftWorkspace.jsx b/src/pages/components/workspace/LeftWorkspace.jsx index c21cc00..b9702d8 100644 --- a/src/pages/components/workspace/LeftWorkspace.jsx +++ b/src/pages/components/workspace/LeftWorkspace.jsx @@ -10,23 +10,13 @@ import {multipartApi} from "../../../api/axiosConfig.js"; const DEFAULT_CODE = "# Напишите ваш код здесь...\n"; -// const languages = [ -// -// { id: 71, name: "Python" }, -// -// { id: 54, name: "C++" }, -// -// { id: 63, name: "JavaScript" } -// -// ]; -// -// const idToLanguage = { -// 71 : 'python', -// 54 : 'cpp', -// 63 : 'javascript' -// }; +const enumToLanguage = { + 'PY' : 'python', + 'CPP' : 'cpp', + 'JAVA' : 'java' +}; -export default function LeftWorkspace({isDarkMode, position = 'left', matchId, submissions = []}) { +export default function LeftWorkspace({isDarkMode, position = 'left', matchId, submissions = [], matchOptions = null}) { // const [languageId, setLanguageId] = useState(71); const [code, setCode] = useState(DEFAULT_CODE); const [showResetModal, setShowResetModal] = useState(false); @@ -157,6 +147,7 @@ export default function LeftWorkspace({isDarkMode, position = 'left', matchId, s height="100%" theme={isDarkMode ? "vs-dark" : "light"} value={code} + language={enumToLanguage[matchOptions?.language] || 'python'} onChange={(value) => setCode(value)} options={{ fontSize: 15, diff --git a/src/pages/components/workspace/RightWorkspace.jsx b/src/pages/components/workspace/RightWorkspace.jsx index 8263bf5..5c8c108 100644 --- a/src/pages/components/workspace/RightWorkspace.jsx +++ b/src/pages/components/workspace/RightWorkspace.jsx @@ -9,7 +9,7 @@ const LANGUAGE_MAP = { 63: 'JavaScript' }; -export default function RightWorkspace({ position = 'right', submissions = [], matchOptions = {} }) { +export default function RightWorkspace({ position = 'right', submissions = [], matchOptions = null }) { const [isCollapsed, setIsCollapsed] = useState(false); const [activeTab, setActiveTab] = useState('description'); const panelRef = useRef(null); @@ -27,98 +27,94 @@ export default function RightWorkspace({ position = 'right', submissions = [], m }; return ( - setIsCollapsed(true)} - onExpand={() => setIsCollapsed(false)} - defaultSize={50} - minSize={4} - className={`column-container ${position}-column`} - > - {isCollapsed ? ( -
panelRef.current?.expand()} - title="Развернуть" - > -
- Условие & Метрики -
-
- ) : ( - - -
-
setActiveTab('description')} - > - - Условие задачи -
- -
setActiveTab('submissions')} - > - - Сабмиты -
-
- -
- {activeTab === 'description' ? ( -
- {/* Выводим переменную с названием */} -

- {matchOptions.title || "Без названия"} -

+ setIsCollapsed(true)} + onExpand={() => setIsCollapsed(false)} + defaultSize={50} + minSize={4} + className={`column-container ${position}-column`} + > + {isCollapsed ? ( +
panelRef.current?.expand()} + title="Развернуть" + > +
+ Условие & Метрики +
+
+ ) : ( + + +
+
setActiveTab('description')} + > + + Условие задачи +
- {/* Выводим переменную с описанием. whiteSpace сохранит переносы строк */} -
- {matchOptions.statement || "Описание отсутствует"} -
+
setActiveTab('submissions')} + > + + Сабмиты
- ) : ( -
- {(!submissions || !Array.isArray(submissions) || submissions.length === 0) ? ( -

Посылок пока нет или данные загружаются...

+
+ +
+ {activeTab === 'description' ? ( +
+ {matchOptions ? ( + <> +

{matchOptions.title || "Без названия"}

+
{matchOptions.statement}
+ + ) : ( +

Загрузка условия задачи...

+ )} +
) : ( -
- {submissions.map((sub, index) => ( -
-
- {sub.status.replace(/_/g, ' ')} -
-
- ID: {sub.id} | Язык: {LANGUAGE_MAP[sub.languageId] || sub.languageId} | Время: {formatDate(sub.createdAt)} -
-
- ))} -
+
+ {(!submissions || !Array.isArray(submissions) || submissions.length === 0) ? ( +

Посылок пока нет или данные загружаются...

+ ) : ( +
+ {submissions.map((sub, index) => ( +
+
+ {sub.status.replace(/_/g, ' ')} +
+
+ ID: {sub.id} | Язык: {LANGUAGE_MAP[sub.languageId] || sub.languageId} | Время: {formatDate(sub.createdAt)} +
+
+ ))} +
+ )} +
)}
- )} -
-
+
- -
-
+ +
+
- - -
-

Данные по метрикам оппонента...

-
-
- - )} - + + +
+

Данные по метрикам оппонента...

+
+
+ + )} + ); } \ No newline at end of file From 3750f4513e6c323b55c781899250cc4f9301962a Mon Sep 17 00:00:00 2001 From: toximu Date: Sat, 27 Jun 2026 15:26:40 +0300 Subject: [PATCH 03/16] add display of level and language --- .../components/workspace/LeftWorkspace.jsx | 68 +++++++++++++++---- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/src/pages/components/workspace/LeftWorkspace.jsx b/src/pages/components/workspace/LeftWorkspace.jsx index b9702d8..a5e4d54 100644 --- a/src/pages/components/workspace/LeftWorkspace.jsx +++ b/src/pages/components/workspace/LeftWorkspace.jsx @@ -11,9 +11,21 @@ import {multipartApi} from "../../../api/axiosConfig.js"; const DEFAULT_CODE = "# Напишите ваш код здесь...\n"; const enumToLanguage = { - 'PY' : 'python', - 'CPP' : 'cpp', - 'JAVA' : 'java' + 'PY': 'python', + 'CPP': 'cpp', + 'JAVA': 'java' +}; + +const complexityStyles = { + 'EASY': {label: 'Easy', color: '#22c55e', bg: 'rgba(34, 197, 94, 0.1)'}, + 'MEDIUM': {label: 'Medium', color: '#f97316', bg: 'rgba(249, 115, 22, 0.1)'}, + 'HARD': {label: 'Hard', color: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)'} +}; + +const displayLanguages = { + 'PY': 'Python', + 'CPP': 'C++', + 'JAVA': 'Java' }; export default function LeftWorkspace({isDarkMode, position = 'left', matchId, submissions = [], matchOptions = null}) { @@ -115,17 +127,43 @@ export default function LeftWorkspace({isDarkMode, position = 'left', matchId, s - {/* setLanguageId(e.target.value)}*/} - {/*>*/} - {/* {languages.map((lang) => (*/} - {/* */} - {/* ))}*/} - {/**/} + {matchOptions && ( +
+ {/* Бейдж Языка */} + + {displayLanguages[matchOptions.language] || matchOptions.language} + + + {/* Бейдж Сложности */} + {matchOptions.problemLevel && ( + + {complexityStyles[matchOptions.problemLevel.toUpperCase()]?.label || matchOptions.problemLevel} + + )} +
+ )} - ); - }) - ) : ( -

Опций не осталось

- )} +
+ ★ {opponent.rating ?? 1000}
- ); - })} -
-
- setShowSettings(false)} - onLogout={handleLogout} - /> + ) : ( +
Загрузка оппонента...
+ )} + + + {/* Индикатор хода */} +
+ {isMyTurn ? "Вы баните опцию" : "Оппонент банит опцию"} +
+ + +
+
+ {categories.map(({category, options, active}) => { + + + const remainCount = options ? options.filter(o => !o.banned).length : 0; + + const isCategoryFinished = !active && remainCount === 1; + + return ( +
+
+

{category}

+ {active && Текущий выбор} + {isCategoryFinished && Выбрано} +
+ +
+ {options && options.length > 0 ? ( + // Копируем массив через [...] и сортируем по свойству option (строка с именем) + [...options] + .sort((a, b) => a.option.localeCompare(b.option)) + .map(({option, banned}) => { + const isClickable = active && !banned; + + let btnClass = "draft-ban-btn"; + if (banned) { + btnClass += " banned"; + } else if (isCategoryFinished) { + btnClass += " selected-winner"; + } + + return ( + + ); + }) + ) : ( +

Опций не осталось

+ )} +
+
+ ); + })} +
+
+ setShowSettings(false)} + onLogout={handleLogout} + /> + ); }; From f48f259d079b9c7e2d24c66a6d5c52987ffed6b8 Mon Sep 17 00:00:00 2001 From: toximu Date: Sat, 27 Jun 2026 20:54:32 +0300 Subject: [PATCH 13/16] fix smtg --- src/pages/DraftPage.jsx | 4 ++-- src/pages/WorkspacePage.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/DraftPage.jsx b/src/pages/DraftPage.jsx index 3c4e7b3..0f3fdb6 100644 --- a/src/pages/DraftPage.jsx +++ b/src/pages/DraftPage.jsx @@ -16,8 +16,8 @@ const DraftPage = () => { const [wsTimeLeft, setWsTimeLeft] = useState(null); const [showSettings, setShowSettings] = useState(false); - const amIFirstUser = user?.id === sessionData.firstUserId; - const isMyTurn = amIFirstUser ? sessionData.firstUserMove : !sessionData.firstUserMove; + const amIFirstUser = user?.id === sessionData?.firstUserId; + const isMyTurn = amIFirstUser ? sessionData?.firstUserMove : !sessionData?.firstUserMove; useEffect(() => { if (!isConnected || !sessionData) return; diff --git a/src/pages/WorkspacePage.jsx b/src/pages/WorkspacePage.jsx index e7763ca..7fde3ff 100644 --- a/src/pages/WorkspacePage.jsx +++ b/src/pages/WorkspacePage.jsx @@ -20,7 +20,7 @@ function WorkspacePage() { const [isDarkMode, setIsDarkMode] = useState(true); const [isSwapped, setIsSwapped] = useState(false); const [showSettings, setShowSettings] = useState(false); - const { userSubmissions } = useMatchSession(); + const { userSubmissions } = useMatchSession(matchId); const [matchOptions, setMatchOptions] = useState(null); const matchResult = useMatchStore((state) => state.matchResult); From 2abd9eacb2d8e2046da61db7ae38a6b36ece17ea Mon Sep 17 00:00:00 2001 From: toximu Date: Sat, 27 Jun 2026 21:12:51 +0300 Subject: [PATCH 14/16] fix smth --- src/pages/DraftPage.jsx | 2 +- src/pages/WorkspacePage.jsx | 2 +- src/pages/components/workspace/RightWorkspace.jsx | 7 ------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/pages/DraftPage.jsx b/src/pages/DraftPage.jsx index 0f3fdb6..464172a 100644 --- a/src/pages/DraftPage.jsx +++ b/src/pages/DraftPage.jsx @@ -13,7 +13,7 @@ const DraftPage = () => { const {matchId} = useParams(); const {sessionData, sendBan, isConnected, opponent} = useMatchSession(matchId); const {user, logout} = useUser(); - const [wsTimeLeft, setWsTimeLeft] = useState(null); + const [wsTimeLeft, setWsTimeLeft] = useState(20); const [showSettings, setShowSettings] = useState(false); const amIFirstUser = user?.id === sessionData?.firstUserId; diff --git a/src/pages/WorkspacePage.jsx b/src/pages/WorkspacePage.jsx index 7fde3ff..5249578 100644 --- a/src/pages/WorkspacePage.jsx +++ b/src/pages/WorkspacePage.jsx @@ -43,7 +43,7 @@ function WorkspacePage() { setMatchOptions(response.data); } } catch (error) { - console.error("Ошибка при загрузке опций матча:", error); + console.error("Ошибка при загрузке опций матча:", error.message); } }; diff --git a/src/pages/components/workspace/RightWorkspace.jsx b/src/pages/components/workspace/RightWorkspace.jsx index 5c8c108..4311d0d 100644 --- a/src/pages/components/workspace/RightWorkspace.jsx +++ b/src/pages/components/workspace/RightWorkspace.jsx @@ -106,13 +106,6 @@ export default function RightWorkspace({ position = 'right', submissions = [], m
- - - -
-

Данные по метрикам оппонента...

-
-
)} From 26fa2c2c9395f5e39ed3223330e937fbbeaca862 Mon Sep 17 00:00:00 2001 From: toximu Date: Sat, 27 Jun 2026 21:17:55 +0300 Subject: [PATCH 15/16] fix smth --- src/App.css | 6 ++++++ src/pages/components/ui/ActionTimer.jsx | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/App.css b/src/App.css index 851b489..aaa1cae 100644 --- a/src/App.css +++ b/src/App.css @@ -46,6 +46,12 @@ html, body, #root { -webkit-font-smoothing: antialiased; } +.draft-container { + flex-grow: 1; + overflow-y: auto; + padding: 20px; +} + .page-header { display: flex; align-items: center; diff --git a/src/pages/components/ui/ActionTimer.jsx b/src/pages/components/ui/ActionTimer.jsx index ec0380b..b9c0682 100644 --- a/src/pages/components/ui/ActionTimer.jsx +++ b/src/pages/components/ui/ActionTimer.jsx @@ -30,7 +30,7 @@ export default function ActionTimer({ timeLeft, totalDuration = 20, isMyTurn = t width: `${percentage}%`, height: '100%', backgroundColor: getBarColor(), - transition: 'width 1s linear, background-color 0.5s ease', + transition: 'width 0.1s linear, background-color 0.5s ease', }} /> ); From 34e2336b2c31bd6432cfa93f0c48737d832c2466 Mon Sep 17 00:00:00 2001 From: toximu Date: Sun, 28 Jun 2026 12:29:57 +0300 Subject: [PATCH 16/16] fix tests --- src/pages/WorkspacePage.test.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/pages/WorkspacePage.test.jsx b/src/pages/WorkspacePage.test.jsx index 0f99c6f..4cd24a2 100644 --- a/src/pages/WorkspacePage.test.jsx +++ b/src/pages/WorkspacePage.test.jsx @@ -42,12 +42,6 @@ vi.mock('../context/UserContext', () => ({ user: { id: 'test-user-id', nickname: 'CodzillaPro' }, logout: vi.fn(), }), -vi.mock('./useMatchSession.js', () => ({ - useMatchSession: () => ({ userSubmissions: [] }), -})); - -vi.mock('./useMatchStore.js', () => ({ - useMatchStore: (selector) => selector({ matchResult: null }), })); describe('WorkspacePage', () => {