/
grigorin
/
InterviewHelper
Обзор
Документация
Войти
/
grigorin
/
InterviewHelper
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/store/interviewSlice.ts
147 строк
4 KB
Grigory Nevarov
Initial commit: CTO Interview Helper app with React, TypeScript, Redux
30 ноя 2025, 20:47
30 ноя 2025, 20:47
0680f45
Код
Авторство
О чём код?
import { type PayloadAction, createSlice } from "@reduxjs/toolkit"; export interface TopicResult { notes: string; score: number | null; } export interface InterviewState { selection: { group?: string; subcategory?: string; topic?: string; }; results: Record<string, Record<string, Record<string, TopicResult>>>; } const emptyState: InterviewState = { selection: {}, results: {}, }; const STORAGE_KEY = "interview-state"; const loadState = (): InterviewState => { if (typeof window === "undefined") { return emptyState; } try { const raw = window.localStorage.getItem(STORAGE_KEY); if (!raw) { return emptyState; } const parsed = JSON.parse(raw) as InterviewState; return { selection: parsed.selection ?? {}, results: parsed.results ?? {}, }; } catch (error) { console.warn("Failed to load persisted state", error); return emptyState; } }; const persistState = (state: InterviewState) => { if (typeof window === "undefined") { return; } try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch (error) { console.warn("Failed to persist state", error); } }; const initialState: InterviewState = typeof window !== "undefined" ? loadState() : emptyState; const ensurePath = ( state: InterviewState, group: string, subcategory: string, topic: string, ) => { if (!state.results[group]) { state.results[group] = {}; } if (!state.results[group][subcategory]) { state.results[group][subcategory] = {}; } if (!state.results[group][subcategory][topic]) { state.results[group][subcategory][topic] = { notes: "", score: null }; } }; export const interviewSlice = createSlice({ name: "interview", initialState, reducers: { selectGroup(state, action: PayloadAction<string>) { state.selection.group = action.payload; state.selection.subcategory = undefined; state.selection.topic = undefined; persistState(state); }, selectSubcategory(state, action: PayloadAction<string>) { state.selection.subcategory = action.payload; state.selection.topic = undefined; persistState(state); }, selectTopic(state, action: PayloadAction<string>) { state.selection.topic = action.payload; persistState(state); }, updateNotes( state, action: PayloadAction<{ group: string; subcategory: string; topic: string; notes: string; }>, ) { const { group, subcategory, topic, notes } = action.payload; ensurePath(state, group, subcategory, topic); state.results[group][subcategory][topic].notes = notes; persistState(state); }, updateScore( state, action: PayloadAction<{ group: string; subcategory: string; topic: string; score: number | null; }>, ) { const { group, subcategory, topic, score } = action.payload; ensurePath(state, group, subcategory, topic); state.results[group][subcategory][topic].score = score; persistState(state); }, resetState() { if (typeof window !== "undefined") { window.localStorage.removeItem(STORAGE_KEY); } return emptyState; }, hydrate(state, action: PayloadAction<InterviewState>) { state.selection = action.payload.selection; state.results = action.payload.results; persistState(state); }, }, }); export const { selectGroup, selectSubcategory, selectTopic, updateNotes, updateScore, resetState, hydrate, } = interviewSlice.actions; export default interviewSlice.reducer;