Доработана логика работы с введенными данными на экране аутентификации. На главный экран добавлена возможность сканирования через камеру устройства.
This commit is contained in:
parent
b0adcad59e
commit
7bc9ddb898
@ -1,6 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
|
|||||||
@ -42,3 +42,6 @@ hermesEnabled=true
|
|||||||
# This allows your app to draw behind system bars for an immersive UI.
|
# This allows your app to draw behind system bars for an immersive UI.
|
||||||
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
||||||
edgeToEdgeEnabled=false
|
edgeToEdgeEnabled=false
|
||||||
|
|
||||||
|
# Vision Camera: включение сканера штрихкодов/QR (useCodeScanner)
|
||||||
|
VisionCamera_enableCodeScanner=true
|
||||||
|
|||||||
@ -34,6 +34,8 @@
|
|||||||
<key>NSAllowsLocalNetworking</key>
|
<key>NSAllowsLocalNetworking</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Приложению нужен доступ к камере для сканирования штрихкодов и QR-кодов.</string>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
"name": "parus_pre_trip_inspections",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"description": "Parus 8 and Pre-Trip Inspections application",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
"ios": "react-native run-ios",
|
"ios": "react-native run-ios",
|
||||||
@ -14,7 +14,8 @@
|
|||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-native": "^0.83.1",
|
"react-native": "^0.83.1",
|
||||||
"react-native-quick-sqlite": "^8.2.7",
|
"react-native-quick-sqlite": "^8.2.7",
|
||||||
"react-native-safe-area-context": "^5.5.2"
|
"react-native-safe-area-context": "^5.5.2",
|
||||||
|
"react-native-vision-camera": "^4.7.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
Предрейсовые осмотры - мобильное приложение
|
|
||||||
Компонент заднего фона (оверлей)
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
const React = require('react'); //React
|
|
||||||
const { Pressable } = require('react-native'); //Базовые компоненты
|
|
||||||
const styles = require('../../styles/common/Backdrop.styles'); //Стили заднего фона
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Задний фон для модальных окон и меню
|
|
||||||
function Backdrop({ visible, onPress, style, children }) {
|
|
||||||
const handlePress = React.useCallback(() => {
|
|
||||||
if (typeof onPress === 'function') {
|
|
||||||
onPress();
|
|
||||||
}
|
|
||||||
}, [onPress]);
|
|
||||||
|
|
||||||
if (!visible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable style={[styles.backdrop, style]} onPress={handlePress}>
|
|
||||||
{children}
|
|
||||||
</Pressable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
module.exports = Backdrop;
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
Предрейсовые осмотры - мобильное приложение
|
|
||||||
Элемент списка предрейсовых осмотров
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
const React = require('react'); //React
|
|
||||||
const { View } = require('react-native'); //Базовые компоненты
|
|
||||||
const AppText = require('../common/AppText'); //Общий текстовый компонент
|
|
||||||
const styles = require('../../styles/inspections/InspectionItem.styles'); //Стили элемента
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Элемент списка осмотров
|
|
||||||
function InspectionItem({ item }) {
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<AppText style={styles.title}>{item.title}</AppText>
|
|
||||||
<View style={styles.metaRow}>
|
|
||||||
<AppText style={styles.meta}>Статус: {item.status}</AppText>
|
|
||||||
<AppText style={styles.meta}>Создан: {item.createdAt}</AppText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
module.exports = InspectionItem;
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
Предрейсовые осмотры - мобильное приложение
|
|
||||||
Список предрейсовых осмотров
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
const React = require('react'); //React
|
|
||||||
const { ActivityIndicator, FlatList, RefreshControl, View } = require('react-native'); //Базовые компоненты списка
|
|
||||||
const AppText = require('../common/AppText'); //Общий текстовый компонент
|
|
||||||
const AppButton = require('../common/AppButton'); //Общая кнопка
|
|
||||||
const InspectionItem = require('./InspectionItem'); //Элемент списка
|
|
||||||
const styles = require('../../styles/inspections/InspectionList.styles'); //Стили списка
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Список осмотров
|
|
||||||
function InspectionList({ inspections, isLoading, error, onRefresh }) {
|
|
||||||
const hasData = Array.isArray(inspections) && inspections.length > 0;
|
|
||||||
|
|
||||||
const renderItem = React.useCallback(({ item }) => <InspectionItem item={item} />, []);
|
|
||||||
|
|
||||||
const keyExtractor = React.useCallback(item => item.id, []);
|
|
||||||
|
|
||||||
const handleRefresh = React.useCallback(() => {
|
|
||||||
if (typeof onRefresh === 'function') onRefresh();
|
|
||||||
}, [onRefresh]);
|
|
||||||
|
|
||||||
if (!hasData && isLoading) {
|
|
||||||
return (
|
|
||||||
<View style={styles.centerContainer}>
|
|
||||||
<ActivityIndicator size="small" color={styles.indicator.color} />
|
|
||||||
<AppText style={styles.centerText}>Загружаем данные...</AppText>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasData && !isLoading) {
|
|
||||||
return (
|
|
||||||
<View style={styles.centerContainer}>
|
|
||||||
<AppText style={styles.centerText}>Нет данных предрейсовых осмотров</AppText>
|
|
||||||
{error ? <AppText style={styles.errorText}>{error}</AppText> : null}
|
|
||||||
<View style={styles.centerButton}>
|
|
||||||
<AppButton title="Обновить" onPress={handleRefresh} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FlatList
|
|
||||||
data={inspections}
|
|
||||||
keyExtractor={keyExtractor}
|
|
||||||
renderItem={renderItem}
|
|
||||||
refreshControl={<RefreshControl refreshing={!!isLoading} onRefresh={handleRefresh} />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
module.exports = InspectionList;
|
|
||||||
@ -21,35 +21,25 @@ const AppAuthContext = React.createContext(null);
|
|||||||
function AppAuthProvider({ children }) {
|
function AppAuthProvider({ children }) {
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
|
|
||||||
//Состояние для хранения данных формы авторизации
|
//Флаг: выполняется проверка соединения при старте (блокирует UI главного экрана)
|
||||||
const [authFormData, setAuthFormData] = React.useState({
|
const [isStartupSessionCheckInProgress, setStartupSessionCheckInProgress] = React.useState(false);
|
||||||
serverUrl: '',
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
savePassword: false
|
|
||||||
});
|
|
||||||
|
|
||||||
//Флаг проверки сессии
|
//Адрес сервера, сохранённый в настройках при текущем визите (для подстановки при возврате на экран логина)
|
||||||
const [sessionChecked, setSessionChecked] = React.useState(false);
|
const lastSavedServerUrlFromSettingsRef = React.useRef(null);
|
||||||
|
|
||||||
//Обновление данных формы авторизации
|
//Очистка данных формы авторизации (после успешного входа; актуальные данные хранятся в authFormStore и сбрасываются на экране)
|
||||||
const updateAuthFormData = React.useCallback(data => {
|
const clearAuthFormData = React.useCallback(() => {}, []);
|
||||||
setAuthFormData(prev => ({ ...prev, ...data }));
|
|
||||||
|
//Отметка о сохранении адреса сервера в настройках (вызывается с экрана настроек при успешном сохранении)
|
||||||
|
const setLastSavedServerUrlFromSettings = React.useCallback(url => {
|
||||||
|
lastSavedServerUrlFromSettingsRef.current = url != null ? String(url).trim() : null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
//Очистка данных формы авторизации
|
//Получить и сбросить адрес сервера, сохранённый в настройках (для восстановления при возврате на экран логина)
|
||||||
const clearAuthFormData = React.useCallback(() => {
|
const getAndClearLastSavedServerUrlFromSettings = React.useCallback(() => {
|
||||||
setAuthFormData({
|
const url = lastSavedServerUrlFromSettingsRef.current;
|
||||||
serverUrl: '',
|
lastSavedServerUrlFromSettingsRef.current = null;
|
||||||
username: '',
|
return url;
|
||||||
password: '',
|
|
||||||
savePassword: false
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//Отметка что сессия была проверена
|
|
||||||
const markSessionChecked = React.useCallback(() => {
|
|
||||||
setSessionChecked(true);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
//Мемоизация значения контекста с перечислением отдельных свойств
|
//Мемоизация значения контекста с перечислением отдельных свойств
|
||||||
@ -58,6 +48,7 @@ function AppAuthProvider({ children }) {
|
|||||||
session: auth.session,
|
session: auth.session,
|
||||||
isAuthenticated: auth.isAuthenticated,
|
isAuthenticated: auth.isAuthenticated,
|
||||||
isLoading: auth.isLoading,
|
isLoading: auth.isLoading,
|
||||||
|
isLogoutInProgress: auth.isLogoutInProgress,
|
||||||
isInitialized: auth.isInitialized,
|
isInitialized: auth.isInitialized,
|
||||||
error: auth.error,
|
error: auth.error,
|
||||||
login: auth.login,
|
login: auth.login,
|
||||||
@ -71,16 +62,17 @@ function AppAuthProvider({ children }) {
|
|||||||
clearAuthSession: auth.clearAuthSession,
|
clearAuthSession: auth.clearAuthSession,
|
||||||
getAndClearSessionRestoredFromStorage: auth.getAndClearSessionRestoredFromStorage,
|
getAndClearSessionRestoredFromStorage: auth.getAndClearSessionRestoredFromStorage,
|
||||||
AUTH_SETTINGS_KEYS: auth.AUTH_SETTINGS_KEYS,
|
AUTH_SETTINGS_KEYS: auth.AUTH_SETTINGS_KEYS,
|
||||||
authFormData,
|
isStartupSessionCheckInProgress,
|
||||||
updateAuthFormData,
|
setStartupSessionCheckInProgress,
|
||||||
clearAuthFormData,
|
clearAuthFormData,
|
||||||
sessionChecked,
|
setLastSavedServerUrlFromSettings,
|
||||||
markSessionChecked
|
getAndClearLastSavedServerUrlFromSettings
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
auth.session,
|
auth.session,
|
||||||
auth.isAuthenticated,
|
auth.isAuthenticated,
|
||||||
auth.isLoading,
|
auth.isLoading,
|
||||||
|
auth.isLogoutInProgress,
|
||||||
auth.isInitialized,
|
auth.isInitialized,
|
||||||
auth.error,
|
auth.error,
|
||||||
auth.login,
|
auth.login,
|
||||||
@ -94,11 +86,11 @@ function AppAuthProvider({ children }) {
|
|||||||
auth.clearAuthSession,
|
auth.clearAuthSession,
|
||||||
auth.getAndClearSessionRestoredFromStorage,
|
auth.getAndClearSessionRestoredFromStorage,
|
||||||
auth.AUTH_SETTINGS_KEYS,
|
auth.AUTH_SETTINGS_KEYS,
|
||||||
authFormData,
|
isStartupSessionCheckInProgress,
|
||||||
updateAuthFormData,
|
setStartupSessionCheckInProgress,
|
||||||
clearAuthFormData,
|
clearAuthFormData,
|
||||||
sessionChecked,
|
setLastSavedServerUrlFromSettings,
|
||||||
markSessionChecked
|
getAndClearLastSavedServerUrlFromSettings
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,6 @@ const React = require('react'); //React
|
|||||||
const { View, Pressable } = require('react-native'); //Базовые компоненты
|
const { View, Pressable } = require('react-native'); //Базовые компоненты
|
||||||
const AppText = require('../common/AppText'); //Общий текстовый компонент
|
const AppText = require('../common/AppText'); //Общий текстовый компонент
|
||||||
const AppLogo = require('../common/AppLogo'); //Логотип приложения
|
const AppLogo = require('../common/AppLogo'); //Логотип приложения
|
||||||
const { useAppModeContext } = require('./AppModeProvider'); //Контекст режима работы
|
|
||||||
const { useAppNavigationContext } = require('./AppNavigationProvider'); //Контекст навигации
|
const { useAppNavigationContext } = require('./AppNavigationProvider'); //Контекст навигации
|
||||||
const { SCREEN_TITLE_SETTINGS } = require('../../config/messages'); //Заголовки экранов
|
const { SCREEN_TITLE_SETTINGS } = require('../../config/messages'); //Заголовки экранов
|
||||||
const styles = require('../../styles/layout/AppHeader.styles'); //Стили заголовка
|
const styles = require('../../styles/layout/AppHeader.styles'); //Стили заголовка
|
||||||
@ -50,7 +49,6 @@ function BackButton({ onPress }) {
|
|||||||
|
|
||||||
//Заголовок приложения
|
//Заголовок приложения
|
||||||
function AppHeader({ title, subtitle, showMenuButton = true, onMenuPress, showBackButton = false, onBackPress }) {
|
function AppHeader({ title, subtitle, showMenuButton = true, onMenuPress, showBackButton = false, onBackPress }) {
|
||||||
const { mode } = useAppModeContext();
|
|
||||||
const { currentScreen, SCREENS } = useAppNavigationContext();
|
const { currentScreen, SCREENS } = useAppNavigationContext();
|
||||||
|
|
||||||
//Получение заголовка экрана
|
//Получение заголовка экрана
|
||||||
@ -73,13 +71,13 @@ function AppHeader({ title, subtitle, showMenuButton = true, onMenuPress, showBa
|
|||||||
|
|
||||||
switch (currentScreen) {
|
switch (currentScreen) {
|
||||||
case SCREENS.MAIN:
|
case SCREENS.MAIN:
|
||||||
return mode === 'NOT_CONNECTED' ? 'Требуется настройка сервера' : 'Демонстрационный экран';
|
return 'Главный экран';
|
||||||
case SCREENS.SETTINGS:
|
case SCREENS.SETTINGS:
|
||||||
return 'Конфигурация приложения';
|
return 'Конфигурация приложения';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}, [subtitle, currentScreen, mode, SCREENS.MAIN, SCREENS.SETTINGS]);
|
}, [subtitle, currentScreen, SCREENS.MAIN, SCREENS.SETTINGS]);
|
||||||
|
|
||||||
//Стиль кнопки меню при нажатии
|
//Стиль кнопки меню при нажатии
|
||||||
const getMenuButtonPressableStyle = React.useMemo(() => {
|
const getMenuButtonPressableStyle = React.useMemo(() => {
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
Предрейсовые осмотры - мобильное приложение
|
|
||||||
Провайдер контекста предметной области "Предрейсовые осмотры"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
const React = require('react'); //React и хуки
|
|
||||||
const usePreTripInspections = require('../../hooks/usePreTripInspections'); //Хук предметной области
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Контекст предрейсовых осмотров
|
|
||||||
const AppPreTripInspectionsContext = React.createContext(null);
|
|
||||||
|
|
||||||
//Провайдер предрейсовых осмотров
|
|
||||||
function AppPreTripInspectionsProvider({ children }) {
|
|
||||||
const inspectionsApi = usePreTripInspections();
|
|
||||||
|
|
||||||
//Мемоизация значения контекста с перечислением отдельных свойств
|
|
||||||
const value = React.useMemo(
|
|
||||||
() => ({
|
|
||||||
inspections: inspectionsApi.inspections,
|
|
||||||
loadStatus: inspectionsApi.loadStatus,
|
|
||||||
error: inspectionsApi.error,
|
|
||||||
isDbReady: inspectionsApi.isDbReady,
|
|
||||||
refreshInspections: inspectionsApi.refreshInspections,
|
|
||||||
upsertInspection: inspectionsApi.upsertInspection
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
inspectionsApi.inspections,
|
|
||||||
inspectionsApi.loadStatus,
|
|
||||||
inspectionsApi.error,
|
|
||||||
inspectionsApi.isDbReady,
|
|
||||||
inspectionsApi.refreshInspections,
|
|
||||||
inspectionsApi.upsertInspection
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return <AppPreTripInspectionsContext.Provider value={value}>{children}</AppPreTripInspectionsContext.Provider>;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Хук доступа к контексту предрейсовых осмотров
|
|
||||||
function useAppPreTripInspectionsContext() {
|
|
||||||
const ctx = React.useContext(AppPreTripInspectionsContext);
|
|
||||||
if (!ctx) {
|
|
||||||
throw new Error('useAppPreTripInspectionsContext должен использоваться внутри AppPreTripInspectionsProvider');
|
|
||||||
}
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
AppPreTripInspectionsProvider,
|
|
||||||
useAppPreTripInspectionsContext
|
|
||||||
};
|
|
||||||
@ -15,6 +15,7 @@ const styles = require('../../styles/layout/AppRoot.styles'); //Стили ко
|
|||||||
const { useAppNavigationContext } = require('./AppNavigationProvider'); //Контекст навигации
|
const { useAppNavigationContext } = require('./AppNavigationProvider'); //Контекст навигации
|
||||||
const { useAppAuthContext } = require('./AppAuthProvider'); //Контекст авторизации
|
const { useAppAuthContext } = require('./AppAuthProvider'); //Контекст авторизации
|
||||||
const { useAppLocalDbContext } = require('./AppLocalDbProvider'); //Контекст локальной БД
|
const { useAppLocalDbContext } = require('./AppLocalDbProvider'); //Контекст локальной БД
|
||||||
|
const useStartupSessionCheck = require('../../hooks/useStartupSessionCheck'); //Проверка сессии при старте
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
@ -29,6 +30,9 @@ function AppRoot() {
|
|||||||
const { isAuthenticated, isInitialized } = useAppAuthContext();
|
const { isAuthenticated, isInitialized } = useAppAuthContext();
|
||||||
const { isDbReady } = useAppLocalDbContext();
|
const { isDbReady } = useAppLocalDbContext();
|
||||||
|
|
||||||
|
//Проверка сессии при старте и установка режима онлайн/оффлайн
|
||||||
|
useStartupSessionCheck();
|
||||||
|
|
||||||
//Флаг для предотвращения повторной установки начального экрана
|
//Флаг для предотвращения повторной установки начального экрана
|
||||||
const initialScreenSetRef = React.useRef(false);
|
const initialScreenSetRef = React.useRef(false);
|
||||||
|
|
||||||
|
|||||||
@ -10,10 +10,13 @@
|
|||||||
const React = require('react'); //React
|
const React = require('react'); //React
|
||||||
const { StatusBar, Platform } = require('react-native'); //Базовые компоненты
|
const { StatusBar, Platform } = require('react-native'); //Базовые компоненты
|
||||||
const { useAppNavigationContext } = require('./AppNavigationProvider'); //Контекст навигации
|
const { useAppNavigationContext } = require('./AppNavigationProvider'); //Контекст навигации
|
||||||
|
const { useAppAuthContext } = require('./AppAuthProvider'); //Контекст авторизации
|
||||||
const MainScreen = require('../../screens/MainScreen'); //Главный экран
|
const MainScreen = require('../../screens/MainScreen'); //Главный экран
|
||||||
const SettingsScreen = require('../../screens/SettingsScreen'); //Экран настроек
|
const SettingsScreen = require('../../screens/SettingsScreen'); //Экран настроек
|
||||||
const AuthScreen = require('../../screens/AuthScreen'); //Экран авторизации
|
const AuthScreen = require('../../screens/AuthScreen'); //Экран авторизации
|
||||||
const AdaptiveView = require('../common/AdaptiveView'); //Адаптивный контейнер
|
const AdaptiveView = require('../common/AdaptiveView'); //Адаптивный контейнер
|
||||||
|
const LoadingOverlay = require('../common/LoadingOverlay'); //Оверлей загрузки
|
||||||
|
const { STARTUP_CHECK_CONNECTION_MESSAGE, LOGOUT_IN_PROGRESS_MESSAGE } = require('../../config/messages'); //Сообщения
|
||||||
const styles = require('../../styles/layout/AppShell.styles'); //Стили оболочки
|
const styles = require('../../styles/layout/AppShell.styles'); //Стили оболочки
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -23,6 +26,7 @@ const styles = require('../../styles/layout/AppShell.styles'); //Стили об
|
|||||||
//Оболочка приложения
|
//Оболочка приложения
|
||||||
function AppShell({ isDarkMode }) {
|
function AppShell({ isDarkMode }) {
|
||||||
const { currentScreen, SCREENS } = useAppNavigationContext();
|
const { currentScreen, SCREENS } = useAppNavigationContext();
|
||||||
|
const { isStartupSessionCheckInProgress, isLogoutInProgress } = useAppAuthContext();
|
||||||
|
|
||||||
const renderScreen = React.useCallback(() => {
|
const renderScreen = React.useCallback(() => {
|
||||||
switch (currentScreen) {
|
switch (currentScreen) {
|
||||||
@ -41,10 +45,15 @@ function AppShell({ isDarkMode }) {
|
|||||||
const statusBarStyle = isDarkMode ? 'light-content' : 'dark-content';
|
const statusBarStyle = isDarkMode ? 'light-content' : 'dark-content';
|
||||||
const statusBarBackground = isDarkMode ? '#0F172A' : '#F8FAFC';
|
const statusBarBackground = isDarkMode ? '#0F172A' : '#F8FAFC';
|
||||||
|
|
||||||
|
const showStartupOverlay = currentScreen === SCREENS.MAIN && isStartupSessionCheckInProgress;
|
||||||
|
const showLogoutOverlay = currentScreen === SCREENS.MAIN && isLogoutInProgress;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StatusBar barStyle={statusBarStyle} backgroundColor={statusBarBackground} translucent={Platform.OS === 'android'} />
|
<StatusBar barStyle={statusBarStyle} backgroundColor={statusBarBackground} translucent={Platform.OS === 'android'} />
|
||||||
<AdaptiveView style={styles.container}>{renderScreen()}</AdaptiveView>
|
<AdaptiveView style={styles.container}>{renderScreen()}</AdaptiveView>
|
||||||
|
<LoadingOverlay visible={showStartupOverlay} message={STARTUP_CHECK_CONNECTION_MESSAGE} />
|
||||||
|
<LoadingOverlay visible={showLogoutOverlay} message={LOGOUT_IN_PROGRESS_MESSAGE} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
32
rn/app/src/components/scanner/BarcodeScanner.js
Normal file
32
rn/app/src/components/scanner/BarcodeScanner.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Обёртка сканера
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const React = require('react'); //React
|
||||||
|
const { Platform } = require('react-native'); //Платформа
|
||||||
|
const BarcodeScannerWeb = require('./BarcodeScannerWeb'); //Заглушка для веб
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Компонент сканера
|
||||||
|
function BarcodeScanner(props) {
|
||||||
|
if (Platform.OS === 'web') {
|
||||||
|
return <BarcodeScannerWeb />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BarcodeScannerNative = require('./BarcodeScannerNative');
|
||||||
|
return <BarcodeScannerNative {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = BarcodeScanner;
|
||||||
104
rn/app/src/components/scanner/BarcodeScannerNative.js
Normal file
104
rn/app/src/components/scanner/BarcodeScannerNative.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Сканер штрихкодов и QR
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const React = require('react'); //React и хуки
|
||||||
|
const { View } = require('react-native'); //Базовые компоненты
|
||||||
|
const { Camera, useCameraDevice, useCodeScanner, useCameraPermission } = require('react-native-vision-camera'); //Камера и сканер кодов
|
||||||
|
const AppText = require('../common/AppText'); //Общий текст
|
||||||
|
const { DEFAULT_CODE_TYPES } = require('../../config/scannerConfig'); //Конфиг сканера
|
||||||
|
const styles = require('../../styles/scanner/BarcodeScanner.styles'); //Стили сканера
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Текст при отсутствии разрешения на камеру
|
||||||
|
const PERMISSION_DENIED_MESSAGE = 'Для сканирования разрешите доступ к камере в настройках приложения';
|
||||||
|
|
||||||
|
//Текст при отсутствии устройства камеры
|
||||||
|
const NO_CAMERA_MESSAGE = 'Камера недоступна';
|
||||||
|
|
||||||
|
//Обработка отсканированных кодов
|
||||||
|
function createCodeScannedHandler(onScan, lastScannedRef) {
|
||||||
|
return function onCodeScanned(codes) {
|
||||||
|
if (!codes || codes.length === 0 || typeof onScan !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const code = codes[0];
|
||||||
|
const value = code.value;
|
||||||
|
if (value == null || value === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lastScannedRef.current === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastScannedRef.current = value;
|
||||||
|
onScan({ type: code.type || 'unknown', value });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//Сканер штрихкодов и QR (нативная камера)
|
||||||
|
function BarcodeScannerNative({ onScan, isActive = true }) {
|
||||||
|
const device = useCameraDevice('back');
|
||||||
|
const { hasPermission, requestPermission } = useCameraPermission();
|
||||||
|
const lastScannedRef = React.useRef(null);
|
||||||
|
|
||||||
|
const onCodeScannedCallback = React.useCallback(
|
||||||
|
codes => {
|
||||||
|
createCodeScannedHandler(onScan, lastScannedRef)(codes);
|
||||||
|
},
|
||||||
|
[onScan]
|
||||||
|
);
|
||||||
|
|
||||||
|
const codeScanner = useCodeScanner({
|
||||||
|
codeTypes: DEFAULT_CODE_TYPES,
|
||||||
|
onCodeScanned: onCodeScannedCallback
|
||||||
|
});
|
||||||
|
|
||||||
|
//Запрос разрешения при монтировании
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (hasPermission === false && typeof requestPermission === 'function') {
|
||||||
|
requestPermission();
|
||||||
|
}
|
||||||
|
}, [hasPermission, requestPermission]);
|
||||||
|
|
||||||
|
//Нет устройства камеры
|
||||||
|
if (device == null) {
|
||||||
|
return (
|
||||||
|
<View style={styles.fallbackContainer}>
|
||||||
|
<AppText style={styles.fallbackText} variant="body">
|
||||||
|
{NO_CAMERA_MESSAGE}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Нет разрешения на камеру
|
||||||
|
if (!hasPermission) {
|
||||||
|
return (
|
||||||
|
<View style={styles.fallbackContainer}>
|
||||||
|
<AppText style={styles.fallbackText} variant="body">
|
||||||
|
{PERMISSION_DENIED_MESSAGE}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Camera style={styles.camera} device={device} isActive={isActive} codeScanner={codeScanner} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = BarcodeScannerNative;
|
||||||
37
rn/app/src/components/scanner/BarcodeScannerWeb.js
Normal file
37
rn/app/src/components/scanner/BarcodeScannerWeb.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Заглушка сканера для веб-платформы (камера недоступна)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const React = require('react'); //React
|
||||||
|
const { View } = require('react-native'); //Базовые компоненты
|
||||||
|
const AppText = require('../common/AppText'); //Общий текст
|
||||||
|
const styles = require('../../styles/scanner/BarcodeScanner.styles'); //Стили сканера
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Текст заглушки для веб
|
||||||
|
const WEB_SCANNER_UNAVAILABLE = 'Сканирование доступно только в мобильном приложении';
|
||||||
|
|
||||||
|
//Компонент-заглушка для веб: камера не поддерживается
|
||||||
|
function BarcodeScannerWeb() {
|
||||||
|
return (
|
||||||
|
<View style={styles.fallbackContainer}>
|
||||||
|
<AppText style={styles.fallbackText} variant="body">
|
||||||
|
{WEB_SCANNER_UNAVAILABLE}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = BarcodeScannerWeb;
|
||||||
84
rn/app/src/components/scanner/ScanResultModal.js
Normal file
84
rn/app/src/components/scanner/ScanResultModal.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Модальное окно с результатом сканирования штрихкода/QR
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const React = require('react'); //React
|
||||||
|
const { Modal, View, Pressable } = require('react-native'); //Базовые компоненты
|
||||||
|
const AppText = require('../common/AppText'); //Общий текстовый компонент
|
||||||
|
const AppButton = require('../common/AppButton'); //Кнопка
|
||||||
|
const { SCAN_RESULT_MODAL_TITLE, SCAN_RESULT_CLOSE_BUTTON } = require('../../config/messages'); //Сообщения
|
||||||
|
const styles = require('../../styles/scanner/ScanResultModal.styles'); //Стили модального окна
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Форматирование типа кода для отображения (например qr -> QR-код)
|
||||||
|
function formatCodeType(type) {
|
||||||
|
if (!type || type === 'unknown') {
|
||||||
|
return 'Неизвестный тип';
|
||||||
|
}
|
||||||
|
const upper = type.toUpperCase().replace(/-/g, ' ');
|
||||||
|
if (type === 'qr') {
|
||||||
|
return 'QR-код';
|
||||||
|
}
|
||||||
|
return upper;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Обработчик закрытия модального окна
|
||||||
|
function handleClose(onRequestClose) {
|
||||||
|
if (typeof onRequestClose === 'function') {
|
||||||
|
onRequestClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Модальное окно результата сканирования
|
||||||
|
function ScanResultModal({ visible, codeType, value, onRequestClose }) {
|
||||||
|
const handleClosePress = React.useCallback(() => {
|
||||||
|
handleClose(onRequestClose);
|
||||||
|
}, [onRequestClose]);
|
||||||
|
|
||||||
|
const displayType = formatCodeType(codeType);
|
||||||
|
const displayValue = value != null && value !== '' ? String(value) : '—';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal animationType="fade" transparent={true} visible={!!visible} onRequestClose={handleClosePress}>
|
||||||
|
<View style={styles.backdrop}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<AppText style={styles.title} numberOfLines={1}>
|
||||||
|
{SCAN_RESULT_MODAL_TITLE}
|
||||||
|
</AppText>
|
||||||
|
<Pressable accessibilityRole="button" accessibilityLabel="Закрыть" onPress={handleClosePress} style={styles.closeButton}>
|
||||||
|
<AppText style={styles.closeButtonText}>×</AppText>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<AppText style={styles.typeLabel} variant="caption" weight="medium">
|
||||||
|
Тип: {displayType}
|
||||||
|
</AppText>
|
||||||
|
<View style={styles.valueBlock}>
|
||||||
|
<AppText style={styles.valueText} selectable={true}>
|
||||||
|
{displayValue}
|
||||||
|
</AppText>
|
||||||
|
</View>
|
||||||
|
<View style={styles.buttonsRow}>
|
||||||
|
<AppButton title={SCAN_RESULT_CLOSE_BUTTON} onPress={handleClosePress} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = ScanResultModal;
|
||||||
89
rn/app/src/components/scanner/ScannerArea.js
Normal file
89
rn/app/src/components/scanner/ScannerArea.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Область сканера на главном экране: всегда активный сканер или заглушка с кнопкой
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const React = require('react'); //React
|
||||||
|
const { View, Modal, Pressable } = require('react-native'); //Базовые компоненты
|
||||||
|
const BarcodeScanner = require('./BarcodeScanner'); //Сканер
|
||||||
|
const ScannerPlaceholder = require('./ScannerPlaceholder'); //Заглушка с кнопкой
|
||||||
|
const AppText = require('../common/AppText'); //Текст
|
||||||
|
const styles = require('../../styles/scanner/ScannerArea.styles'); //Стили области
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Обработчик успешного сканирования: передаём результат наружу и при необходимости закрываем модал
|
||||||
|
function handleScanResult(result, onScanResult, closeModal) {
|
||||||
|
if (typeof onScanResult === 'function') {
|
||||||
|
onScanResult(result);
|
||||||
|
}
|
||||||
|
if (typeof closeModal === 'function') {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Область сканера: при включённой настройке — активный сканер (если открыт), иначе заглушка и модальный сканер по кнопке
|
||||||
|
function ScannerArea({ alwaysShowScanner = false, scannerOpen = true, onScanResult }) {
|
||||||
|
const [scannerModalVisible, setScannerModalVisible] = React.useState(false);
|
||||||
|
|
||||||
|
const handleScanFromArea = React.useCallback(
|
||||||
|
result => {
|
||||||
|
handleScanResult(result, onScanResult, null);
|
||||||
|
},
|
||||||
|
[onScanResult]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleScanFromModal = React.useCallback(
|
||||||
|
result => {
|
||||||
|
handleScanResult(result, onScanResult, () => setScannerModalVisible(false));
|
||||||
|
},
|
||||||
|
[onScanResult]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOpenScanner = React.useCallback(() => {
|
||||||
|
setScannerModalVisible(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCloseScannerModal = React.useCallback(() => {
|
||||||
|
setScannerModalVisible(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderScannerContent = () => {
|
||||||
|
if (alwaysShowScanner && scannerOpen) {
|
||||||
|
return <BarcodeScanner isActive={true} onScan={handleScanFromArea} />;
|
||||||
|
}
|
||||||
|
return <ScannerPlaceholder onScanPress={handleOpenScanner} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{renderScannerContent()}
|
||||||
|
|
||||||
|
<Modal visible={scannerModalVisible} animationType="slide" onRequestClose={handleCloseScannerModal}>
|
||||||
|
<View style={styles.modalContainer}>
|
||||||
|
<BarcodeScanner isActive={scannerModalVisible} onScan={handleScanFromModal} />
|
||||||
|
<Pressable
|
||||||
|
style={styles.modalCloseButton}
|
||||||
|
onPress={handleCloseScannerModal}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="Закрыть сканер"
|
||||||
|
>
|
||||||
|
<AppText style={styles.modalCloseButtonText}>Закрыть</AppText>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = ScannerArea;
|
||||||
39
rn/app/src/components/scanner/ScannerPlaceholder.js
Normal file
39
rn/app/src/components/scanner/ScannerPlaceholder.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Заглушка области сканера с кнопкой «Сканировать»
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const React = require('react'); //React
|
||||||
|
const { View } = require('react-native'); //Базовые компоненты
|
||||||
|
const AppButton = require('../common/AppButton'); //Кнопка
|
||||||
|
const { SCAN_BUTTON_TITLE } = require('../../config/messages'); //Сообщения
|
||||||
|
const styles = require('../../styles/scanner/ScannerPlaceholder.styles'); //Стили заглушки
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Заглушка: затемнённая область и кнопка открытия сканера
|
||||||
|
function ScannerPlaceholder({ onScanPress }) {
|
||||||
|
const handlePress = React.useCallback(() => {
|
||||||
|
if (typeof onScanPress === 'function') {
|
||||||
|
onScanPress();
|
||||||
|
}
|
||||||
|
}, [onScanPress]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<AppButton title={SCAN_BUTTON_TITLE} onPress={handlePress} style={styles.button} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = ScannerPlaceholder;
|
||||||
@ -10,13 +10,15 @@
|
|||||||
//Ключи настроек авторизации
|
//Ключи настроек авторизации
|
||||||
const AUTH_SETTINGS_KEYS = {
|
const AUTH_SETTINGS_KEYS = {
|
||||||
SERVER_URL: 'app_server_url',
|
SERVER_URL: 'app_server_url',
|
||||||
|
LAST_CONNECTED_SERVER_URL: 'auth_last_connected_server_url',
|
||||||
HIDE_SERVER_URL: 'auth_hide_server_url',
|
HIDE_SERVER_URL: 'auth_hide_server_url',
|
||||||
IDLE_TIMEOUT: 'auth_idle_timeout',
|
IDLE_TIMEOUT: 'auth_idle_timeout',
|
||||||
DEVICE_ID: 'auth_device_id',
|
DEVICE_ID: 'auth_device_id',
|
||||||
DEVICE_SECRET_KEY: 'auth_device_secret_key',
|
DEVICE_SECRET_KEY: 'auth_device_secret_key',
|
||||||
SAVED_LOGIN: 'auth_saved_login',
|
SAVED_LOGIN: 'auth_saved_login',
|
||||||
SAVED_PASSWORD: 'auth_saved_password',
|
SAVED_PASSWORD: 'auth_saved_password',
|
||||||
SAVE_PASSWORD_ENABLED: 'auth_save_password_enabled'
|
SAVE_PASSWORD_ENABLED: 'auth_save_password_enabled',
|
||||||
|
ALWAYS_SHOW_SCANNER: 'main_always_show_scanner'
|
||||||
};
|
};
|
||||||
|
|
||||||
//Значение времени простоя по умолчанию (минуты)
|
//Значение времени простоя по умолчанию (минуты)
|
||||||
|
|||||||
@ -13,6 +13,9 @@ const CONNECTION_LOST_MESSAGE = 'Нет связи с сервером. Прил
|
|||||||
//Заголовок сообщения при переходе в режим офлайн
|
//Заголовок сообщения при переходе в режим офлайн
|
||||||
const OFFLINE_MODE_TITLE = 'Режим офлайн';
|
const OFFLINE_MODE_TITLE = 'Режим офлайн';
|
||||||
|
|
||||||
|
//Сообщение при проверке соединения при старте приложения
|
||||||
|
const STARTUP_CHECK_CONNECTION_MESSAGE = 'Проверка соединения...';
|
||||||
|
|
||||||
//Заголовок диалога/экрана «Информация о приложении»
|
//Заголовок диалога/экрана «Информация о приложении»
|
||||||
const APP_ABOUT_TITLE = 'Информация о приложении';
|
const APP_ABOUT_TITLE = 'Информация о приложении';
|
||||||
|
|
||||||
@ -32,16 +35,27 @@ const MENU_ITEM_LOGOUT = 'Выход';
|
|||||||
const AUTH_SCREEN_TITLE = 'Вход в приложение';
|
const AUTH_SCREEN_TITLE = 'Вход в приложение';
|
||||||
const AUTH_BUTTON_LOGIN = 'Войти';
|
const AUTH_BUTTON_LOGIN = 'Войти';
|
||||||
const AUTH_BUTTON_LOADING = 'Вход...';
|
const AUTH_BUTTON_LOADING = 'Вход...';
|
||||||
|
const LOGOUT_IN_PROGRESS_MESSAGE = 'Выход...';
|
||||||
|
|
||||||
|
//Диалог подтверждения при смене сервера (относительно последнего успешного подключения)
|
||||||
|
const AUTH_SERVER_CHANGE_CONFIRM_TITLE = 'Подтверждение входа';
|
||||||
|
const AUTH_SERVER_CHANGE_CONFIRM_MESSAGE = 'Сервер подключения изменился относительно последнего входа. Локальные данные будут сброшены. Продолжить?';
|
||||||
|
const AUTH_SERVER_CHANGE_CONFIRM_BUTTON = 'Продолжить';
|
||||||
|
const AUTH_SERVER_CHANGE_CANCEL_BUTTON = 'Отмена';
|
||||||
|
|
||||||
//Сообщения об успешных действиях
|
//Сообщения об успешных действиях
|
||||||
const LOGIN_SUCCESS_MESSAGE = 'Вход выполнен успешно';
|
|
||||||
const LOGOUT_SUCCESS_MESSAGE = 'Выход выполнен';
|
|
||||||
const SETTINGS_SERVER_SAVED_MESSAGE = 'Настройки сервера сохранены';
|
const SETTINGS_SERVER_SAVED_MESSAGE = 'Настройки сервера сохранены';
|
||||||
const SETTINGS_RESET_SUCCESS_MESSAGE = 'Настройки сброшены';
|
const SETTINGS_RESET_SUCCESS_MESSAGE = 'Настройки сброшены';
|
||||||
|
|
||||||
//Заголовок экрана настроек
|
//Заголовок экрана настроек
|
||||||
const SCREEN_TITLE_SETTINGS = 'Настройки';
|
const SCREEN_TITLE_SETTINGS = 'Настройки';
|
||||||
|
|
||||||
|
//Сканер на главном экране
|
||||||
|
const SCANNER_SETTING_LABEL = 'Всегда отображать сканер на главном экране';
|
||||||
|
const SCAN_BUTTON_TITLE = 'Сканировать';
|
||||||
|
const SCAN_RESULT_MODAL_TITLE = 'Результат сканирования';
|
||||||
|
const SCAN_RESULT_CLOSE_BUTTON = 'Закрыть';
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
@ -49,6 +63,7 @@ const SCREEN_TITLE_SETTINGS = 'Настройки';
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
CONNECTION_LOST_MESSAGE,
|
CONNECTION_LOST_MESSAGE,
|
||||||
OFFLINE_MODE_TITLE,
|
OFFLINE_MODE_TITLE,
|
||||||
|
STARTUP_CHECK_CONNECTION_MESSAGE,
|
||||||
APP_ABOUT_TITLE,
|
APP_ABOUT_TITLE,
|
||||||
SIDE_MENU_TITLE,
|
SIDE_MENU_TITLE,
|
||||||
ORGANIZATION_SELECT_DIALOG_TITLE,
|
ORGANIZATION_SELECT_DIALOG_TITLE,
|
||||||
@ -59,9 +74,16 @@ module.exports = {
|
|||||||
AUTH_SCREEN_TITLE,
|
AUTH_SCREEN_TITLE,
|
||||||
AUTH_BUTTON_LOGIN,
|
AUTH_BUTTON_LOGIN,
|
||||||
AUTH_BUTTON_LOADING,
|
AUTH_BUTTON_LOADING,
|
||||||
LOGIN_SUCCESS_MESSAGE,
|
LOGOUT_IN_PROGRESS_MESSAGE,
|
||||||
LOGOUT_SUCCESS_MESSAGE,
|
AUTH_SERVER_CHANGE_CONFIRM_TITLE,
|
||||||
|
AUTH_SERVER_CHANGE_CONFIRM_MESSAGE,
|
||||||
|
AUTH_SERVER_CHANGE_CONFIRM_BUTTON,
|
||||||
|
AUTH_SERVER_CHANGE_CANCEL_BUTTON,
|
||||||
SETTINGS_SERVER_SAVED_MESSAGE,
|
SETTINGS_SERVER_SAVED_MESSAGE,
|
||||||
SETTINGS_RESET_SUCCESS_MESSAGE,
|
SETTINGS_RESET_SUCCESS_MESSAGE,
|
||||||
SCREEN_TITLE_SETTINGS
|
SCREEN_TITLE_SETTINGS,
|
||||||
|
SCANNER_SETTING_LABEL,
|
||||||
|
SCAN_BUTTON_TITLE,
|
||||||
|
SCAN_RESULT_MODAL_TITLE,
|
||||||
|
SCAN_RESULT_CLOSE_BUTTON
|
||||||
};
|
};
|
||||||
|
|||||||
25
rn/app/src/config/scannerConfig.js
Normal file
25
rn/app/src/config/scannerConfig.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Конфигурация сканера штрихкодов и QR-кодов
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Типы кодов для распознавания: QR и распространённые штрихкоды
|
||||||
|
const DEFAULT_CODE_TYPES = ['qr', 'code-128', 'code-39', 'ean-13', 'ean-8', 'upc-a', 'upc-e', 'pdf-417', 'aztec', 'data-matrix'];
|
||||||
|
|
||||||
|
//Определение, должен ли отображаться сканер на главном экране
|
||||||
|
function shouldShowScanner({ alwaysShowScanner, hasScanResult, isStartupCheckInProgress }) {
|
||||||
|
return Boolean(alwaysShowScanner && !hasScanResult && !isStartupCheckInProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
DEFAULT_CODE_TYPES,
|
||||||
|
shouldShowScanner
|
||||||
|
};
|
||||||
@ -38,8 +38,6 @@ class SQLiteDatabase {
|
|||||||
location: 'default'
|
location: 'default'
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('База данных успешно открыта');
|
|
||||||
|
|
||||||
//Настраиваем базу данных
|
//Настраиваем базу данных
|
||||||
await this.setupDatabase();
|
await this.setupDatabase();
|
||||||
|
|
||||||
@ -60,21 +58,14 @@ class SQLiteDatabase {
|
|||||||
try {
|
try {
|
||||||
//Выполняем SQL запросы последовательно
|
//Выполняем SQL запросы последовательно
|
||||||
await this.executeQuery(this.sqlQueries.CREATE_TABLE_APP_SETTINGS);
|
await this.executeQuery(this.sqlQueries.CREATE_TABLE_APP_SETTINGS);
|
||||||
console.log('Таблица app_settings создана/проверена');
|
|
||||||
|
|
||||||
await this.executeQuery(this.sqlQueries.CREATE_TABLE_INSPECTIONS);
|
await this.executeQuery(this.sqlQueries.CREATE_TABLE_INSPECTIONS);
|
||||||
console.log('Таблица inspections создана/проверена');
|
|
||||||
|
|
||||||
await this.executeQuery(this.sqlQueries.CREATE_TABLE_AUTH_SESSION);
|
await this.executeQuery(this.sqlQueries.CREATE_TABLE_AUTH_SESSION);
|
||||||
console.log('Таблица auth_session создана/проверена');
|
|
||||||
|
|
||||||
await this.executeQuery(this.sqlQueries.CREATE_INDEX_INSPECTIONS_STATUS);
|
await this.executeQuery(this.sqlQueries.CREATE_INDEX_INSPECTIONS_STATUS);
|
||||||
console.log('Индекс idx_inspections_status создан/проверен');
|
|
||||||
|
|
||||||
await this.executeQuery(this.sqlQueries.CREATE_INDEX_INSPECTIONS_CREATED);
|
await this.executeQuery(this.sqlQueries.CREATE_INDEX_INSPECTIONS_CREATED);
|
||||||
console.log('Индекс idx_inspections_created создан/проверен');
|
|
||||||
|
|
||||||
console.log('Все таблицы и индексы созданы/проверены');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка настройки базы данных:', error);
|
console.error('Ошибка настройки базы данных:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@ -398,7 +389,6 @@ class SQLiteDatabase {
|
|||||||
// База данных закрывается автоматически при уничтожении объекта
|
// База данных закрывается автоматически при уничтожении объекта
|
||||||
this.db = null;
|
this.db = null;
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
console.log('База данных закрыта');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка закрытия базы данных:', error);
|
console.error('Ошибка закрытия базы данных:', error);
|
||||||
|
|||||||
@ -49,12 +49,8 @@ function useAppMode() {
|
|||||||
const savedMode = await getSetting(STORAGE_KEY);
|
const savedMode = await getSetting(STORAGE_KEY);
|
||||||
if (savedMode && Object.values(APP_MODE).includes(savedMode)) {
|
if (savedMode && Object.values(APP_MODE).includes(savedMode)) {
|
||||||
setMode(savedMode);
|
setMode(savedMode);
|
||||||
} else {
|
|
||||||
const serverUrl = await getSetting('app_server_url');
|
|
||||||
if (serverUrl) {
|
|
||||||
setMode(APP_MODE.ONLINE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
//При отсутствии сохранённого режима остаётся NOT_CONNECTED
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки режима:', error);
|
console.error('Ошибка загрузки режима:', error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -17,10 +17,11 @@ const { SCREENS } = require('../config/routes'); //Экраны навигаци
|
|||||||
//Хук навигации приложения
|
//Хук навигации приложения
|
||||||
const useAppNavigation = () => {
|
const useAppNavigation = () => {
|
||||||
//Начальный экран - AUTH (до определения статуса авторизации)
|
//Начальный экран - AUTH (до определения статуса авторизации)
|
||||||
|
//История хранит пары { screen, params } для сохранения параметров при возврате
|
||||||
const [navigationState, setNavigationState] = React.useState({
|
const [navigationState, setNavigationState] = React.useState({
|
||||||
currentScreen: SCREENS.AUTH,
|
currentScreen: SCREENS.AUTH,
|
||||||
screenParams: {},
|
screenParams: {},
|
||||||
history: [SCREENS.AUTH]
|
history: [{ screen: SCREENS.AUTH, params: {} }]
|
||||||
});
|
});
|
||||||
|
|
||||||
//Навигация на экран
|
//Навигация на экран
|
||||||
@ -28,24 +29,23 @@ const useAppNavigation = () => {
|
|||||||
setNavigationState(prev => ({
|
setNavigationState(prev => ({
|
||||||
currentScreen: screen,
|
currentScreen: screen,
|
||||||
screenParams: params,
|
screenParams: params,
|
||||||
history: [...prev.history, screen]
|
history: [...prev.history, { screen, params }]
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
//Возврат назад
|
//Возврат назад (восстанавливаем экран и его параметры из истории)
|
||||||
const goBack = React.useCallback(() => {
|
const goBack = React.useCallback(() => {
|
||||||
setNavigationState(prev => {
|
setNavigationState(prev => {
|
||||||
if (prev.history.length <= 1) {
|
if (prev.history.length <= 1) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newHistory = [...prev.history];
|
const newHistory = prev.history.slice(0, -1);
|
||||||
newHistory.pop();
|
const previous = newHistory[newHistory.length - 1];
|
||||||
const previousScreen = newHistory[newHistory.length - 1];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentScreen: previousScreen,
|
currentScreen: previous.screen,
|
||||||
screenParams: {},
|
screenParams: previous.params || {},
|
||||||
history: newHistory
|
history: newHistory
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -65,7 +65,7 @@ const useAppNavigation = () => {
|
|||||||
setNavigationState({
|
setNavigationState({
|
||||||
currentScreen: screen,
|
currentScreen: screen,
|
||||||
screenParams: params,
|
screenParams: params,
|
||||||
history: [screen]
|
history: [{ screen, params }]
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ function useAuth() {
|
|||||||
const [session, setSession] = React.useState(null);
|
const [session, setSession] = React.useState(null);
|
||||||
const [isAuthenticated, setIsAuthenticated] = React.useState(false);
|
const [isAuthenticated, setIsAuthenticated] = React.useState(false);
|
||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
const [isLogoutInProgress, setIsLogoutInProgress] = React.useState(false);
|
||||||
const [isInitialized, setIsInitialized] = React.useState(false);
|
const [isInitialized, setIsInitialized] = React.useState(false);
|
||||||
const [error, setError] = React.useState(null);
|
const [error, setError] = React.useState(null);
|
||||||
|
|
||||||
@ -286,10 +287,14 @@ function useAuth() {
|
|||||||
|
|
||||||
await setAuthSession(sessionData);
|
await setAuthSession(sessionData);
|
||||||
await setSetting(AUTH_SETTINGS_KEYS.SERVER_URL, serverUrl);
|
await setSetting(AUTH_SETTINGS_KEYS.SERVER_URL, serverUrl);
|
||||||
|
await setSetting(AUTH_SETTINGS_KEYS.LAST_CONNECTED_SERVER_URL, serverUrl);
|
||||||
await setSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN, user);
|
await setSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN, user);
|
||||||
|
|
||||||
if (savePassword) {
|
if (savePassword) {
|
||||||
await saveCredentials(user, password, deviceId);
|
await saveCredentials(user, password, deviceId);
|
||||||
|
} else {
|
||||||
|
await setSetting(AUTH_SETTINGS_KEYS.SAVED_PASSWORD, '');
|
||||||
|
await setSetting(AUTH_SETTINGS_KEYS.SAVE_PASSWORD_ENABLED, 'false');
|
||||||
}
|
}
|
||||||
|
|
||||||
setSession(sessionData);
|
setSession(sessionData);
|
||||||
@ -365,6 +370,7 @@ function useAuth() {
|
|||||||
|
|
||||||
await setAuthSession(sessionData);
|
await setAuthSession(sessionData);
|
||||||
await setSetting(AUTH_SETTINGS_KEYS.SERVER_URL, serverUrl);
|
await setSetting(AUTH_SETTINGS_KEYS.SERVER_URL, serverUrl);
|
||||||
|
await setSetting(AUTH_SETTINGS_KEYS.LAST_CONNECTED_SERVER_URL, serverUrl);
|
||||||
const loginToSave = loginCredentials?.login || user?.SCODE || user?.SNAME || '';
|
const loginToSave = loginCredentials?.login || user?.SCODE || user?.SNAME || '';
|
||||||
if (loginToSave) {
|
if (loginToSave) {
|
||||||
await setSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN, loginToSave);
|
await setSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN, loginToSave);
|
||||||
@ -372,6 +378,9 @@ function useAuth() {
|
|||||||
|
|
||||||
if (savePassword && loginCredentials) {
|
if (savePassword && loginCredentials) {
|
||||||
await saveCredentials(loginCredentials.login, loginCredentials.password, loginCredentials.deviceId);
|
await saveCredentials(loginCredentials.login, loginCredentials.password, loginCredentials.deviceId);
|
||||||
|
} else {
|
||||||
|
await setSetting(AUTH_SETTINGS_KEYS.SAVED_PASSWORD, '');
|
||||||
|
await setSetting(AUTH_SETTINGS_KEYS.SAVE_PASSWORD_ENABLED, 'false');
|
||||||
}
|
}
|
||||||
|
|
||||||
setSession(sessionData);
|
setSession(sessionData);
|
||||||
@ -399,6 +408,7 @@ function useAuth() {
|
|||||||
const { skipServerRequest = false } = options;
|
const { skipServerRequest = false } = options;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
setIsLogoutInProgress(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -435,6 +445,7 @@ function useAuth() {
|
|||||||
return { success: false, error: errorMessage };
|
return { success: false, error: errorMessage };
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
setIsLogoutInProgress(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[session, getAuthSession, executeRequest, clearAuthSession]
|
[session, getAuthSession, executeRequest, clearAuthSession]
|
||||||
@ -601,6 +612,7 @@ function useAuth() {
|
|||||||
session,
|
session,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isLogoutInProgress,
|
||||||
isInitialized,
|
isInitialized,
|
||||||
error,
|
error,
|
||||||
|
|
||||||
|
|||||||
@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
Предрейсовые осмотры - мобильное приложение
|
|
||||||
Хук предметной области "Предрейсовые осмотры"
|
|
||||||
|
|
||||||
Объединяет:
|
|
||||||
- работу с сервером приложений;
|
|
||||||
- работу с локальной базой данных;
|
|
||||||
- управление состоянием загрузки/ошибок.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
const React = require('react'); //React и хуки
|
|
||||||
const useAppServer = require('./useAppServer'); //Хук для сервера приложений
|
|
||||||
const { useAppLocalDbContext } = require('../components/layout/AppLocalDbProvider'); //Контекст локальной БД
|
|
||||||
const { useAppModeContext } = require('../components/layout/AppModeProvider'); //Контекст режима работы
|
|
||||||
const { LOAD_STATUS_IDLE, LOAD_STATUS_LOADING, LOAD_STATUS_DONE, LOAD_STATUS_ERROR } = require('../config/loadStatus'); //Статусы загрузки
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук предметной области "Предрейсовые осмотры"
|
|
||||||
function usePreTripInspections() {
|
|
||||||
const { executeAction, isRespErr, getRespErrMessage, RESP_STATUS_OK } = useAppServer();
|
|
||||||
const { inspections, loadInspections, saveInspection, isDbReady } = useAppLocalDbContext();
|
|
||||||
const { APP_MODE, mode } = useAppModeContext();
|
|
||||||
|
|
||||||
const [loadStatus, setLoadStatus] = React.useState(LOAD_STATUS_IDLE);
|
|
||||||
const [error, setError] = React.useState(null);
|
|
||||||
|
|
||||||
//Загрузка списка осмотров:
|
|
||||||
//1) Если режим OFFLINE - работаем только с локальной БД;
|
|
||||||
//2) Если режим ONLINE - пробуем получить данные с сервера приложений,
|
|
||||||
// при ошибке используем локальные данные.
|
|
||||||
const refreshInspections = React.useCallback(async () => {
|
|
||||||
//Проверяем готовность базы данных
|
|
||||||
if (!isDbReady) {
|
|
||||||
return {
|
|
||||||
inspections: [],
|
|
||||||
fromServer: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoadStatus(LOAD_STATUS_LOADING);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
//Режим OFFLINE - работаем только с локальными данными
|
|
||||||
if (mode === APP_MODE.OFFLINE) {
|
|
||||||
const localInspections = await loadInspections();
|
|
||||||
setLoadStatus(LOAD_STATUS_DONE);
|
|
||||||
return {
|
|
||||||
inspections: localInspections,
|
|
||||||
fromServer: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//Режим ONLINE - пробуем получить данные с сервера приложений
|
|
||||||
const serverResponse = await executeAction({
|
|
||||||
path: 'api/pretrip/inspections',
|
|
||||||
method: 'POST',
|
|
||||||
payload: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Ошибка запроса — используем локальные данные
|
|
||||||
if (isRespErr(serverResponse) || serverResponse.status !== RESP_STATUS_OK) {
|
|
||||||
const localInspections = await loadInspections();
|
|
||||||
setLoadStatus(localInspections.length > 0 ? LOAD_STATUS_DONE : LOAD_STATUS_ERROR);
|
|
||||||
setError(getRespErrMessage(serverResponse) || 'Не удалось получить данные с сервера приложений');
|
|
||||||
return {
|
|
||||||
inspections: localInspections,
|
|
||||||
fromServer: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//Успех - приводим полезную нагрузку к ожидаемому виду
|
|
||||||
const payload = serverResponse.payload || {};
|
|
||||||
const resultInspections = Array.isArray(payload.inspections) ? payload.inspections : [];
|
|
||||||
|
|
||||||
setLoadStatus(LOAD_STATUS_DONE);
|
|
||||||
|
|
||||||
return {
|
|
||||||
inspections: resultInspections,
|
|
||||||
fromServer: true
|
|
||||||
};
|
|
||||||
}, [APP_MODE.OFFLINE, RESP_STATUS_OK, executeAction, getRespErrMessage, isDbReady, isRespErr, loadInspections, mode]);
|
|
||||||
|
|
||||||
//Создание или обновление осмотра
|
|
||||||
const upsertInspection = React.useCallback(
|
|
||||||
async inspection => {
|
|
||||||
const safeInspection = {
|
|
||||||
id: inspection.id || `local-${Date.now()}`,
|
|
||||||
title: inspection.title || 'Новый осмотр',
|
|
||||||
status: inspection.status || 'DRAFT',
|
|
||||||
createdAt: inspection.createdAt || new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
//Сохраняем в локальной БД (всегда, независимо от режима)
|
|
||||||
await saveInspection(safeInspection);
|
|
||||||
|
|
||||||
//Если приложение в режиме ONLINE - отправляем данные на сервер приложений
|
|
||||||
if (mode === APP_MODE.ONLINE) {
|
|
||||||
await executeAction({
|
|
||||||
path: 'api/pretrip/inspections/save',
|
|
||||||
method: 'POST',
|
|
||||||
payload: { inspection: safeInspection }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return safeInspection;
|
|
||||||
},
|
|
||||||
[APP_MODE.ONLINE, executeAction, saveInspection, mode]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
inspections,
|
|
||||||
loadStatus,
|
|
||||||
error,
|
|
||||||
isDbReady,
|
|
||||||
refreshInspections,
|
|
||||||
upsertInspection
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
module.exports = usePreTripInspections;
|
|
||||||
89
rn/app/src/hooks/useStartupSessionCheck.js
Normal file
89
rn/app/src/hooks/useStartupSessionCheck.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Проверка сессии при старте приложения и установка режима онлайн/оффлайн
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const React = require('react'); //React и хуки
|
||||||
|
const { useAppLocalDbContext } = require('../components/layout/AppLocalDbProvider'); //Контекст локальной БД
|
||||||
|
const { useAppAuthContext } = require('../components/layout/AppAuthProvider'); //Контекст авторизации
|
||||||
|
const { useAppModeContext } = require('../components/layout/AppModeProvider'); //Контекст режима работы
|
||||||
|
const { useAppMessagingContext } = require('../components/layout/AppMessagingProvider'); //Контекст сообщений
|
||||||
|
const { CONNECTION_LOST_MESSAGE, OFFLINE_MODE_TITLE } = require('../config/messages'); //Сообщения
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Проверка сессии при входе в приложение
|
||||||
|
function useStartupSessionCheck() {
|
||||||
|
const { isDbReady } = useAppLocalDbContext();
|
||||||
|
const { isInitialized, isAuthenticated, checkSession, getAndClearSessionRestoredFromStorage, setStartupSessionCheckInProgress } =
|
||||||
|
useAppAuthContext();
|
||||||
|
const { setOnline, setOffline } = useAppModeContext();
|
||||||
|
const { showInfo } = useAppMessagingContext();
|
||||||
|
|
||||||
|
//Флаг: проверка соединения уже выполнена в этой сессии приложения
|
||||||
|
const checkDoneRef = React.useRef(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isDbReady || !isInitialized || !isAuthenticated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkDoneRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const restored = getAndClearSessionRestoredFromStorage();
|
||||||
|
if (!restored) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDoneRef.current = true;
|
||||||
|
setStartupSessionCheckInProgress(true);
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
checkSession()
|
||||||
|
.then(result => {
|
||||||
|
if (cancelled || !result || !result.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.isOffline) {
|
||||||
|
setOffline();
|
||||||
|
showInfo(CONNECTION_LOST_MESSAGE, { title: OFFLINE_MODE_TITLE });
|
||||||
|
} else {
|
||||||
|
setOnline();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setStartupSessionCheckInProgress(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
setStartupSessionCheckInProgress(false);
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
isDbReady,
|
||||||
|
isInitialized,
|
||||||
|
isAuthenticated,
|
||||||
|
checkSession,
|
||||||
|
getAndClearSessionRestoredFromStorage,
|
||||||
|
setStartupSessionCheckInProgress,
|
||||||
|
setOnline,
|
||||||
|
setOffline,
|
||||||
|
showInfo
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = useStartupSessionCheck;
|
||||||
@ -29,6 +29,7 @@ const { getAppInfo } = require('../utils/appInfo'); //Информация о п
|
|||||||
const { isServerUrlFieldVisible } = require('../utils/loginFormUtils'); //Утилиты формы входа
|
const { isServerUrlFieldVisible } = require('../utils/loginFormUtils'); //Утилиты формы входа
|
||||||
const { normalizeServerUrl, validateServerUrl } = require('../utils/validation'); //Валидация
|
const { normalizeServerUrl, validateServerUrl } = require('../utils/validation'); //Валидация
|
||||||
const { AUTH_SETTINGS_KEYS } = require('../config/authConfig'); //Конфиг авторизации
|
const { AUTH_SETTINGS_KEYS } = require('../config/authConfig'); //Конфиг авторизации
|
||||||
|
const { getAuthFormStore, setAuthFormStore, clearAuthFormStore } = require('../utils/authFormStore'); //Хранилище формы входа
|
||||||
const {
|
const {
|
||||||
APP_ABOUT_TITLE,
|
APP_ABOUT_TITLE,
|
||||||
SIDE_MENU_TITLE,
|
SIDE_MENU_TITLE,
|
||||||
@ -38,7 +39,10 @@ const {
|
|||||||
AUTH_SCREEN_TITLE,
|
AUTH_SCREEN_TITLE,
|
||||||
AUTH_BUTTON_LOGIN,
|
AUTH_BUTTON_LOGIN,
|
||||||
AUTH_BUTTON_LOADING,
|
AUTH_BUTTON_LOADING,
|
||||||
LOGIN_SUCCESS_MESSAGE
|
AUTH_SERVER_CHANGE_CONFIRM_TITLE,
|
||||||
|
AUTH_SERVER_CHANGE_CONFIRM_MESSAGE,
|
||||||
|
AUTH_SERVER_CHANGE_CONFIRM_BUTTON,
|
||||||
|
AUTH_SERVER_CHANGE_CANCEL_BUTTON
|
||||||
} = require('../config/messages'); //Сообщения
|
} = require('../config/messages'); //Сообщения
|
||||||
const styles = require('../styles/screens/AuthScreen.styles'); //Стили экрана
|
const styles = require('../styles/screens/AuthScreen.styles'); //Стили экрана
|
||||||
|
|
||||||
@ -48,9 +52,9 @@ const styles = require('../styles/screens/AuthScreen.styles'); //Стили эк
|
|||||||
|
|
||||||
//Экран аутентификации
|
//Экран аутентификации
|
||||||
function AuthScreen() {
|
function AuthScreen() {
|
||||||
const { showError, showSuccess, showInfo } = useAppMessagingContext();
|
const { showError, showInfo } = useAppMessagingContext();
|
||||||
const { APP_MODE, mode, setOnline } = useAppModeContext();
|
const { APP_MODE, mode, setOnline } = useAppModeContext();
|
||||||
const { navigate, goBack, canGoBack, reset, screenParams, SCREENS } = useAppNavigationContext();
|
const { navigate, goBack, canGoBack, reset, screenParams, currentScreen, SCREENS } = useAppNavigationContext();
|
||||||
const { getSetting, isDbReady, clearInspections } = useAppLocalDbContext();
|
const { getSetting, isDbReady, clearInspections } = useAppLocalDbContext();
|
||||||
const {
|
const {
|
||||||
session,
|
session,
|
||||||
@ -60,23 +64,26 @@ function AuthScreen() {
|
|||||||
getSavedCredentials,
|
getSavedCredentials,
|
||||||
getAuthSession,
|
getAuthSession,
|
||||||
clearAuthSession,
|
clearAuthSession,
|
||||||
authFormData,
|
clearAuthFormData,
|
||||||
updateAuthFormData,
|
getAndClearLastSavedServerUrlFromSettings
|
||||||
clearAuthFormData
|
|
||||||
} = useAppAuthContext();
|
} = useAppAuthContext();
|
||||||
|
|
||||||
|
//Данные формы при монтировании — из хранилища (восстанавливаются при возврате с других экранов)
|
||||||
|
const initialForm = getAuthFormStore();
|
||||||
|
|
||||||
//Состояние меню
|
//Состояние меню
|
||||||
const [menuVisible, setMenuVisible] = React.useState(false);
|
const [menuVisible, setMenuVisible] = React.useState(false);
|
||||||
|
|
||||||
//Состояние формы
|
//Состояние формы
|
||||||
const [serverUrl, setServerUrl] = React.useState(authFormData.serverUrl);
|
const [serverUrl, setServerUrl] = React.useState(initialForm.serverUrl || '');
|
||||||
const [username, setUsername] = React.useState(authFormData.username);
|
const [username, setUsername] = React.useState(initialForm.username || '');
|
||||||
const [password, setPassword] = React.useState(authFormData.password);
|
const [password, setPassword] = React.useState(initialForm.password || '');
|
||||||
const [savePassword, setSavePassword] = React.useState(authFormData.savePassword);
|
const [savePassword, setSavePassword] = React.useState(!!initialForm.savePassword);
|
||||||
const [showPassword, setShowPassword] = React.useState(false);
|
const [showPassword, setShowPassword] = React.useState(!!initialForm.showPassword);
|
||||||
|
|
||||||
//Состояние отображения
|
//Состояние отображения
|
||||||
const [hideServerUrl, setHideServerUrl] = React.useState(false);
|
const [hideServerUrl, setHideServerUrl] = React.useState(false);
|
||||||
|
const [savedServerUrlFromSettings, setSavedServerUrlFromSettings] = React.useState('');
|
||||||
const [isSettingsLoaded, setIsSettingsLoaded] = React.useState(false);
|
const [isSettingsLoaded, setIsSettingsLoaded] = React.useState(false);
|
||||||
|
|
||||||
//Флаг для предотвращения повторной загрузки
|
//Флаг для предотвращения повторной загрузки
|
||||||
@ -94,31 +101,80 @@ function AuthScreen() {
|
|||||||
const isFromMenu = screenParams?.fromMenu === true;
|
const isFromMenu = screenParams?.fromMenu === true;
|
||||||
const fromMenuKey = screenParams?.fromMenuKey;
|
const fromMenuKey = screenParams?.fromMenuKey;
|
||||||
|
|
||||||
//Ref для хранения актуальных значений формы (для синхронизации при размонтировании)
|
//Ref актуальных значений формы (для записи в store при размонтировании)
|
||||||
const formDataRef = React.useRef({ serverUrl, username, password, savePassword });
|
const formDataRef = React.useRef({
|
||||||
|
serverUrl,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
savePassword,
|
||||||
|
showPassword
|
||||||
|
});
|
||||||
|
|
||||||
//Ref полей ввода для перехода фокуса по Enter
|
//Ref полей ввода для перехода фокуса по Enter
|
||||||
const serverInputRef = React.useRef(null);
|
const serverInputRef = React.useRef(null);
|
||||||
const loginInputRef = React.useRef(null);
|
const loginInputRef = React.useRef(null);
|
||||||
const passwordInputRef = React.useRef(null);
|
const passwordInputRef = React.useRef(null);
|
||||||
|
|
||||||
//Обновление ref при изменении значений формы
|
//Актуализация ref и store при изменении полей; при размонтировании — сохранение в store
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
formDataRef.current = { serverUrl, username, password, savePassword };
|
const next = { serverUrl, username, password, savePassword, showPassword };
|
||||||
}, [serverUrl, username, password, savePassword]);
|
formDataRef.current = next;
|
||||||
|
setAuthFormStore(next);
|
||||||
|
return () => setAuthFormStore(formDataRef.current);
|
||||||
|
}, [serverUrl, username, password, savePassword, showPassword]);
|
||||||
|
|
||||||
//Синхронизация данных формы с контекстом при размонтировании компонента
|
//Обработчики полей формы: сразу пишем в store и ref, затем setState
|
||||||
React.useEffect(() => {
|
const handleServerUrlChange = React.useCallback(value => {
|
||||||
return () => {
|
formDataRef.current.serverUrl = value;
|
||||||
updateAuthFormData(formDataRef.current);
|
setAuthFormStore({ serverUrl: value });
|
||||||
|
setServerUrl(value);
|
||||||
|
}, []);
|
||||||
|
const handleUsernameChange = React.useCallback(value => {
|
||||||
|
formDataRef.current.username = value;
|
||||||
|
setAuthFormStore({ username: value });
|
||||||
|
setUsername(value);
|
||||||
|
}, []);
|
||||||
|
const handlePasswordChange = React.useCallback(value => {
|
||||||
|
formDataRef.current.password = value;
|
||||||
|
setAuthFormStore({ password: value });
|
||||||
|
setPassword(value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//Восстановление формы при возврате на экран логина
|
||||||
|
React.useLayoutEffect(() => {
|
||||||
|
if (!canGoBack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const storedForm = getAuthFormStore();
|
||||||
|
const savedServerUrl = getAndClearLastSavedServerUrlFromSettings();
|
||||||
|
const serverUrlVal = savedServerUrl || (storedForm.serverUrl != null ? String(storedForm.serverUrl) : '');
|
||||||
|
const usernameVal = storedForm.username != null ? String(storedForm.username) : '';
|
||||||
|
const passwordVal = storedForm.password != null ? String(storedForm.password) : '';
|
||||||
|
const savePasswordVal = Boolean(storedForm.savePassword);
|
||||||
|
const showPasswordVal = Boolean(storedForm.showPassword);
|
||||||
|
setServerUrl(serverUrlVal);
|
||||||
|
setUsername(usernameVal);
|
||||||
|
setPassword(passwordVal);
|
||||||
|
setSavePassword(savePasswordVal);
|
||||||
|
setShowPassword(showPasswordVal);
|
||||||
|
formDataRef.current = {
|
||||||
|
serverUrl: serverUrlVal,
|
||||||
|
username: usernameVal,
|
||||||
|
password: passwordVal,
|
||||||
|
savePassword: savePasswordVal,
|
||||||
|
showPassword: showPasswordVal
|
||||||
};
|
};
|
||||||
}, [updateAuthFormData]);
|
}, [canGoBack, getAndClearLastSavedServerUrlFromSettings]);
|
||||||
|
|
||||||
//При готовности БД один раз подставляем сохранённый логин в поле (если оно пустое)
|
//При готовности БД один раз подставляем сохранённый логин в поле (только если в форме ещё нет логина)
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!isDbReady || savedLoginFilledRef.current) {
|
if (!isDbReady || savedLoginFilledRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const storedForm = getAuthFormStore();
|
||||||
|
if (storedForm.username && String(storedForm.username).trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
savedLoginFilledRef.current = true;
|
savedLoginFilledRef.current = true;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
getSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN)
|
getSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN)
|
||||||
@ -133,8 +189,7 @@ function AuthScreen() {
|
|||||||
};
|
};
|
||||||
}, [isDbReady, getSetting]);
|
}, [isDbReady, getSetting]);
|
||||||
|
|
||||||
//Сброс формы и загрузка credentials при переходе по кнопке "Вход" из меню или при открытии экрана в оффлайн
|
//Сброс формы и загрузка credentials при открытии из меню или в оффлайн
|
||||||
//fromMenuKey в deps обеспечивает повторную загрузку при каждом новом переходе из меню
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const shouldLoadFromMenu = isFromMenu && isDbReady;
|
const shouldLoadFromMenu = isFromMenu && isDbReady;
|
||||||
const shouldLoadOffline = mode === APP_MODE.OFFLINE && isDbReady;
|
const shouldLoadOffline = mode === APP_MODE.OFFLINE && isDbReady;
|
||||||
@ -146,6 +201,7 @@ function AuthScreen() {
|
|||||||
try {
|
try {
|
||||||
if (isFromMenu) {
|
if (isFromMenu) {
|
||||||
clearAuthFormData();
|
clearAuthFormData();
|
||||||
|
clearAuthFormStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Загружаем сохранённый URL сервера
|
//Загружаем сохранённый URL сервера
|
||||||
@ -156,6 +212,7 @@ function AuthScreen() {
|
|||||||
setServerUrl(savedServerUrl);
|
setServerUrl(savedServerUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSavedServerUrlFromSettings(savedServerUrl || '');
|
||||||
setHideServerUrl(savedHideServerUrl === 'true' || savedHideServerUrl === true);
|
setHideServerUrl(savedHideServerUrl === 'true' || savedHideServerUrl === true);
|
||||||
|
|
||||||
//Загружаем сохранённые credentials
|
//Загружаем сохранённые credentials
|
||||||
@ -215,11 +272,38 @@ function AuthScreen() {
|
|||||||
loadSavedLoginOnly();
|
loadSavedLoginOnly();
|
||||||
}, [isDbReady, username, isFromMenu, mode, APP_MODE.OFFLINE, getSetting]);
|
}, [isDbReady, username, isFromMenu, mode, APP_MODE.OFFLINE, getSetting]);
|
||||||
|
|
||||||
//Загрузка настроек при готовности БД
|
//Загрузка адреса сервера и настройки видимости поля (при возврате на экран форму не перезаписываем)
|
||||||
//В т.ч. при работе в оффлайн подставляем сохранённый логин и при необходимости пароль
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
//Пропускаем если БД не готова, уже загружали или открыто из меню
|
if (!isDbReady || currentScreen !== SCREENS.AUTH) {
|
||||||
if (!isDbReady || initialLoadRef.current || isFromMenu) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadServerAndVisibility = async () => {
|
||||||
|
try {
|
||||||
|
const savedUrl = await getSetting(AUTH_SETTINGS_KEYS.SERVER_URL);
|
||||||
|
const savedHide = await getSetting(AUTH_SETTINGS_KEYS.HIDE_SERVER_URL);
|
||||||
|
const trimmedUrl = savedUrl && String(savedUrl).trim() ? String(savedUrl).trim() : '';
|
||||||
|
if (trimmedUrl && !canGoBack) {
|
||||||
|
setServerUrl(trimmedUrl);
|
||||||
|
}
|
||||||
|
setSavedServerUrlFromSettings(trimmedUrl);
|
||||||
|
setHideServerUrl(savedHide === 'true' || savedHide === true);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Загрузка адреса сервера и настройки видимости:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadServerAndVisibility();
|
||||||
|
}, [isDbReady, currentScreen, SCREENS.AUTH, canGoBack, getSetting]);
|
||||||
|
|
||||||
|
//Загрузка настроек при готовности БД (только при первом открытии экрана; не перезаписываем форму, если в store уже есть введённые данные — возврат из настроек)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isDbReady || initialLoadRef.current || isFromMenu || canGoBack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stored = getAuthFormStore();
|
||||||
|
if ((stored.username && String(stored.username).trim()) || (stored.password && String(stored.password).length > 0)) {
|
||||||
|
setIsSettingsLoaded(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,16 +314,15 @@ function AuthScreen() {
|
|||||||
const savedServerUrl = await getSetting(AUTH_SETTINGS_KEYS.SERVER_URL);
|
const savedServerUrl = await getSetting(AUTH_SETTINGS_KEYS.SERVER_URL);
|
||||||
const savedHideServerUrl = await getSetting(AUTH_SETTINGS_KEYS.HIDE_SERVER_URL);
|
const savedHideServerUrl = await getSetting(AUTH_SETTINGS_KEYS.HIDE_SERVER_URL);
|
||||||
|
|
||||||
//Получаем текущие значения формы
|
|
||||||
const currentFormData = formDataRef.current;
|
const currentFormData = formDataRef.current;
|
||||||
|
|
||||||
if (savedServerUrl && !currentFormData.serverUrl) {
|
if (savedServerUrl && !currentFormData.serverUrl) {
|
||||||
setServerUrl(savedServerUrl);
|
setServerUrl(savedServerUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSavedServerUrlFromSettings(savedServerUrl || '');
|
||||||
setHideServerUrl(savedHideServerUrl === 'true' || savedHideServerUrl === true);
|
setHideServerUrl(savedHideServerUrl === 'true' || savedHideServerUrl === true);
|
||||||
|
|
||||||
//Загружаем сохранённые credentials (логин/пароль) для отображения в т.ч. в оффлайн
|
|
||||||
const savedCredentials = await getSavedCredentials();
|
const savedCredentials = await getSavedCredentials();
|
||||||
if (savedCredentials && savedCredentials.login) {
|
if (savedCredentials && savedCredentials.login) {
|
||||||
setUsername(savedCredentials.login);
|
setUsername(savedCredentials.login);
|
||||||
@ -263,7 +346,7 @@ function AuthScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
}, [isDbReady, isFromMenu, getSetting, getSavedCredentials]);
|
}, [isDbReady, isFromMenu, canGoBack, getSetting, getSavedCredentials]);
|
||||||
|
|
||||||
//Валидация формы
|
//Валидация формы
|
||||||
const validateForm = React.useCallback(() => {
|
const validateForm = React.useCallback(() => {
|
||||||
@ -323,13 +406,13 @@ function AuthScreen() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccess(LOGIN_SUCCESS_MESSAGE);
|
|
||||||
setOnline();
|
setOnline();
|
||||||
clearAuthFormData();
|
clearAuthFormData();
|
||||||
|
clearAuthFormStore();
|
||||||
reset();
|
reset();
|
||||||
}, [login, serverUrl, username, password, savePassword, getSetting, showError, showSuccess, setOnline, clearAuthFormData, reset]);
|
}, [login, serverUrl, username, password, savePassword, getSetting, showError, setOnline, clearAuthFormData, reset]);
|
||||||
|
|
||||||
//Обработчик входа: проверка смены параметров подключения, при необходимости — предупреждение и сброс локальных данных
|
//Обработчик входа (очистка локальных данных только при смене сервера относительно последнего успешного подключения)
|
||||||
const handleLogin = React.useCallback(async () => {
|
const handleLogin = React.useCallback(async () => {
|
||||||
if (!validateForm()) {
|
if (!validateForm()) {
|
||||||
return;
|
return;
|
||||||
@ -337,24 +420,24 @@ function AuthScreen() {
|
|||||||
|
|
||||||
const currentNormalized = normalizeServerUrl(serverUrl);
|
const currentNormalized = normalizeServerUrl(serverUrl);
|
||||||
const prevSession = await getAuthSession();
|
const prevSession = await getAuthSession();
|
||||||
const savedServerUrl = await getSetting(AUTH_SETTINGS_KEYS.SERVER_URL).catch(() => '');
|
const lastConnectedServerUrl = await getSetting(AUTH_SETTINGS_KEYS.LAST_CONNECTED_SERVER_URL).catch(() => '');
|
||||||
const prevServerUrl = prevSession?.serverUrl || savedServerUrl;
|
const prevServerUrl = prevSession?.serverUrl || lastConnectedServerUrl || '';
|
||||||
const prevNormalized = normalizeServerUrl(prevServerUrl);
|
const prevNormalized = prevServerUrl ? normalizeServerUrl(prevServerUrl) : '';
|
||||||
|
|
||||||
const connectionParamsChanged = prevNormalized !== '' && currentNormalized !== prevNormalized;
|
const connectionParamsChanged = prevNormalized !== '' && currentNormalized !== prevNormalized;
|
||||||
|
|
||||||
if (connectionParamsChanged) {
|
if (connectionParamsChanged) {
|
||||||
showInfo('При смене параметров подключения все локальные данные будут сброшены. Продолжить?', {
|
showInfo(AUTH_SERVER_CHANGE_CONFIRM_MESSAGE, {
|
||||||
title: 'Подтверждение входа',
|
title: AUTH_SERVER_CHANGE_CONFIRM_TITLE,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
id: 'cancel',
|
id: 'cancel',
|
||||||
title: 'Отмена',
|
title: AUTH_SERVER_CHANGE_CANCEL_BUTTON,
|
||||||
onPress: () => {}
|
onPress: () => {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'confirm',
|
id: 'confirm',
|
||||||
title: 'Продолжить',
|
title: AUTH_SERVER_CHANGE_CONFIRM_BUTTON,
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
await clearAuthSession();
|
await clearAuthSession();
|
||||||
await clearInspections();
|
await clearInspections();
|
||||||
@ -392,20 +475,18 @@ function AuthScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Успешный вход
|
//Успешный вход
|
||||||
showSuccess(LOGIN_SUCCESS_MESSAGE);
|
|
||||||
setOnline();
|
setOnline();
|
||||||
|
|
||||||
//Очищаем временные данные
|
//Очищаем временные данные
|
||||||
setPendingLoginData(null);
|
setPendingLoginData(null);
|
||||||
setOrganizations([]);
|
setOrganizations([]);
|
||||||
|
|
||||||
//Очищаем данные формы в контексте
|
|
||||||
clearAuthFormData();
|
clearAuthFormData();
|
||||||
|
clearAuthFormStore();
|
||||||
|
|
||||||
//Сбрасываем навигацию
|
//Сбрасываем навигацию
|
||||||
reset();
|
reset();
|
||||||
},
|
},
|
||||||
[pendingLoginData, selectCompany, showError, showSuccess, setOnline, clearAuthFormData, reset]
|
[pendingLoginData, selectCompany, showError, setOnline, clearAuthFormData, reset]
|
||||||
);
|
);
|
||||||
|
|
||||||
//Обработчик отмены выбора организации
|
//Обработчик отмены выбора организации
|
||||||
@ -424,11 +505,15 @@ function AuthScreen() {
|
|||||||
|
|
||||||
//Обработчик переключения показа пароля
|
//Обработчик переключения показа пароля
|
||||||
const handleTogglePassword = React.useCallback(value => {
|
const handleTogglePassword = React.useCallback(value => {
|
||||||
|
formDataRef.current.showPassword = value;
|
||||||
|
setAuthFormStore({ showPassword: value });
|
||||||
setShowPassword(value);
|
setShowPassword(value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
//Обработчик переключения сохранения пароля
|
//Обработчик переключения сохранения пароля
|
||||||
const handleToggleSavePassword = React.useCallback(value => {
|
const handleToggleSavePassword = React.useCallback(value => {
|
||||||
|
formDataRef.current.savePassword = value;
|
||||||
|
setAuthFormStore({ savePassword: value });
|
||||||
setSavePassword(value);
|
setSavePassword(value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -495,8 +580,8 @@ function AuthScreen() {
|
|||||||
];
|
];
|
||||||
}, [handleOpenSettings, handleShowAbout]);
|
}, [handleOpenSettings, handleShowAbout]);
|
||||||
|
|
||||||
//Поле сервера показываем, если настройка выключена или сервер не указан
|
//Поле сервера показываем, если настройка выключена или в настройках ещё не сохранён адрес
|
||||||
const shouldShowServerUrl = isServerUrlFieldVisible(hideServerUrl, serverUrl);
|
const shouldShowServerUrl = isServerUrlFieldVisible(hideServerUrl, savedServerUrlFromSettings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdaptiveView padding={false}>
|
<AdaptiveView padding={false}>
|
||||||
@ -528,12 +613,12 @@ function AuthScreen() {
|
|||||||
key="server-input"
|
key="server-input"
|
||||||
label="Сервер"
|
label="Сервер"
|
||||||
value={serverUrl}
|
value={serverUrl}
|
||||||
onChangeText={setServerUrl}
|
onChangeText={handleServerUrlChange}
|
||||||
placeholder="https://example.com/api"
|
placeholder="https://example.com/api"
|
||||||
keyboardType="url"
|
keyboardType="url"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
disabled={isLoading}
|
disabled={isLoading || isFromMenu}
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
returnKeyType="next"
|
returnKeyType="next"
|
||||||
onSubmitEditing={handleServerSubmitEditing}
|
onSubmitEditing={handleServerSubmitEditing}
|
||||||
@ -545,7 +630,7 @@ function AuthScreen() {
|
|||||||
key="login-input"
|
key="login-input"
|
||||||
label="Логин"
|
label="Логин"
|
||||||
value={username}
|
value={username}
|
||||||
onChangeText={setUsername}
|
onChangeText={handleUsernameChange}
|
||||||
placeholder="Введите логин"
|
placeholder="Введите логин"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
@ -560,7 +645,7 @@ function AuthScreen() {
|
|||||||
key="password-input"
|
key="password-input"
|
||||||
label="Пароль"
|
label="Пароль"
|
||||||
value={password}
|
value={password}
|
||||||
onChangeText={setPassword}
|
onChangeText={handlePasswordChange}
|
||||||
placeholder="Введите пароль"
|
placeholder="Введите пароль"
|
||||||
showPassword={showPassword}
|
showPassword={showPassword}
|
||||||
onTogglePassword={handleTogglePassword}
|
onTogglePassword={handleTogglePassword}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Предрейсовые осмотры - мобильное приложение
|
Предрейсовые осмотры - мобильное приложение
|
||||||
Главный экран приложения с боковым меню
|
Главный экран приложения
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//---------------------
|
//---------------------
|
||||||
@ -9,33 +9,21 @@
|
|||||||
|
|
||||||
const React = require('react'); //React и хуки
|
const React = require('react'); //React и хуки
|
||||||
const { View } = require('react-native'); //Базовые компоненты
|
const { View } = require('react-native'); //Базовые компоненты
|
||||||
const { LOAD_STATUS_LOADING } = require('../config/loadStatus'); //Статусы загрузки
|
|
||||||
const { useAppMessagingContext } = require('../components/layout/AppMessagingProvider'); //Контекст сообщений
|
const { useAppMessagingContext } = require('../components/layout/AppMessagingProvider'); //Контекст сообщений
|
||||||
const { useAppModeContext } = require('../components/layout/AppModeProvider'); //Контекст режима работы
|
const { useAppModeContext } = require('../components/layout/AppModeProvider'); //Контекст режима работы
|
||||||
const { useAppNavigationContext } = require('../components/layout/AppNavigationProvider'); //Контекст навигации
|
const { useAppNavigationContext } = require('../components/layout/AppNavigationProvider'); //Контекст навигации
|
||||||
const { useAppLocalDbContext } = require('../components/layout/AppLocalDbProvider'); //Контекст локальной БД
|
const { useAppLocalDbContext } = require('../components/layout/AppLocalDbProvider'); //Контекст локальной БД
|
||||||
const { useAppPreTripInspectionsContext } = require('../components/layout/AppPreTripInspectionsProvider'); //Контекст осмотров
|
|
||||||
const { useAppAuthContext } = require('../components/layout/AppAuthProvider'); //Контекст авторизации
|
const { useAppAuthContext } = require('../components/layout/AppAuthProvider'); //Контекст авторизации
|
||||||
const AppHeader = require('../components/layout/AppHeader'); //Заголовок с меню
|
const AppHeader = require('../components/layout/AppHeader'); //Заголовок с меню
|
||||||
const SideMenu = require('../components/menu/SideMenu'); //Боковое меню
|
const SideMenu = require('../components/menu/SideMenu'); //Боковое меню
|
||||||
const InspectionList = require('../components/inspections/InspectionList'); //Список осмотров
|
const ScannerArea = require('../components/scanner/ScannerArea'); //Область сканера
|
||||||
const LoadingOverlay = require('../components/common/LoadingOverlay'); //Оверлей загрузки
|
const ScanResultModal = require('../components/scanner/ScanResultModal'); //Модальное окно результата сканирования
|
||||||
const OrganizationSelectDialog = require('../components/auth/OrganizationSelectDialog'); //Диалог выбора организации
|
const { AUTH_SETTINGS_KEYS } = require('../config/authConfig'); //Ключи настроек
|
||||||
const { getAppInfo } = require('../utils/appInfo'); //Информация о приложении
|
const { getAppInfo } = require('../utils/appInfo'); //Информация о приложении
|
||||||
const {
|
const { APP_ABOUT_TITLE, SIDE_MENU_TITLE, MENU_ITEM_SETTINGS, MENU_ITEM_ABOUT, MENU_ITEM_LOGIN, MENU_ITEM_LOGOUT } = require('../config/messages'); //Сообщения
|
||||||
CONNECTION_LOST_MESSAGE,
|
|
||||||
OFFLINE_MODE_TITLE,
|
|
||||||
APP_ABOUT_TITLE,
|
|
||||||
SIDE_MENU_TITLE,
|
|
||||||
ORGANIZATION_SELECT_DIALOG_TITLE,
|
|
||||||
MENU_ITEM_SETTINGS,
|
|
||||||
MENU_ITEM_ABOUT,
|
|
||||||
MENU_ITEM_LOGIN,
|
|
||||||
MENU_ITEM_LOGOUT,
|
|
||||||
LOGOUT_SUCCESS_MESSAGE
|
|
||||||
} = require('../config/messages'); //Сообщения
|
|
||||||
const { DIALOG_BUTTON_TYPE, DIALOG_CANCEL_BUTTON, getConfirmButtonOptions } = require('../config/dialogButtons'); //Кнопки диалогов
|
const { DIALOG_BUTTON_TYPE, DIALOG_CANCEL_BUTTON, getConfirmButtonOptions } = require('../config/dialogButtons'); //Кнопки диалогов
|
||||||
const { APP_COLORS } = require('../config/theme'); //Цветовая схема
|
const { APP_COLORS } = require('../config/theme'); //Цветовая схема
|
||||||
|
const { shouldShowScanner } = require('../config/scannerConfig'); //Логика видимости сканера
|
||||||
const styles = require('../styles/screens/MainScreen.styles'); //Стили экрана
|
const styles = require('../styles/screens/MainScreen.styles'); //Стили экрана
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -44,162 +32,40 @@ const styles = require('../styles/screens/MainScreen.styles'); //Стили эк
|
|||||||
|
|
||||||
//Главный экран приложения
|
//Главный экран приложения
|
||||||
function MainScreen() {
|
function MainScreen() {
|
||||||
const { inspections, loadStatus, error, isDbReady, refreshInspections } = useAppPreTripInspectionsContext();
|
const { showInfo, showError } = useAppMessagingContext();
|
||||||
const { showInfo, showError, showSuccess } = useAppMessagingContext();
|
const { mode, setNotConnected } = useAppModeContext();
|
||||||
const { mode, setOnline, setOffline, setNotConnected } = useAppModeContext();
|
|
||||||
const { navigate, SCREENS, setInitialScreen } = useAppNavigationContext();
|
const { navigate, SCREENS, setInitialScreen } = useAppNavigationContext();
|
||||||
const { getSetting, isDbReady: isLocalDbReady } = useAppLocalDbContext();
|
const { getSetting, isDbReady: isLocalDbReady } = useAppLocalDbContext();
|
||||||
const {
|
const { session, isAuthenticated, logout, isStartupSessionCheckInProgress } = useAppAuthContext();
|
||||||
session,
|
|
||||||
isAuthenticated,
|
|
||||||
isInitialized,
|
|
||||||
logout,
|
|
||||||
checkSession,
|
|
||||||
selectCompany,
|
|
||||||
isLoading: isAuthLoading,
|
|
||||||
sessionChecked,
|
|
||||||
markSessionChecked,
|
|
||||||
getAndClearSessionRestoredFromStorage
|
|
||||||
} = useAppAuthContext();
|
|
||||||
|
|
||||||
const [menuVisible, setMenuVisible] = React.useState(false);
|
const [menuVisible, setMenuVisible] = React.useState(false);
|
||||||
const [serverUrl, setServerUrl] = React.useState('');
|
const [serverUrl, setServerUrl] = React.useState('');
|
||||||
|
const [alwaysShowScanner, setAlwaysShowScanner] = React.useState(false);
|
||||||
|
const [scanResult, setScanResult] = React.useState(null);
|
||||||
|
|
||||||
//Состояние для диалога выбора организации при проверке сессии
|
//Загрузка настроек главного экрана при готовности БД
|
||||||
const [showOrgDialog, setShowOrgDialog] = React.useState(false);
|
|
||||||
const [organizations, setOrganizations] = React.useState([]);
|
|
||||||
const [pendingSessionData, setPendingSessionData] = React.useState(null);
|
|
||||||
|
|
||||||
//Предотвращение повторной загрузки при монтировании
|
|
||||||
const initialLoadRef = React.useRef(false);
|
|
||||||
|
|
||||||
//Загрузка URL сервера при готовности БД
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!isLocalDbReady) {
|
if (!isLocalDbReady) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadServerUrl = async () => {
|
const loadMainScreenSettings = async () => {
|
||||||
try {
|
try {
|
||||||
const savedUrl = await getSetting('app_server_url');
|
const savedUrl = await getSetting(AUTH_SETTINGS_KEYS.SERVER_URL);
|
||||||
if (savedUrl) {
|
if (savedUrl) {
|
||||||
setServerUrl(savedUrl);
|
setServerUrl(savedUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const savedAlwaysShowScanner = await getSetting(AUTH_SETTINGS_KEYS.ALWAYS_SHOW_SCANNER);
|
||||||
|
setAlwaysShowScanner(savedAlwaysShowScanner === 'true' || savedAlwaysShowScanner === true);
|
||||||
} catch (loadError) {
|
} catch (loadError) {
|
||||||
console.error('Ошибка загрузки URL сервера:', loadError);
|
console.error('Ошибка загрузки настроек главного экрана:', loadError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadServerUrl();
|
loadMainScreenSettings();
|
||||||
}, [isLocalDbReady, getSetting]);
|
}, [isLocalDbReady, getSetting]);
|
||||||
|
|
||||||
//Проверка соединения только при открытии приложения и только если пользователь был авторизован и не выходил
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!isInitialized || sessionChecked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!getAndClearSessionRestoredFromStorage()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
markSessionChecked();
|
|
||||||
|
|
||||||
const verifySession = async () => {
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await checkSession();
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
//Сессия недействительна
|
|
||||||
showInfo('Сессия истекла. Выполните повторный вход.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Проверяем необходимость выбора организации
|
|
||||||
if (result.needSelectCompany) {
|
|
||||||
setOrganizations(result.organizations);
|
|
||||||
setPendingSessionData({
|
|
||||||
serverUrl: result.serverUrl,
|
|
||||||
sessionId: result.sessionId,
|
|
||||||
user: result.user,
|
|
||||||
savePassword: result.savePassword
|
|
||||||
});
|
|
||||||
setShowOrgDialog(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Устанавливаем режим работы на основе проверки сервера
|
|
||||||
if (result.isOffline) {
|
|
||||||
setOffline();
|
|
||||||
showInfo(CONNECTION_LOST_MESSAGE, { title: OFFLINE_MODE_TITLE });
|
|
||||||
} else {
|
|
||||||
setOnline();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
verifySession();
|
|
||||||
}, [
|
|
||||||
isInitialized,
|
|
||||||
isAuthenticated,
|
|
||||||
sessionChecked,
|
|
||||||
markSessionChecked,
|
|
||||||
getAndClearSessionRestoredFromStorage,
|
|
||||||
checkSession,
|
|
||||||
setOnline,
|
|
||||||
setOffline,
|
|
||||||
showInfo
|
|
||||||
]);
|
|
||||||
|
|
||||||
//Первичная загрузка данных
|
|
||||||
React.useEffect(() => {
|
|
||||||
//Выходим, если БД не готова или уже загружали
|
|
||||||
if (!isDbReady || initialLoadRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
initialLoadRef.current = true;
|
|
||||||
refreshInspections();
|
|
||||||
}, [isDbReady, refreshInspections]);
|
|
||||||
|
|
||||||
//Обработчик выбора организации при проверке сессии
|
|
||||||
const handleSelectOrganization = React.useCallback(
|
|
||||||
async org => {
|
|
||||||
setShowOrgDialog(false);
|
|
||||||
|
|
||||||
if (!pendingSessionData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await selectCompany({
|
|
||||||
serverUrl: pendingSessionData.serverUrl,
|
|
||||||
sessionId: pendingSessionData.sessionId,
|
|
||||||
user: pendingSessionData.user,
|
|
||||||
company: org,
|
|
||||||
savePassword: pendingSessionData.savePassword
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
showError(result.error || 'Ошибка выбора организации');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnline();
|
|
||||||
setPendingSessionData(null);
|
|
||||||
setOrganizations([]);
|
|
||||||
},
|
|
||||||
[pendingSessionData, selectCompany, showError, setOnline]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Обработчик отмены выбора организации
|
|
||||||
const handleCancelOrganization = React.useCallback(() => {
|
|
||||||
setShowOrgDialog(false);
|
|
||||||
setPendingSessionData(null);
|
|
||||||
setOrganizations([]);
|
|
||||||
//При отмене остаемся в оффлайн режиме
|
|
||||||
setOffline();
|
|
||||||
}, [setOffline]);
|
|
||||||
|
|
||||||
//Обработчик открытия меню
|
//Обработчик открытия меню
|
||||||
const handleMenuOpen = React.useCallback(() => {
|
const handleMenuOpen = React.useCallback(() => {
|
||||||
setMenuVisible(true);
|
setMenuVisible(true);
|
||||||
@ -233,18 +99,17 @@ function MainScreen() {
|
|||||||
navigate(SCREENS.SETTINGS);
|
navigate(SCREENS.SETTINGS);
|
||||||
}, [navigate, SCREENS.SETTINGS]);
|
}, [navigate, SCREENS.SETTINGS]);
|
||||||
|
|
||||||
//Обработчик подтверждения выхода (для диалога)
|
//Выполнение выхода (для диалога подтверждения)
|
||||||
const performLogout = React.useCallback(async () => {
|
const performLogout = React.useCallback(async () => {
|
||||||
const result = await logout({ skipServerRequest: mode === 'OFFLINE' });
|
const result = await logout({ skipServerRequest: mode === 'OFFLINE' });
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
showSuccess(LOGOUT_SUCCESS_MESSAGE);
|
|
||||||
setNotConnected();
|
setNotConnected();
|
||||||
setInitialScreen(SCREENS.AUTH);
|
setInitialScreen(SCREENS.AUTH);
|
||||||
} else {
|
} else {
|
||||||
showError(result.error || 'Ошибка выхода');
|
showError(result.error || 'Ошибка выхода');
|
||||||
}
|
}
|
||||||
}, [logout, mode, showSuccess, showError, setNotConnected, setInitialScreen, SCREENS.AUTH]);
|
}, [logout, mode, showError, setNotConnected, setInitialScreen, SCREENS.AUTH]);
|
||||||
|
|
||||||
//Обработчик выхода из приложения
|
//Обработчик выхода из приложения
|
||||||
const handleLogout = React.useCallback(() => {
|
const handleLogout = React.useCallback(() => {
|
||||||
@ -299,17 +164,29 @@ function MainScreen() {
|
|||||||
return items;
|
return items;
|
||||||
}, [handleOpenSettings, handleShowAbout, handleLogin, handleLogout, mode, isAuthenticated]);
|
}, [handleOpenSettings, handleShowAbout, handleLogin, handleLogout, mode, isAuthenticated]);
|
||||||
|
|
||||||
|
//Обработчик результата сканирования
|
||||||
|
const handleScanResult = React.useCallback(result => {
|
||||||
|
setScanResult(result);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//Закрытие модального окна результата сканирования
|
||||||
|
const handleCloseScanResult = React.useCallback(() => {
|
||||||
|
setScanResult(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//Видимость сканера
|
||||||
|
const isScannerOpen = shouldShowScanner({
|
||||||
|
alwaysShowScanner,
|
||||||
|
hasScanResult: scanResult != null,
|
||||||
|
isStartupCheckInProgress: isStartupSessionCheckInProgress
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<AppHeader onMenuPress={handleMenuOpen} />
|
<AppHeader onMenuPress={handleMenuOpen} />
|
||||||
|
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<InspectionList
|
<ScannerArea alwaysShowScanner={alwaysShowScanner} scannerOpen={isScannerOpen} onScanResult={handleScanResult} />
|
||||||
inspections={inspections}
|
|
||||||
isLoading={loadStatus === LOAD_STATUS_LOADING}
|
|
||||||
error={error}
|
|
||||||
onRefresh={refreshInspections}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<SideMenu
|
<SideMenu
|
||||||
@ -322,15 +199,12 @@ function MainScreen() {
|
|||||||
organization={session?.companyName}
|
organization={session?.companyName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<OrganizationSelectDialog
|
<ScanResultModal
|
||||||
visible={showOrgDialog}
|
visible={scanResult != null}
|
||||||
organizations={organizations}
|
codeType={scanResult?.type}
|
||||||
onSelect={handleSelectOrganization}
|
value={scanResult?.value}
|
||||||
onCancel={handleCancelOrganization}
|
onRequestClose={handleCloseScanResult}
|
||||||
title={ORGANIZATION_SELECT_DIALOG_TITLE}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LoadingOverlay visible={isAuthLoading} message="Выполняется операция..." />
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,13 @@ const { AUTH_SETTINGS_KEYS, DEFAULT_IDLE_TIMEOUT } = require('../config/authConf
|
|||||||
const { DIALOG_BUTTON_TYPE, DIALOG_CANCEL_BUTTON, getConfirmButtonOptions } = require('../config/dialogButtons'); //Кнопки диалогов
|
const { DIALOG_BUTTON_TYPE, DIALOG_CANCEL_BUTTON, getConfirmButtonOptions } = require('../config/dialogButtons'); //Кнопки диалогов
|
||||||
const { getAppInfo, getModeLabel } = require('../utils/appInfo'); //Информация о приложении и режиме
|
const { getAppInfo, getModeLabel } = require('../utils/appInfo'); //Информация о приложении и режиме
|
||||||
const { validateServerUrlAllowEmpty, validateIdleTimeout } = require('../utils/validation'); //Валидация
|
const { validateServerUrlAllowEmpty, validateIdleTimeout } = require('../utils/validation'); //Валидация
|
||||||
const { APP_ABOUT_TITLE, SETTINGS_SERVER_SAVED_MESSAGE, SETTINGS_RESET_SUCCESS_MESSAGE, MENU_ITEM_ABOUT } = require('../config/messages'); //Сообщения
|
const {
|
||||||
|
APP_ABOUT_TITLE,
|
||||||
|
SETTINGS_SERVER_SAVED_MESSAGE,
|
||||||
|
SETTINGS_RESET_SUCCESS_MESSAGE,
|
||||||
|
MENU_ITEM_ABOUT,
|
||||||
|
SCANNER_SETTING_LABEL
|
||||||
|
} = require('../config/messages'); //Сообщения
|
||||||
const styles = require('../styles/screens/SettingsScreen.styles'); //Стили экрана
|
const styles = require('../styles/screens/SettingsScreen.styles'); //Стили экрана
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -37,7 +43,7 @@ function SettingsScreen() {
|
|||||||
const { APP_MODE, mode, setNotConnected } = useAppModeContext();
|
const { APP_MODE, mode, setNotConnected } = useAppModeContext();
|
||||||
const { goBack, canGoBack } = useAppNavigationContext();
|
const { goBack, canGoBack } = useAppNavigationContext();
|
||||||
const { getSetting, setSetting, clearSettings, clearInspections, vacuum, isDbReady } = useAppLocalDbContext();
|
const { getSetting, setSetting, clearSettings, clearInspections, vacuum, isDbReady } = useAppLocalDbContext();
|
||||||
const { session, isAuthenticated, getDeviceId } = useAppAuthContext();
|
const { session, isAuthenticated, getDeviceId, setLastSavedServerUrlFromSettings } = useAppAuthContext();
|
||||||
|
|
||||||
const [serverUrl, setServerUrl] = React.useState('');
|
const [serverUrl, setServerUrl] = React.useState('');
|
||||||
const [hideServerUrl, setHideServerUrl] = React.useState(false);
|
const [hideServerUrl, setHideServerUrl] = React.useState(false);
|
||||||
@ -46,6 +52,7 @@ function SettingsScreen() {
|
|||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
const [isServerUrlDialogVisible, setIsServerUrlDialogVisible] = React.useState(false);
|
const [isServerUrlDialogVisible, setIsServerUrlDialogVisible] = React.useState(false);
|
||||||
const [isIdleTimeoutDialogVisible, setIsIdleTimeoutDialogVisible] = React.useState(false);
|
const [isIdleTimeoutDialogVisible, setIsIdleTimeoutDialogVisible] = React.useState(false);
|
||||||
|
const [alwaysShowScanner, setAlwaysShowScanner] = React.useState(false);
|
||||||
|
|
||||||
//Предотвращение повторной загрузки настроек
|
//Предотвращение повторной загрузки настроек
|
||||||
const settingsLoadedRef = React.useRef(false);
|
const settingsLoadedRef = React.useRef(false);
|
||||||
@ -83,6 +90,9 @@ function SettingsScreen() {
|
|||||||
//Получаем или генерируем идентификатор устройства
|
//Получаем или генерируем идентификатор устройства
|
||||||
const currentDeviceId = await getDeviceId();
|
const currentDeviceId = await getDeviceId();
|
||||||
setDeviceId(currentDeviceId || '');
|
setDeviceId(currentDeviceId || '');
|
||||||
|
|
||||||
|
const savedAlwaysShowScanner = await getSetting(AUTH_SETTINGS_KEYS.ALWAYS_SHOW_SCANNER);
|
||||||
|
setAlwaysShowScanner(savedAlwaysShowScanner === 'true' || savedAlwaysShowScanner === true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки настроек:', error);
|
console.error('Ошибка загрузки настроек:', error);
|
||||||
showError('Не удалось загрузить настройки');
|
showError('Не удалось загрузить настройки');
|
||||||
@ -141,6 +151,9 @@ function SettingsScreen() {
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
setServerUrl(valueToSave);
|
setServerUrl(valueToSave);
|
||||||
|
if (valueToSave) {
|
||||||
|
setLastSavedServerUrlFromSettings(valueToSave);
|
||||||
|
}
|
||||||
showSuccess(SETTINGS_SERVER_SAVED_MESSAGE);
|
showSuccess(SETTINGS_SERVER_SAVED_MESSAGE);
|
||||||
} else {
|
} else {
|
||||||
showError('Не удалось сохранить настройки');
|
showError('Не удалось сохранить настройки');
|
||||||
@ -152,7 +165,27 @@ function SettingsScreen() {
|
|||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
},
|
},
|
||||||
[setSetting, showError, showSuccess]
|
[setSetting, showError, showSuccess, setLastSavedServerUrlFromSettings]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Переключатель «Всегда отображать сканер на главном экране»
|
||||||
|
const handleToggleAlwaysShowScanner = React.useCallback(
|
||||||
|
async value => {
|
||||||
|
try {
|
||||||
|
const success = await setSetting(AUTH_SETTINGS_KEYS.ALWAYS_SHOW_SCANNER, value ? 'true' : 'false');
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
setAlwaysShowScanner(value);
|
||||||
|
showSuccess('Настройка сохранена');
|
||||||
|
} else {
|
||||||
|
showError('Не удалось сохранить настройку');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка сохранения настройки сканера:', error);
|
||||||
|
showError('Не удалось сохранить настройку');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setSetting, showSuccess, showError]
|
||||||
);
|
);
|
||||||
|
|
||||||
//Переключатель скрытия URL сервера в окне логина
|
//Переключатель скрытия URL сервера в окне логина
|
||||||
@ -247,6 +280,7 @@ function SettingsScreen() {
|
|||||||
if (success) {
|
if (success) {
|
||||||
setServerUrl('');
|
setServerUrl('');
|
||||||
setHideServerUrl(false);
|
setHideServerUrl(false);
|
||||||
|
setAlwaysShowScanner(false);
|
||||||
setIdleTimeout(defaultValue);
|
setIdleTimeout(defaultValue);
|
||||||
await setSetting(AUTH_SETTINGS_KEYS.IDLE_TIMEOUT, defaultValue);
|
await setSetting(AUTH_SETTINGS_KEYS.IDLE_TIMEOUT, defaultValue);
|
||||||
setNotConnected();
|
setNotConnected();
|
||||||
@ -409,6 +443,21 @@ function SettingsScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.section}>
|
||||||
|
<AppText style={styles.sectionTitle} variant="h3" weight="semibold">
|
||||||
|
Главный экран
|
||||||
|
</AppText>
|
||||||
|
|
||||||
|
<View style={styles.switchRow}>
|
||||||
|
<AppSwitch
|
||||||
|
label={SCANNER_SETTING_LABEL}
|
||||||
|
value={alwaysShowScanner}
|
||||||
|
onValueChange={handleToggleAlwaysShowScanner}
|
||||||
|
disabled={isLoading || !isDbReady}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<AppText style={styles.sectionTitle} variant="h3" weight="semibold">
|
<AppText style={styles.sectionTitle} variant="h3" weight="semibold">
|
||||||
Системные настройки
|
Системные настройки
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
Предрейсовые осмотры - мобильное приложение
|
|
||||||
Стили компонента заднего фона
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
const { StyleSheet } = require('react-native'); //StyleSheet React Native
|
|
||||||
const { APP_COLORS } = require('../../config/theme'); //Цветовая схема приложения
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Стили заднего фона
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
backdrop: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: APP_COLORS.overlay,
|
|
||||||
zIndex: 999
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
module.exports = styles;
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
Предрейсовые осмотры - мобильное приложение
|
|
||||||
Стили элемента списка предрейсовых осмотров
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
const { StyleSheet } = require('react-native'); //StyleSheet React Native
|
|
||||||
const { APP_COLORS } = require('../../config/theme'); //Цветовая схема приложения
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Стили элемента
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
paddingVertical: 12,
|
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
||||||
borderBottomColor: APP_COLORS.borderSubtle,
|
|
||||||
backgroundColor: APP_COLORS.surface
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '500',
|
|
||||||
marginBottom: 4
|
|
||||||
},
|
|
||||||
metaRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between'
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: APP_COLORS.textSecondary
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
module.exports = styles;
|
|
||||||
48
rn/app/src/styles/scanner/BarcodeScanner.styles.js
Normal file
48
rn/app/src/styles/scanner/BarcodeScanner.styles.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Стили компонента сканера штрихкодов
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const { StyleSheet } = require('react-native'); //StyleSheet React Native
|
||||||
|
const { APP_COLORS } = require('../../config/theme'); //Цветовая схема приложения
|
||||||
|
const { UI } = require('../../config/appConfig'); //Конфигурация UI
|
||||||
|
const { responsiveSpacing } = require('../../utils/responsive'); //Адаптивные утилиты
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Стили сканера
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
borderRadius: UI.BORDER_RADIUS,
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundColor: APP_COLORS.black,
|
||||||
|
minHeight: responsiveSpacing(30)
|
||||||
|
},
|
||||||
|
camera: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
fallbackContainer: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: APP_COLORS.surfaceAlt,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: responsiveSpacing(4)
|
||||||
|
},
|
||||||
|
fallbackText: {
|
||||||
|
textAlign: 'center',
|
||||||
|
color: APP_COLORS.textSecondary
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = styles;
|
||||||
106
rn/app/src/styles/scanner/ScanResultModal.styles.js
Normal file
106
rn/app/src/styles/scanner/ScanResultModal.styles.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Стили модального окна результата сканирования
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const { StyleSheet, Platform } = require('react-native'); //StyleSheet React Native
|
||||||
|
const { APP_COLORS } = require('../../config/theme'); //Цветовая схема приложения
|
||||||
|
const { UI } = require('../../config/appConfig'); //Конфигурация UI
|
||||||
|
const { responsiveSpacing, widthPercentage } = require('../../utils/responsive'); //Адаптивные утилиты
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Стили модального окна результата сканирования
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
backdrop: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: APP_COLORS.overlay,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: UI.PADDING
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: widthPercentage(90),
|
||||||
|
backgroundColor: APP_COLORS.surface,
|
||||||
|
borderRadius: UI.BORDER_RADIUS,
|
||||||
|
...Platform.select({
|
||||||
|
ios: {
|
||||||
|
shadowColor: APP_COLORS.black,
|
||||||
|
shadowOpacity: 0.25,
|
||||||
|
shadowRadius: 10,
|
||||||
|
shadowOffset: { width: 0, height: 4 }
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
elevation: 10
|
||||||
|
},
|
||||||
|
web: {
|
||||||
|
boxShadow: '0 4px 10px rgba(0, 0, 0, 0.25)'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingHorizontal: UI.PADDING,
|
||||||
|
paddingVertical: responsiveSpacing(3),
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: APP_COLORS.borderSubtle
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: UI.FONT_SIZE_LG,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: APP_COLORS.textPrimary,
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
width: responsiveSpacing(8),
|
||||||
|
height: responsiveSpacing(8),
|
||||||
|
borderRadius: responsiveSpacing(4),
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: APP_COLORS.surfaceAlt
|
||||||
|
},
|
||||||
|
closeButtonText: {
|
||||||
|
fontSize: responsiveSpacing(6),
|
||||||
|
color: APP_COLORS.textSecondary,
|
||||||
|
fontWeight: '300',
|
||||||
|
lineHeight: responsiveSpacing(8)
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
paddingHorizontal: UI.PADDING,
|
||||||
|
paddingVertical: responsiveSpacing(4)
|
||||||
|
},
|
||||||
|
typeLabel: {
|
||||||
|
marginBottom: responsiveSpacing(1),
|
||||||
|
color: APP_COLORS.textSecondary
|
||||||
|
},
|
||||||
|
valueBlock: {
|
||||||
|
paddingVertical: responsiveSpacing(2),
|
||||||
|
paddingHorizontal: responsiveSpacing(3),
|
||||||
|
backgroundColor: APP_COLORS.surfaceAlt,
|
||||||
|
borderRadius: UI.BORDER_RADIUS,
|
||||||
|
marginBottom: responsiveSpacing(4)
|
||||||
|
},
|
||||||
|
valueText: {
|
||||||
|
fontSize: UI.FONT_SIZE_MD,
|
||||||
|
color: APP_COLORS.textPrimary
|
||||||
|
},
|
||||||
|
buttonsRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = styles;
|
||||||
48
rn/app/src/styles/scanner/ScannerArea.styles.js
Normal file
48
rn/app/src/styles/scanner/ScannerArea.styles.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Стили области сканера на главном экране
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const { StyleSheet } = require('react-native'); //StyleSheet React Native
|
||||||
|
const { responsiveSpacing, heightPercentage } = require('../../utils/responsive'); //Адаптивные утилиты
|
||||||
|
const { APP_COLORS } = require('../../config/theme'); //Цветовая схема
|
||||||
|
const { UI } = require('../../config/appConfig'); //Конфигурация UI
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Стили области сканера (примерно треть высоты экрана)
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
height: heightPercentage(33),
|
||||||
|
minHeight: responsiveSpacing(30),
|
||||||
|
marginBottom: responsiveSpacing(2)
|
||||||
|
},
|
||||||
|
modalContainer: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
modalCloseButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: responsiveSpacing(4),
|
||||||
|
right: responsiveSpacing(4),
|
||||||
|
paddingHorizontal: responsiveSpacing(4),
|
||||||
|
paddingVertical: responsiveSpacing(2),
|
||||||
|
backgroundColor: APP_COLORS.overlay,
|
||||||
|
borderRadius: UI.BORDER_RADIUS
|
||||||
|
},
|
||||||
|
modalCloseButtonText: {
|
||||||
|
color: APP_COLORS.textInverse,
|
||||||
|
fontWeight: '600'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = styles;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Предрейсовые осмотры - мобильное приложение
|
Предрейсовые осмотры - мобильное приложение
|
||||||
Стили списка предрейсовых осмотров
|
Стили заглушки области сканера (кнопка «Сканировать»)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//---------------------
|
//---------------------
|
||||||
@ -9,35 +9,25 @@
|
|||||||
|
|
||||||
const { StyleSheet } = require('react-native'); //StyleSheet React Native
|
const { StyleSheet } = require('react-native'); //StyleSheet React Native
|
||||||
const { APP_COLORS } = require('../../config/theme'); //Цветовая схема приложения
|
const { APP_COLORS } = require('../../config/theme'); //Цветовая схема приложения
|
||||||
|
const { UI } = require('../../config/appConfig'); //Конфигурация UI
|
||||||
|
const { responsiveSpacing } = require('../../utils/responsive'); //Адаптивные утилиты
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Стили списка
|
//Стили заглушки сканера
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
centerContainer: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
backgroundColor: APP_COLORS.surfaceAlt,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingHorizontal: 32
|
borderRadius: UI.BORDER_RADIUS,
|
||||||
|
minHeight: responsiveSpacing(30)
|
||||||
},
|
},
|
||||||
centerText: {
|
button: {
|
||||||
marginTop: 12,
|
minWidth: responsiveSpacing(40)
|
||||||
textAlign: 'center',
|
|
||||||
color: APP_COLORS.textSecondary
|
|
||||||
},
|
|
||||||
errorText: {
|
|
||||||
marginTop: 8,
|
|
||||||
textAlign: 'center',
|
|
||||||
color: APP_COLORS.error,
|
|
||||||
fontSize: 12
|
|
||||||
},
|
|
||||||
centerButton: {
|
|
||||||
marginTop: 16
|
|
||||||
},
|
|
||||||
indicator: {
|
|
||||||
color: APP_COLORS.primary
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
const { StyleSheet } = require('react-native'); //StyleSheet React Native
|
const { StyleSheet } = require('react-native'); //StyleSheet React Native
|
||||||
const { APP_COLORS } = require('../../config/theme'); //Цветовая схема приложения
|
const { APP_COLORS } = require('../../config/theme'); //Цветовая схема приложения
|
||||||
const { UI } = require('../../config/appConfig'); //Конфигурация UI
|
|
||||||
const { responsiveSpacing } = require('../../utils/responsive'); //Адаптивные утилиты
|
const { responsiveSpacing } = require('../../utils/responsive'); //Адаптивные утилиты
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -25,19 +24,6 @@ const styles = StyleSheet.create({
|
|||||||
content: {
|
content: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingTop: responsiveSpacing(2)
|
paddingTop: responsiveSpacing(2)
|
||||||
},
|
|
||||||
emptyState: {
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
paddingHorizontal: UI.PADDING
|
|
||||||
},
|
|
||||||
emptyStateText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
marginBottom: responsiveSpacing(4)
|
|
||||||
},
|
|
||||||
emptyStateButton: {
|
|
||||||
minWidth: responsiveSpacing(40)
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,14 +3,20 @@
|
|||||||
Утилиты информации о приложении и режимах работы
|
Утилиты информации о приложении и режимах работы
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
const pkg = require('../../package.json'); //Версия и описание из package.json
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
//---------
|
//---------
|
||||||
|
|
||||||
//Версия приложения
|
//Версия приложения (из package.json)
|
||||||
const APP_VERSION = '1.0.0';
|
const APP_VERSION = pkg.version || '1.0.0';
|
||||||
|
|
||||||
//Название приложения
|
//Название приложения (отображаемое)
|
||||||
const APP_NAME = 'Парус© Предрейсовые осмотры';
|
const APP_NAME = 'Парус© Предрейсовые осмотры';
|
||||||
|
|
||||||
//Описание приложения
|
//Описание приложения
|
||||||
|
|||||||
57
rn/app/src/utils/authFormStore.js
Normal file
57
rn/app/src/utils/authFormStore.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Предрейсовые осмотры - мобильное приложение
|
||||||
|
Хранилище данных формы входа
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
const DEFAULT = {
|
||||||
|
serverUrl: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
savePassword: false,
|
||||||
|
showPassword: false
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
let stored = { ...DEFAULT };
|
||||||
|
|
||||||
|
//Флаг: после очистки не принимать следующую запись (cleanup при размонтировании не должен перезаписать очищенный store)
|
||||||
|
let skipNextSet = false;
|
||||||
|
|
||||||
|
//Получить копию сохранённых данных формы
|
||||||
|
function getAuthFormStore() {
|
||||||
|
return { ...DEFAULT, ...stored };
|
||||||
|
}
|
||||||
|
|
||||||
|
//Сохранить данные формы (мержим с текущими)
|
||||||
|
function setAuthFormStore(data) {
|
||||||
|
if (skipNextSet) {
|
||||||
|
skipNextSet = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data != null && typeof data === 'object') {
|
||||||
|
stored = { ...stored, ...data };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Сбросить хранилище (например после успешного входа)
|
||||||
|
function clearAuthFormStore() {
|
||||||
|
stored = { ...DEFAULT };
|
||||||
|
skipNextSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAuthFormStore,
|
||||||
|
setAuthFormStore,
|
||||||
|
clearAuthFormStore
|
||||||
|
};
|
||||||
@ -8,9 +8,10 @@
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Определяет, нужно ли показывать поле ввода адреса сервера
|
//Определяет, нужно ли показывать поле ввода адреса сервера
|
||||||
function isServerUrlFieldVisible(hideServerUrl, serverUrl) {
|
//Поле показывается, если настройка «не отображать» выключена ИЛИ в настройках ещё не сохранён адрес
|
||||||
const hasServerUrl = Boolean(serverUrl && String(serverUrl).trim());
|
function isServerUrlFieldVisible(hideServerUrl, savedServerUrl) {
|
||||||
return !hideServerUrl || !hasServerUrl;
|
const hasSavedUrl = Boolean(savedServerUrl && String(savedServerUrl).trim());
|
||||||
|
return !hideServerUrl || !hasSavedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user