Добавил асинхронные функции и типыдля для дэшборда, slice, обновил store
This commit is contained in:
@@ -1,10 +1,18 @@
|
|||||||
import { configureStore } from "@reduxjs/toolkit";
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
import authReducer from "../features/auth/authSlice";
|
import authReducer from "../features/auth/authSlice";
|
||||||
|
import dashboardReducer from '../features/dashboard/dashboardSlice'
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
auth: authReducer,
|
auth: authReducer,
|
||||||
|
dashboard: dashboardReducer,
|
||||||
},
|
},
|
||||||
|
middleware: (getDefaultMiddleware) =>
|
||||||
|
getDefaultMiddleware({
|
||||||
|
serializableCheck: {
|
||||||
|
ignoredActions: ['persist/PERSIST'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RootState = ReturnType<typeof store.getState>;
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { createSlice} from '@reduxjs/toolkit';
|
||||||
|
import type { DashboardState, Robot, ScanRecord, Statistics, AIPrediction } from '../../model/types/dashboardTypes';
|
||||||
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import { fetchDashboardData } from './dashboardThunks';
|
||||||
|
import { fetchAIPredictions } from './dashboardThunks';
|
||||||
|
|
||||||
|
const initialState: DashboardState = {
|
||||||
|
robots: [],
|
||||||
|
recentScans: [],
|
||||||
|
statistics: null,
|
||||||
|
aiPredictions: [],
|
||||||
|
isWebSocketConnected: false,
|
||||||
|
isScanAutoUpdate: true,
|
||||||
|
isLoading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const dashboardSlice = createSlice({
|
||||||
|
name: 'dashboard',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setWebSocketConnection: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isWebSocketConnected = action.payload;
|
||||||
|
},
|
||||||
|
updateRobotData: (state, action: PayloadAction<Robot>) => {
|
||||||
|
const index = state.robots.findIndex(r => r.id === action.payload.id);
|
||||||
|
if (index >= 0) {
|
||||||
|
state.robots[index] = action.payload;
|
||||||
|
} else {
|
||||||
|
state.robots.push(action.payload);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addScanRecord: (state, action: PayloadAction<ScanRecord>) => {
|
||||||
|
if (state.isScanAutoUpdate) {
|
||||||
|
state.recentScans.unshift(action.payload);
|
||||||
|
// Ограничиваем количество записей
|
||||||
|
if (state.recentScans.length > 20) {
|
||||||
|
state.recentScans = state.recentScans.slice(0, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleScanAutoUpdate: (state) => {
|
||||||
|
state.isScanAutoUpdate = !state.isScanAutoUpdate;
|
||||||
|
},
|
||||||
|
updateStatistics: (state, action: PayloadAction<Statistics>) => {
|
||||||
|
state.statistics = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder
|
||||||
|
.addCase(fetchDashboardData.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
})
|
||||||
|
.addCase(fetchDashboardData.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.robots = action.payload.robots;
|
||||||
|
state.recentScans = action.payload.recent_scans;
|
||||||
|
state.statistics = action.payload.statistics;
|
||||||
|
})
|
||||||
|
.addCase(fetchDashboardData.rejected, (state) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
})
|
||||||
|
.addCase(fetchAIPredictions.fulfilled, (state, action) => {
|
||||||
|
state.aiPredictions = action.payload.predictions;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
setWebSocketConnection,
|
||||||
|
updateRobotData,
|
||||||
|
addScanRecord,
|
||||||
|
toggleScanAutoUpdate,
|
||||||
|
updateStatistics,
|
||||||
|
} = dashboardSlice.actions;
|
||||||
|
|
||||||
|
export default dashboardSlice.reducer;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
// Получение текущего состояния дашборда
|
||||||
|
export const fetchDashboardData = createAsyncThunk(
|
||||||
|
'dashboard/fetchData',
|
||||||
|
async (_, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/dashboard/current', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('token')}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка загрузки данных');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error: any) {
|
||||||
|
return rejectWithValue(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Получение прогнозов от ИИ
|
||||||
|
export const fetchAIPredictions = createAsyncThunk(
|
||||||
|
'dashboard/fetchPredictions',
|
||||||
|
async (_, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/ai/predict', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('token')}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
period_days: 7,
|
||||||
|
categories: [],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка получения прогнозов');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error: any) {
|
||||||
|
return rejectWithValue(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
export interface Robot {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
batteryLevel: number;
|
||||||
|
status: 'active' | 'low_battery' | 'offline';
|
||||||
|
location: string;
|
||||||
|
lastUpdate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScanRecord {
|
||||||
|
id: string;
|
||||||
|
timestamp: string;
|
||||||
|
robotId: string;
|
||||||
|
zone: string;
|
||||||
|
productName: string;
|
||||||
|
productSku: string;
|
||||||
|
quantity: number;
|
||||||
|
status: 'ok' | 'low_stock' | 'critical';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Statistics {
|
||||||
|
activeRobots: number;
|
||||||
|
totalRobots: number;
|
||||||
|
scannedToday: number;
|
||||||
|
criticalItems: number;
|
||||||
|
averageBattery: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AIPrediction {
|
||||||
|
id: string;
|
||||||
|
productName: string;
|
||||||
|
currentStock: number;
|
||||||
|
predictedDepletionDate: string;
|
||||||
|
recommendedOrder: number;
|
||||||
|
confidence: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardState {
|
||||||
|
robots: Robot[];
|
||||||
|
recentScans: ScanRecord[];
|
||||||
|
statistics: Statistics | null;
|
||||||
|
aiPredictions: AIPrediction[];
|
||||||
|
isWebSocketConnected: boolean;
|
||||||
|
isScanAutoUpdate: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user