нужно встроить загрузку performFetchRoles, когда загружается админка

import React, { useRef } from 'react';
import {
  Button,
  LoadIndicator,
  TextBox,
  DataGrid,
  Autocomplete,
} from 'devextreme-react';
import {
  Column,
  Paging,
  Pager,
  Editing,
  RequiredRule,
  Button as DataGridButton,
} from 'devextreme-react/data-grid';
import { useCallback, useEffect, useState } from 'react';
import 'devextreme/dist/css/dx.common.css';
import 'devextreme/dist/css/dx.light.css';
import DataSource from 'devextreme/data/data_source';
import CustomStore from 'devextreme/data/custom_store';
import { useFetching } from './useFetching';
import ADAPIAuth from './api';
// Импортируем диалог подтверждения
import { confirm } from 'devextreme/ui/dialog';

const RECENT_LOGINS_KEY = 'recentLogins';
const MAX_RECENT_LOGINS = 5;

function App() {
  const [token, setToken] = useState(() =>
    window.localStorage.getItem('token')
  );
  const [message, setMessage] = useState('');
  const [error, setError] = useState(null);
  const [statusData, setStatusData] = useState(null);
  const [usersDataSource, setUsersDataSource] = useState(null);
  const [recentLogins, setRecentLogins] = useState(() => {
    try {
      const savedLogins = window.localStorage.getItem(RECENT_LOGINS_KEY);
      return savedLogins ? JSON.parse(savedLogins) : [];
    } catch {
      return [];
    }
  });

  const loginAutocompleteRef = useRef(null);
  const passwdRef = useRef(null);
  const dataGridRef = useRef(null);

  const [performLogin, isLoginLoading] = useFetching(
    async (login, password) => {
      try {
        const response = await ADAPIAuth.postLogin(login, password);
        const accessToken = response.data.token.access_token;
        setToken(accessToken);
        setMessage('Вы успешно вошли в систему!');

        const updatedLogins = recentLogins.filter((l) => l !== login);
        updatedLogins.unshift(login);
        const trimmedLogins = updatedLogins.slice(0, MAX_RECENT_LOGINS);

        setRecentLogins(trimmedLogins);
        window.localStorage.setItem(
          RECENT_LOGINS_KEY,
          JSON.stringify(trimmedLogins)
        );
      } catch (e) {
        setError(e);
        setToken(null);
      }
    }
  );

  const [performCheckStatus, isStatusLoading] = useFetching(async () => {
    if (!token) return;
    try {
      const response = await ADAPIAuth.getTokenInfo(token);
      setStatusData(response.data);
      setMessage('Токен действителен. Статус пользователя получен.');
    } catch (e) {
      setError(e);
      setStatusData(null);
      if (e.response?.status === 401 || e.response?.status === 403) {
        setToken(null);
        setMessage('Сессия истекла. Пожалуйста, войдите снова.');
      }
    }
  });

  const [performLogout, isLogoutLoading] = useFetching(async () => {
    if (!token) return;
    try {
      await ADAPIAuth.logout(token);
    } catch (e) {
      console.error('Ошибка при выходе на бэкенде:', e);
    } finally {
      setToken(null);
      setMessage('Вы успешно вышли из системы.');
    }
  });

  const [performEndUserSession, isEndUserSessionLoading] = useFetching(
    async (userId) => {
      if (!token) return;
      try {
        // Предполагаем, что в api.js есть метод endUserSession
        await ADAPIAuth.endUserSession(token, userId);
        setMessage(`Сессия пользователя с ID ${userId} успешно завершена.`);

        if (dataGridRef.current) {
          dataGridRef.current.instance.refresh();
        }
      } catch (e) {
        setError(e);
        throw e;
      }
    }
  );

  const [performFetchRoles, isRolesLoading] = useFetching(async () => {
    try {
      const response = await ADAPIAuth.getRoles(token);
      setAvailableRoles(response.data);
    } catch (e) {
      // Ошибку можно обработать, но для простоты пока игнорируем
      console.error('Failed to fetch roles:', e);
    }
  });

  const clearUiState = () => {
    setMessage('');
    setError(null);
  };

  const handleLogin = useCallback(() => {
    clearUiState();
    performLogin(
      loginAutocompleteRef.current.instance.option('value'),
      passwdRef.current.instance.option('value')
    );
  }, [performLogin, recentLogins]);

  const handleCheckStatus = useCallback(() => {
    clearUiState();
    performCheckStatus();
  }, [performCheckStatus]);

  const handleLogout = useCallback(() => {
    clearUiState();
    performLogout();
  }, [performLogout]);

  const handleFetchUsers = useCallback(() => {
    clearUiState();
    setUsersDataSource(
      new DataSource({
        store: new CustomStore({
          key: 'id',
          load: async () => {
            try {
              const response = await ADAPIAuth.getAllUsers(token);
              setMessage('Список пользователей успешно загружен.');
              return response.data;
            } catch (e) {
              setError(e);
              throw e;
            }
          },
          update: async (key, values) => {
            if (values.password === undefined || values.password === '') {
              delete values.password;
            }
            try {
              await ADAPIAuth.updateUser(token, key, values);
              setMessage(`Пользователь с ID ${key} успешно обновлен.`);
            } catch (e) {
              setError(e);
              throw e;
            }
          },
          insert: async (values) => {
            try {
              await ADAPIAuth.createUser(token, values);
              setMessage('Пользователь успешно создан.');
            } catch (e) {
              setError(e);
              throw e;
            }
          },
        }),
      })
    );
  }, [token]);

  const handleEndUserSession = useCallback(
    (userData) => {
      clearUiState();

      // Проверяем, есть ли данные о текущем админе и совпадает ли его ID
      // с ID пользователя в строке таблицы.
      if (statusData && statusData.id === userData.id) {
        alert('Вы не можете завершить собственный сеанс.');
        return;
      }

      const result = confirm(
        `Вы уверены, что хотите завершить все сеансы для пользователя <b>${userData.username}</b>?`,
        'Подтверждение действия'
      );
      result.then((dialogResult) => {
        if (dialogResult) {
          performEndUserSession(userData.id);
        }
      });
    },
    [performEndUserSession]
  );

  useEffect(() => {
    if (token) {
      window.localStorage.setItem('token', token);
    } else {
      window.localStorage.removeItem('token');
      setStatusData(null);
      setUsersDataSource(null);
      if (loginAutocompleteRef.current && passwdRef.current) {
        passwdRef.current.instance.reset();
      }
    }
  }, [token]);

  const onEditorPreparing = (e) => {
    if (e.parentType !== 'dataRow') {
      return;
    }
    if (e.dataField === 'login') {
      e.editorOptions.disabled = !e.row.isNewRow;
    }
    if (e.dataField === 'password') {
      e.editorName = 'dxTextBox';
      e.editorOptions.mode = 'password';
      e.editorOptions.placeholder = e.row.isNewRow
        ? ''
        : 'Введите для смены...';
    }
    if (e.dataField === 'delete_mark' && e.row.isNewRow) {
      e.cancel = true;
    }
  };

  const onInitNewRow = (e) => {
    e.data.role = 'Контролер';
  };

  const handleLoginFocus = () => {
    if (loginAutocompleteRef.current) {
      loginAutocompleteRef.current.instance.open();
    }
  };

  const isLoading =
    isLoginLoading ||
    isStatusLoading ||
    isLogoutLoading ||
    isEndUserSessionLoading;
  const isAdmin = statusData?.role === 'Администратор';
  const renderButtonX = useCallback(
    ({ data }) => {
      const canEndSession = data.session_exists && !isLoading;

      return (
        <div style={{ display: 'flex', justifyContent: 'center' }}>
          <Button
            icon="runner" // Иконка "выход"
            hint="Завершить сеанс"
            onClick={() => handleEndUserSession(data)}
            //disabled={isLoading || isNewUnsavedRow}
            disabled={!canEndSession}
            stylingMode="contained"
            type="danger"
          />
        </div>
      );
    },
    [isLoading]
  );

  return (
    <div
      style={{
        padding: '20px',
        maxWidth: '1000px',
        margin: 'auto',
        fontFamily: 'sans-serif',
      }}
    >
      <h1>Демонстрация</h1>

      {!token ? (
        <div
          style={{
            padding: '15px',
            border: '1px solid #ccc',
            borderRadius: '5px',
          }}
        >
          <h2>Вход в систему</h2>
          <div style={{ marginBottom: '10px' }}>
            <Autocomplete
              ref={loginAutocompleteRef}
              dataSource={recentLogins}
              placeholder="Логин"
              width="100%"
              openOnFocus={true}
              showClearButton={true}
              showDropDownButton={true}
              minSearchLength={0}
              onFocus={handleLoginFocus}
            />
          </div>
          <div style={{ marginBottom: '15px' }}>
            <TextBox
              ref={passwdRef}
              placeholder="Пароль"
              mode="password"
              width="100%"
            />
          </div>
          <Button
            text="Войти"
            type="default"
            onClick={handleLogin}
            disabled={isLoading}
          />
        </div>
      ) : (
        <div>
          <div
            style={{
              padding: '15px',
              border: '1px solid #ccc',
              borderRadius: '5px',
            }}
          >
            <h2>Добро пожаловать!</h2>
            <p>
              Вы вошли в систему. Нажмите "Проверить статус", чтобы получить
              информацию о себе.
            </p>
            <div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
              <Button
                text="1. Проверить статус"
                type="success"
                onClick={handleCheckStatus}
                disabled={isLoading}
              />
              <Button
                text="2. Выйти"
                type="danger"
                onClick={handleLogout}
                disabled={isLoading}
              />
            </div>
          </div>

          {isAdmin && (
            <div
              style={{
                marginTop: '20px',
                padding: '15px',
                border: '1px solid #e0b4b4',
                background: '#fff5f5',
                borderRadius: '5px',
              }}
            >
              <h3>Панель администратора</h3>
              <Button
                text="Получить список всех пользователей"
                icon="group"
                onClick={handleFetchUsers}
                disabled={isLoading}
              />
            </div>
          )}
        </div>
      )}

      <div
        style={{
          marginTop: '20px',
          padding: '15px',
          background: '#f5f5f5',
          borderRadius: '5px',
          minHeight: '100px',
        }}
      >
        <h2>Статус операции</h2>
        {isLoading && <LoadIndicator visible={true} />}
        {message && (
          <p style={{ color: 'green', fontWeight: 'bold' }}>{message}</p>
        )}
        {error && (
          <div style={{ color: 'red' }}>
            <strong>Ошибка:</strong> {error.message}
            <pre
              style={{
                background: '#ffebeb',
                padding: '10px',
                borderRadius: '3px',
                whiteSpace: 'pre-wrap',
                wordBreak: 'break-all',
              }}
            >
              {JSON.stringify(error.response?.data || error, null, 2)}
            </pre>
          </div>
        )}
        {statusData && !usersDataSource && (
          <div>
            <strong>Данные из /token_info:</strong>
            <pre
              style={{
                background: '#e6f7ff',
                padding: '10px',
                borderRadius: '3px',
              }}
            >
              {JSON.stringify(statusData, null, 2)}
            </pre>
          </div>
        )}

        {usersDataSource && (
          <div style={{ marginTop: '20px' }}>
            <DataGrid
              ref={dataGridRef}
              dataSource={usersDataSource}
              showBorders={true}
              wordWrapEnabled={true}
              keyExpr="id"
              onSaving={() => {
                setMessage('');
                setError(null);
              }}
              onEditorPreparing={onEditorPreparing}
              onInitNewRow={onInitNewRow}
            >
              <Editing
                mode="row"
                allowUpdating={true}
                allowAdding={true}
                allowDeleting={false}
                useIcons={true}
              />
              <Paging defaultPageSize={10} />
              <Pager
                showPageSizeSelector={true}
                allowedPageSizes={[5, 10, 20]}
                showInfo={true}
              />

              <Column
                dataField="id"
                caption="ID"
                width={40}
                allowEditing={false}
              />
              <Column dataField="login" caption="Логин">
                <RequiredRule message="Логин обязателен" />
              </Column>
              <Column dataField="username" caption="Имя пользователя">
                <RequiredRule message="Имя пользователя обязательно" />
              </Column>
              <Column dataField="password" caption="Пароль" />
              <Column dataField="role" caption="Роль" />
              <Column
                dataField="insert_time"
                caption="Время создания"
                allowEditing={false}
                dataType="datetime"
                format="dd.MM.yyyy HH:mm"
                width={120}
              />
              <Column
                dataField="delete_mark"
                caption="Пометка удаления"
                dataType="boolean"
                width={80}
              />

              <Column type="buttons" width={80}>
                <Button name="edit" />
              </Column>
              <Column width={60} cellRender={renderButtonX} />
            </DataGrid>
          </div>
        )}
      </div>
    </div>
  );
}

export default App;