/
grigorin
/
InterviewHelper
Обзор
Документация
Войти
/
grigorin
/
InterviewHelper
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/App.tsx
152 строки
5 KB
Grigory Nevarov
Initial commit: CTO Interview Helper app with React, TypeScript, Redux
30 ноя 2025, 20:47
30 ноя 2025, 20:47
0680f45
Код
Авторство
О чём код?
import { useEffect, useMemo, useState } from "react"; import "./App.css"; import { CategorySelector } from "./components/CategorySelector"; import { SendResultsButton } from "./components/SendResultsButton"; import { SubcategorySelector } from "./components/SubcategorySelector"; import { TopicDetails } from "./components/TopicDetails"; import { useAppDispatch } from "./hooks/useAppDispatch"; import { useAppSelector } from "./hooks/useAppSelector"; import { selectGroup, selectSubcategory, selectTopic, resetState, updateNotes, updateScore, } from "./store/interviewSlice"; import type { KnowledgeData, Topic } from "./types/knowledge"; function App() { const [knowledge, setKnowledge] = useState<KnowledgeData | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); const dispatch = useAppDispatch(); const { selection, results } = useAppSelector((state) => state.interview); useEffect(() => { const fetchData = async () => { try { setLoading(true); const response = await fetch("/data/knowledge.json"); if (!response.ok) { throw new Error("Не удалось загрузить данные"); } const data: KnowledgeData = await response.json(); setKnowledge(data); } catch (err) { setError(err instanceof Error ? err.message : "Ошибка загрузки данных"); } finally { setLoading(false); } }; fetchData(); }, []); const groups = useMemo( () => (knowledge ? Object.keys(knowledge.knowledge) : []), [knowledge], ); const subcategories = useMemo(() => { if (!knowledge || !selection.group) { return []; } return Object.keys(knowledge.knowledge[selection.group] ?? {}); }, [knowledge, selection.group]); const topics: Topic[] = useMemo(() => { if (!knowledge || !selection.group || !selection.subcategory) { return []; } return ( knowledge.knowledge[selection.group]?.[selection.subcategory]?.["Темы"] ?? [] ); }, [knowledge, selection.group, selection.subcategory]); const currentResult = selection.group && selection.subcategory && selection.topic ? results[selection.group]?.[selection.subcategory]?.[selection.topic] : undefined; const notes = currentResult?.notes ?? ""; const score = currentResult?.score ?? null; const handleNotesChange = (value: string) => { if (!selection.group || !selection.subcategory || !selection.topic) { return; } dispatch( updateNotes({ group: selection.group, subcategory: selection.subcategory, topic: selection.topic, notes: value, }), ); }; const handleScoreChange = (value: number | null) => { if (!selection.group || !selection.subcategory || !selection.topic) { return; } dispatch( updateScore({ group: selection.group, subcategory: selection.subcategory, topic: selection.topic, score: value, }), ); }; return ( <div className="app-shell"> <header className="hero"> <h1>Помощник Интервьюера CTO</h1> <p className="hero__subtitle"> Проходите через hard и soft-компетенции, фиксируйте заметки и оценки, а затем отправляйте результаты кандидата в один клик. </p> </header> {loading && <p className="status">Загружаем матрицу знаний...</p>} {error && ( <p className="status status--error"> {error}. Проверьте файл /data/knowledge.json </p> )} {!loading && !error && knowledge && ( <> <CategorySelector categories={groups} selected={selection.group} onSelect={(value) => dispatch(selectGroup(value))} /> <SubcategorySelector subcategories={subcategories} disabled={!selection.group} selected={selection.subcategory} onSelect={(value) => dispatch(selectSubcategory(value))} /> <TopicDetails topics={topics} selectedTopic={selection.topic} notes={notes} score={score} onSelectTopic={(value) => dispatch(selectTopic(value))} onNotesChange={handleNotesChange} onScoreChange={handleScoreChange} /> <SendResultsButton results={results} onClear={() => dispatch(resetState())} /> </> )} </div> ); } export default App;