fixed mobx
This commit is contained in:
parent
0cec8be4cd
commit
c77dd23dd1
@ -1,11 +1,8 @@
|
||||
import { Theme } from '@radix-ui/themes';
|
||||
import '@radix-ui/themes/styles.css';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
import queryClient from './api/queryClient';
|
||||
import './App.css';
|
||||
import MyRoutes from './routes/routes';
|
||||
import React from 'react';
|
||||
import { appStore } from './stores/AppStore';
|
||||
import { Ctx } from './context';
|
||||
|
||||
|
||||
@ -9,6 +9,6 @@ export const axiosBase = axios.create({
|
||||
export const axiosAuth = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}` // Maybe we will use cookies
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
@ -1,11 +0,0 @@
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: 2,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default queryClient;
|
||||
@ -9,7 +9,6 @@ import AddUserToProjectDialog from '../dialogs/AddUserToProjectDialog/AddUserToP
|
||||
import CreateTaskDialog from '../dialogs/CreateTaskDialog/CreateTaskDialog';
|
||||
import DeleteProjectDialog from '../dialogs/DeleteProjectDialog/CreateTaskDialog';
|
||||
|
||||
// Импортируем наш mobx-store
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
|
||||
type TCardGroup = {
|
||||
@ -19,18 +18,15 @@ type TCardGroup = {
|
||||
|
||||
|
||||
function CardGroup(props: TCardGroup) {
|
||||
// При монтировании (и при изменении `props.id`) грузим задачи для проекта
|
||||
const store = useStore();
|
||||
useEffect(() => {
|
||||
store.mainStore.fetchTasksForGroup(props.id).catch(console.error);
|
||||
}, [props.id, store.mainStore]);
|
||||
|
||||
|
||||
// Получаем задачи для данного проекта из стора (или пустой массив, если их нет)
|
||||
const tasks = store.mainStore.tasksByProject.get(props.id) ?? [];
|
||||
const isLoading = store.mainStore.isLoading; // общее isLoading из стора
|
||||
const isLoading = store.mainStore.isLoading;
|
||||
|
||||
// Создать задачу
|
||||
const createTask = (taskText: string, date: string) => {
|
||||
store.mainStore.createTask({
|
||||
title: taskText,
|
||||
@ -40,7 +36,6 @@ function CardGroup(props: TCardGroup) {
|
||||
});
|
||||
};
|
||||
|
||||
// Удалить проект (аналог deleteProjectMutation)
|
||||
const deleteGroup = () => {
|
||||
store.mainStore.deleteProject(props.id);
|
||||
};
|
||||
|
||||
@ -7,22 +7,17 @@ import CardGroup from '../CardGroup/CardGroup';
|
||||
import CreateProjectDialog from '../dialogs/CreateProjectDialog/CreateProjectDialog';
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
|
||||
type TDragResult = any; // или определите, если надо
|
||||
|
||||
function MainBoard() {
|
||||
const store = useStore();
|
||||
|
||||
// Загрузка проектов при монтировании
|
||||
useEffect(() => {
|
||||
store.mainStore.fetchProjects().catch(console.error);
|
||||
}, []);
|
||||
|
||||
const dragEndHandle = (result: TDragResult) => {
|
||||
// Логика перетаскивания, если нужно
|
||||
console.log(result);
|
||||
};
|
||||
|
||||
// Для кнопки "Create project"
|
||||
const createProject = (newProjectData: any) => {
|
||||
store.mainStore.createProject(newProjectData);
|
||||
};
|
||||
|
||||
@ -2,7 +2,6 @@ import { Draggable } from '@hello-pangea/dnd';
|
||||
import { Badge, Button, Card, Flex, Text } from '@radix-ui/themes';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type TTaskCard = {
|
||||
title?: string;
|
||||
@ -36,10 +35,6 @@ const TaskCard = observer((props: TTaskCard) => {
|
||||
// @ts-ignore
|
||||
console.log(`rerendered with`, store.mainStore.tasksByProject.get(2) );
|
||||
|
||||
useEffect(() => {
|
||||
console.log(store.mainStore.fakeData)
|
||||
}, [store.mainStore.fakeData])
|
||||
|
||||
|
||||
return (
|
||||
<Draggable draggableId={props.id || '123'} index={props.index || 0}>
|
||||
@ -50,7 +45,6 @@ const TaskCard = observer((props: TTaskCard) => {
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
<h1>{store.mainStore.fakeData.toString()}</h1>
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex justify={'between'}>
|
||||
<Text wrap="pretty">{props.title}</Text>
|
||||
@ -83,11 +77,6 @@ const TaskCard = observer((props: TTaskCard) => {
|
||||
Completed
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={() => {
|
||||
store.mainStore.fetchPosts()
|
||||
}}>
|
||||
posts
|
||||
</Button>
|
||||
</Flex>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@ -2,7 +2,6 @@ import { PropsWithChildren, useEffect, useState } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Button, Dialog, Flex, Select } from '@radix-ui/themes';
|
||||
|
||||
// Импортируем сторы
|
||||
import { useStore } from '../../../hooks/useStore';
|
||||
|
||||
type TAddUserToProjectDialog = {
|
||||
@ -15,8 +14,7 @@ function AddUserToProjectDialog(props: TAddUserToProjectDialog) {
|
||||
|
||||
const store = useStore();
|
||||
|
||||
// При открытии диалога (или при ререндере) можно обновлять список пользователей
|
||||
// (В RTK Query был refetch. Тут можно просто вызвать fetchAllUsers(force=true) если надо всегда свежий список)
|
||||
|
||||
useEffect(() => {
|
||||
store.authStore.fetchAllUsers();
|
||||
}, []);
|
||||
@ -24,7 +22,6 @@ function AddUserToProjectDialog(props: TAddUserToProjectDialog) {
|
||||
const handleAddUser = async () => {
|
||||
if (selectedUserId) {
|
||||
try {
|
||||
// В store.mainStore метод addProjectMember(projectId: string, memberId: string/number)
|
||||
await store.mainStore.addProjectMember(projectId.toString(), selectedUserId.toString());
|
||||
setSelectedUserId(null);
|
||||
} catch (err) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { makeAutoObservable, runInAction } from 'mobx';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
import { MainStore } from './MainStore';
|
||||
import { AuthStore } from './AuthStore';
|
||||
|
||||
|
||||
@ -2,18 +2,13 @@ import { makeAutoObservable, runInAction } from 'mobx';
|
||||
import axios from 'axios';
|
||||
import { AppStore } from './AppStore';
|
||||
|
||||
/**
|
||||
* AuthStore
|
||||
* Хранит логику для авторизации, регистрации и получения всех пользователей.
|
||||
*/
|
||||
|
||||
export class AuthStore {
|
||||
appStore: AppStore
|
||||
|
||||
// Можно также хранить здесь данные о текущем пользователе, ошибках и т.д.
|
||||
|
||||
isLoading = false;
|
||||
error: string | null = null;
|
||||
|
||||
// Список всех пользователей (если вам нужно его кэшировать)
|
||||
allUsers: any[] = [];
|
||||
|
||||
constructor(appStore:any) {
|
||||
@ -25,9 +20,6 @@ export class AuthStore {
|
||||
return localStorage.getItem('token') || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Логин: отправляет запрос на сервер и сохраняет токен в localStorage
|
||||
*/
|
||||
async login(credentials: { username: string; password: string }) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
@ -40,7 +32,6 @@ export class AuthStore {
|
||||
runInAction(() => {
|
||||
if (response.data.access_token) {
|
||||
localStorage.setItem('token', response.data.access_token);
|
||||
// Если нужно перенаправить после логина:
|
||||
window.location.href = '/';
|
||||
}
|
||||
this.isLoading = false;
|
||||
@ -54,9 +45,6 @@ export class AuthStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрация: отправляет запрос на сервер
|
||||
*/
|
||||
async register(credentials: { username: string; password: string }) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
@ -67,7 +55,6 @@ export class AuthStore {
|
||||
);
|
||||
|
||||
runInAction(() => {
|
||||
// Если регистрация успешна, например, статус 201 – перенаправляем на /login
|
||||
if (response.status === 201) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
@ -82,11 +69,7 @@ export class AuthStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить всех пользователей
|
||||
*/
|
||||
async fetchAllUsers(force = false) {
|
||||
// Если уже есть пользователи и не запрошен force, просто возвращаем
|
||||
if (this.allUsers.length && !force) {
|
||||
return this.allUsers;
|
||||
}
|
||||
|
||||
@ -2,40 +2,18 @@ import { makeAutoObservable, runInAction } from 'mobx';
|
||||
import axios from 'axios';
|
||||
import { AppStore } from './AppStore';
|
||||
|
||||
export type Posts = Post[]
|
||||
|
||||
export interface Post {
|
||||
userId: number
|
||||
id: number
|
||||
title: string
|
||||
body: string
|
||||
}
|
||||
|
||||
/**
|
||||
* MainStore
|
||||
* Хранит логику для задач, проектов и участников проектов
|
||||
*/
|
||||
export class MainStore {
|
||||
appStore: AppStore
|
||||
|
||||
// ----- Общие поля -----
|
||||
isLoading = false;
|
||||
error: string | null = null;
|
||||
|
||||
// ----- Задачи -----
|
||||
|
||||
tasks: any[] = [];
|
||||
// Кэшированный список задач по ID проекта
|
||||
tasksByProject = new Map<string, any[]>();
|
||||
|
||||
// ----- Проекты -----
|
||||
projects: any[] = [];
|
||||
// Детальные данные по конкретному проекту (если нужно кэшировать)
|
||||
projectById = new Map<string, any>();
|
||||
|
||||
fakeData: Posts = []
|
||||
|
||||
// ----- Участники проекта -----
|
||||
// Если нужно кэшировать участников по проекту
|
||||
projectMembersById = new Map<string, any[]>();
|
||||
|
||||
constructor(appStore:any) {
|
||||
@ -43,75 +21,18 @@ export class MainStore {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить токен из localStorage
|
||||
*/
|
||||
|
||||
get token() {
|
||||
return localStorage.getItem('token') || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Заготовка для заголовков (проставляем Bearer токен)
|
||||
*/
|
||||
|
||||
get headers() {
|
||||
return {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* ЗАДАЧИ
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Получить все задачи (кэширование: если уже загружены, второй раз не грузим)
|
||||
*/
|
||||
|
||||
async fetchPosts(){
|
||||
fetch('https://jsonplaceholder.typicode.com/posts?limit=10')
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
|
||||
this.fakeData = json
|
||||
console.log(this.fakeData);
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async fetchTasks(force = false) {
|
||||
|
||||
console.error(`Aboba`);
|
||||
|
||||
if (this.tasks.length && !force) {
|
||||
return this.tasks;
|
||||
}
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
const response = await axios.get('http://109.107.166.17:5000/api/tasks', {
|
||||
headers: this.headers,
|
||||
});
|
||||
runInAction(() => {
|
||||
console.log(`This is all tasks`,response.data);
|
||||
|
||||
this.tasks = response.data;
|
||||
this.isLoading = false;
|
||||
});
|
||||
return this.tasks;
|
||||
} catch (error: any) {
|
||||
runInAction(() => {
|
||||
this.error = error?.response?.data?.message || 'Ошибка при загрузке задач';
|
||||
this.isLoading = false;
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить задачи для конкретного проекта (группы)
|
||||
*/
|
||||
async fetchTasksForGroup(projectId: string, force = false) {
|
||||
if (this.tasksByProject.has(projectId) && !force) {
|
||||
return this.tasksByProject.get(projectId);
|
||||
@ -129,8 +50,6 @@ export class MainStore {
|
||||
},
|
||||
);
|
||||
runInAction(() => {
|
||||
console.log(`This is tasks for ${projectId}`,response.data);
|
||||
|
||||
this.tasksByProject.set(projectId, response.data);
|
||||
this.isLoading = false;
|
||||
});
|
||||
@ -144,9 +63,6 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить одну задачу по ID
|
||||
*/
|
||||
async fetchTask(taskId: string) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
@ -169,18 +85,17 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать задачу
|
||||
*/
|
||||
async createTask(newTask: any) {
|
||||
try {
|
||||
await axios.post(
|
||||
const response = await axios.post(
|
||||
'http://109.107.166.17:5000/api/tasks/create',
|
||||
newTask,
|
||||
{ headers: this.headers },
|
||||
);
|
||||
// Инвалидируем кэш (чтобы при следующем getTasks() данные были актуальны)
|
||||
this.tasks = [];
|
||||
const createdTask = response.data;
|
||||
this.tasks.push(createdTask);
|
||||
const oldTasks = this.tasksByProject.get(newTask.projectId) ?? [];
|
||||
this.tasksByProject.set(newTask.projectId, [...oldTasks, createdTask]);
|
||||
} catch (error: any) {
|
||||
runInAction(() => {
|
||||
this.error = error?.response?.data?.message || 'Ошибка при создании задачи';
|
||||
@ -189,22 +104,25 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить задачу
|
||||
*/
|
||||
async updateTask(task: { id: string; status: string }) {
|
||||
try {
|
||||
await axios.patch(
|
||||
const response = await axios.patch(
|
||||
`http://109.107.166.17:5000/api/tasks/${task.id}`,
|
||||
{ status: task.status },
|
||||
{ headers: this.headers },
|
||||
);
|
||||
// Локально обновим статус в массиве tasks
|
||||
runInAction(() => {
|
||||
const index = this.tasks.findIndex((t) => t.id === task.id);
|
||||
if (index !== -1) {
|
||||
this.tasks[index].status = task.status;
|
||||
}
|
||||
const projectId = response.data.project.id;
|
||||
const oldArray = this.tasksByProject.get(projectId) || [];
|
||||
const indexInMap = oldArray.findIndex((t) => t.id === +task.id);
|
||||
if (indexInMap !== -1) {
|
||||
oldArray[indexInMap].status = task.status;
|
||||
this.tasksByProject.set(projectId, [...oldArray]);
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
runInAction(() => {
|
||||
@ -214,9 +132,7 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить задачу
|
||||
*/
|
||||
|
||||
async deleteTask(taskId: string) {
|
||||
try {
|
||||
await axios.delete(`http://109.107.166.17:5000/api/tasks/${taskId}`, {
|
||||
@ -234,15 +150,7 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* ПРОЕКТЫ
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Получить все проекты текущего пользователя
|
||||
*/
|
||||
async fetchProjects(force = false) {
|
||||
if (this.projects.length && !force) {
|
||||
return this.projects;
|
||||
@ -269,11 +177,7 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить данные одного проекта по ID
|
||||
*/
|
||||
async fetchProject(projectId: string) {
|
||||
// Если хотим кэшировать отдельно проекты по id
|
||||
if (this.projectById.has(projectId)) {
|
||||
return this.projectById.get(projectId);
|
||||
}
|
||||
@ -299,18 +203,17 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать проект
|
||||
*/
|
||||
async createProject(newProject: any) {
|
||||
try {
|
||||
await axios.post(
|
||||
const response = await axios.post(
|
||||
'http://109.107.166.17:5000/api/projects/create',
|
||||
newProject,
|
||||
{ headers: this.headers },
|
||||
);
|
||||
// Инвалидируем кэш
|
||||
this.projects = [];
|
||||
const created = response.data;
|
||||
runInAction(() => {
|
||||
this.projects.push(created);
|
||||
});
|
||||
} catch (error: any) {
|
||||
runInAction(() => {
|
||||
this.error = error?.response?.data?.message || 'Ошибка при создании проекта';
|
||||
@ -319,9 +222,6 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить проект
|
||||
*/
|
||||
async updateProject(id: string, projectData: any) {
|
||||
try {
|
||||
await axios.patch(
|
||||
@ -329,7 +229,6 @@ export class MainStore {
|
||||
projectData,
|
||||
{ headers: this.headers },
|
||||
);
|
||||
// Можно либо перезагрузить список проектов, либо локально обновить
|
||||
this.fetchProjects(true);
|
||||
} catch (error: any) {
|
||||
runInAction(() => {
|
||||
@ -339,9 +238,6 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить проект
|
||||
*/
|
||||
async deleteProject(id: string) {
|
||||
try {
|
||||
await axios.delete(`http://109.107.166.17:5000/api/projects/${id}`, {
|
||||
@ -359,15 +255,6 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* УЧАСТНИКИ ПРОЕКТА
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Добавить участника в проект
|
||||
*/
|
||||
async addProjectMember(projectId: string, memberId: string) {
|
||||
try {
|
||||
await axios.post(
|
||||
@ -378,7 +265,6 @@ export class MainStore {
|
||||
},
|
||||
{ headers: this.headers },
|
||||
);
|
||||
// Инвалидируем список участников
|
||||
this.projectMembersById.delete(projectId);
|
||||
} catch (error: any) {
|
||||
runInAction(() => {
|
||||
@ -387,10 +273,6 @@ export class MainStore {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить участников проекта
|
||||
*/
|
||||
async fetchProjectMembers(projectId: string, force = false) {
|
||||
if (this.projectMembersById.has(projectId) && !force) {
|
||||
return this.projectMembersById.get(projectId);
|
||||
@ -418,9 +300,6 @@ export class MainStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить участника из проекта
|
||||
*/
|
||||
async removeProjectMember(projectId: string, memberId: string) {
|
||||
try {
|
||||
await axios.delete(
|
||||
@ -430,7 +309,7 @@ export class MainStore {
|
||||
headers: this.headers,
|
||||
},
|
||||
);
|
||||
// Инвалидируем список участников
|
||||
|
||||
this.projectMembersById.delete(projectId);
|
||||
} catch (error: any) {
|
||||
runInAction(() => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user