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