From bdc7dd2bb3bbbbd39f32a23469d7a341bb184fc0 Mon Sep 17 00:00:00 2001 From: shats Date: Wed, 11 Feb 2026 15:51:05 +0700 Subject: [PATCH] refactor(MikrotikBackupsManager): optimize diff computation and synchronize scrolling between panels --- frontend/src/MikrotikBackupsManager.jsx | 82 ++++++++++++++++++++----- 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/frontend/src/MikrotikBackupsManager.jsx b/frontend/src/MikrotikBackupsManager.jsx index fc1a66f..77c1edc 100644 --- a/frontend/src/MikrotikBackupsManager.jsx +++ b/frontend/src/MikrotikBackupsManager.jsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import * as Diff from 'diff'; import api from './lib/api.js'; import { useNotify } from './components/NotifyProvider.jsx'; @@ -75,9 +75,9 @@ const diffAddedBg = { borderLeft: '3px solid rgba(63, 185, 80, 0.9)', }; -function DiffPanel({ lines, side }) { +function DiffPanel({ lines, side, innerRef, onScroll }) { return ( -
+    
       {lines.length === 0 ? (
         # пусто
       ) : (
@@ -141,6 +141,9 @@ function MikrotikBackupsManager() {
   const [diffResult, setDiffResult] = useState(null);
   const [diffLoading, setDiffLoading] = useState(false);
   const [diffModalOpen, setDiffModalOpen] = useState(false);
+  const leftDiffRef = useRef(null);
+  const rightDiffRef = useRef(null);
+  const isSyncingScrollRef = useRef(false);
 
   // UI-состояние выбора сервера для просмотра бэкапов (в стиле /filters)
   const [serverSearch, setServerSearch] = useState('');
@@ -254,12 +257,41 @@ function MikrotikBackupsManager() {
     return filteredBackupServers.slice(start, start + backupPageSize);
   }, [filteredBackupServers, backupPage, backupTotalPages]);
 
-  const diffLines = useMemo(
-    () => (diffResult?.configA != null && diffResult?.configB != null
-      ? getDiffLines(diffResult.configA, diffResult.configB)
-      : { left: [], right: [] }),
-    [diffResult?.configA, diffResult?.configB],
-  );
+  const diffComputed = useMemo(() => {
+    if (diffResult?.configA == null || diffResult?.configB == null) {
+      return {
+        left: [],
+        right: [],
+        stats: { removed: 0, added: 0, total: 0 },
+      };
+    }
+    const { left, right } = getDiffLines(diffResult.configA, diffResult.configB);
+    const removed = left.filter((l) => l.type === 'removed').length;
+    const added = right.filter((l) => l.type === 'added').length;
+    return {
+      left,
+      right,
+      stats: { removed, added, total: removed + added },
+    };
+  }, [diffResult?.configA, diffResult?.configB]);
+
+  const syncScroll = (source) => {
+    const leftEl = leftDiffRef.current;
+    const rightEl = rightDiffRef.current;
+    if (!leftEl || !rightEl) return;
+    const current = source === 'left' ? leftEl : rightEl;
+    const other = source === 'left' ? rightEl : leftEl;
+
+    if (isSyncingScrollRef.current) return;
+    isSyncingScrollRef.current = true;
+
+    const maxCurrent = current.scrollHeight - current.clientHeight;
+    const ratio = maxCurrent > 0 ? current.scrollTop / maxCurrent : 0;
+    const maxOther = other.scrollHeight - other.clientHeight;
+    other.scrollTop = ratio * (maxOther > 0 ? maxOther : 0);
+
+    isSyncingScrollRef.current = false;
+  };
 
   const handleBackupSort = (field) => {
     setBackupSortField((prevField) => {
@@ -891,20 +923,34 @@ function MikrotikBackupsManager() {
           
         ) : (
           <>
-            {!diffResult.same && (
-              
- - Конфигурации отличаются — слева A, справа B. Проверьте различия перед откатом. -
- )} +
+ + {!diffResult.same ? ( + <> + + Конфигурации отличаются — слева A, справа B. Проверьте различия перед откатом. + + ) : ( + 'Конфигурации идентичны — изменений нет.' + )} + + + Всего изменений:{' '} + {diffComputed.stats.total} + +{diffComputed.stats.added} + −{diffComputed.stats.removed} + +
A: {diffResult.a?.key}
syncScroll('left')} />
@@ -912,8 +958,10 @@ function MikrotikBackupsManager() { B: {diffResult.b?.key}
syncScroll('right')} />