добавил методы для конкурсов

This commit is contained in:
Мельников Данил 2025-06-01 23:25:12 +05:00
parent 00d17363f2
commit 203f6763d6
23 changed files with 624 additions and 41 deletions

View File

@ -0,0 +1,16 @@
from typing import Optional
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.domain.models import Contest
class ContestStatusesRepository:
def __init__(self, db: AsyncSession):
self.db = db
async def get_by_id(self, contest_status_id: int) -> Optional[Contest]:
stmt = select(Contest).filter_by(id=contest_status_id)
result = await self.db.execute(stmt)
return result.scalars().first()

View File

@ -0,0 +1,37 @@
from typing import Optional
from sqlalchemy import select, Sequence
from sqlalchemy.ext.asyncio import AsyncSession
from app.domain.models import Contest
class ContestsRepository:
def __init__(self, db: AsyncSession):
self.db = db
async def get_all(self) -> Sequence[Contest]:
stmt = select(Contest)
result = await self.db.execute(stmt)
return result.scalars().all()
async def get_by_id(self, contest_id: int) -> Optional[Contest]:
stmt = select(Contest).filter_by(id=contest_id)
result = await self.db.execute(stmt)
return result.scalars().first()
async def create(self, contest: Contest) -> Contest:
self.db.add(contest)
await self.db.commit()
await self.db.refresh(contest)
return contest
async def update(self, contest: Contest) -> Contest:
await self.db.merge(contest)
await self.db.commit()
return contest
async def delete(self, contest: Contest) -> Contest:
await self.db.delete(contest)
await self.db.commit()
return contest

View File

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from sqlalchemy import select from sqlalchemy import select, Sequence
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.domain.models import Profile from app.domain.models import Profile
@ -10,6 +10,11 @@ class ProfilesRepository:
def __init__(self, db: AsyncSession): def __init__(self, db: AsyncSession):
self.db = db self.db = db
async def get_all(self) -> Sequence[Profile]:
stmt = select(Profile)
result = await self.db.execute(stmt)
return result.scalars().all()
async def get_by_id(self, profile_id: int) -> Optional[Profile]: async def get_by_id(self, profile_id: int) -> Optional[Profile]:
stmt = select(Profile).filter_by(id=profile_id) stmt = select(Profile).filter_by(id=profile_id)
result = await self.db.execute(stmt) result = await self.db.execute(stmt)

View File

@ -0,0 +1,88 @@
from typing import Optional
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.session import get_db
from app.domain.entities.contest import ContestEntity
from app.infrastructure.contests_service import ContestsService
from app.infrastructure.dependencies import require_admin, get_current_user
router = APIRouter()
@router.get(
'/',
response_model=list[ContestEntity],
summary='Get all contests',
description='Returns all contests',
)
async def get_all_contests(
db: AsyncSession = Depends(get_db),
):
contests_service = ContestsService(db)
return await contests_service.get_all_contests()
@router.post(
'/',
response_model=Optional[ContestEntity],
summary='Create a new contest',
description='Creates a new contest',
)
async def create_contest(
contest: ContestEntity,
db: AsyncSession = Depends(get_db),
user=Depends(require_admin),
):
contests_service = ContestsService(db)
return await contests_service.create_contest(contest)
@router.put(
'/{contest_id}/',
response_model=Optional[ContestEntity],
summary='Update a contest',
description='Updates a contest',
)
async def update_contest(
contest_id: int,
contest: ContestEntity,
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
):
contests_service = ContestsService(db)
return await contests_service.update_contest(contest_id, contest, user)
@router.delete(
'/{contest_id}/',
response_model=Optional[ContestEntity],
summary='Delete a contest',
description='Delete a contest',
)
async def delete_contest(
contest_id: int,
db: AsyncSession = Depends(get_db),
user=Depends(require_admin),
):
contests_service = ContestsService(db)
return await contests_service.delete(contest_id, user)
@router.get(
'/project/{project_id}/',
response_model=Optional[ContestEntity],
summary='Get project by contest ID',
description='Retrieve project data by contest ID',
)
async def get_project_by_contest_id(
project_id: int,
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
):
contests_service = ContestsService(db)
contest = await contests_service.get_by_project(project_id)
return contest

View File

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.database.session import get_db from app.database.session import get_db
@ -10,6 +10,18 @@ from app.infrastructure.profiles_service import ProfilesService
router = APIRouter() router = APIRouter()
@router.get(
'/',
response_model=list[ProfileEntity],
summary='Get all profiles',
description='Returns all profiles',
)
async def get_all_profiles(
db: AsyncSession = Depends(get_db),
):
profiles_service = ProfilesService(db)
return await profiles_service.get_all_profiles()
@router.post( @router.post(
'/', '/',

View File

@ -0,0 +1,13 @@
from typing import Optional
from pydantic import BaseModel
class ContestEntity(BaseModel):
id: int
title: str
description: Optional[str] = None
web_url: str
photo: Optional[str] = None
results: Optional[str] = None
is_win: Optional[bool] = None
project_id: int
status_id: int

View File

@ -0,0 +1,158 @@
from typing import Optional
from fastapi import HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.application.contest_statuses_repository import ContestStatusesRepository
from app.application.contests_repository import ContestsRepository
from app.application.projects_repository import ProjectsRepository
from app.application.users_repository import UsersRepository
from app.domain.entities.contest import ContestEntity
from app.domain.models import Contest, User
class ContestsService:
def __init__(self, db: AsyncSession):
self.contests_repository = ContestsRepository(db)
self.projects_repository = ProjectsRepository(db)
self.statuses_repository = ContestStatusesRepository(db)
self.users_repository = UsersRepository(db)
async def get_all_contests(self) -> list[ContestEntity]:
contests = await self.contests_repository.get_all()
return [
self.model_to_entity(contest)
for contest in contests
]
async def create_contest(self, contest: ContestEntity) -> Optional[ContestEntity]:
project = await self.projects_repository.get_by_id(contest.project_id)
if project is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='The project with this ID was not found',
)
status_contest = await self.statuses_repository.get_by_id(contest.status_id)
if status_contest is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='The status with this ID was not found',
)
contest_model = self.entity_to_model(contest)
contest_model = await self.contests_repository.create(contest_model)
return self.model_to_entity(contest_model)
async def update_contest(self, contest_id: int, contest: ContestEntity, user: User) -> Optional[
ContestEntity
]:
user = await self.users_repository.get_by_id(user.id)
if user is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='The user with this ID was not found',
)
elif user.profile.role.title != 'Администратор':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Permission denied",
)
contest_model = await self.contests_repository.get_by_id(contest_id)
if contest_model is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='The contest with this ID was not found',
)
project = await self.projects_repository.get_by_id(contest.project_id)
if project is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='The project with this ID was not found',
)
status_contest = await self.statuses_repository.get_by_id(contest.role_id)
if status_contest is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='The status with this ID was not found',
)
contest_model.title = contest.title
contest_model.description = contest.description
contest_model.web_url = contest.web_url
contest_model.photo = contest.photo
contest_model.results = contest.results
contest_model.is_win = contest.is_win
contest_model.project_id = contest.project_id
contest_model.status_id = contest.status_id
contest_model = await self.contests_repository.update(contest_model)
return self.model_to_entity(contest_model)
async def delete(self, contest_id: int, user: User):
user = await self.users_repository.get_by_id(user.id)
if user is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='The user with this ID was not found',
)
contest_model = await self.contests_repository.get_by_id(contest_id)
if user.profile.role.title != 'Администратор':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail='Permission denied',
)
result = await self.contests_repository.delete(contest_model)
return self.model_to_entity(result)
async def get_by_project(self, project_id: int) -> Optional[ContestEntity]:
project = await self.projects_repository.get_by_id(project_id)
if project is None:
raise HTTPException(status_code=404, detail='Project not found')
contest_model = await self.contests_repository.get_by_id(project.contest_id)
if not contest_model:
raise HTTPException(status_code=404, detail='Contest not found')
return self.model_to_entity(contest_model)
@staticmethod
def model_to_entity(contest_model: Contest) -> ContestEntity:
return ContestEntity(
id=contest_model.id,
title=contest_model.title,
description=contest_model.description,
web_url=contest_model.web_url,
photo=contest_model.photo,
results=contest_model.results,
is_win=contest_model.is_win,
project_id=contest_model.project_id,
status_id=contest_model.status_id,
)
@staticmethod
def entity_to_model(contest_entity: ContestEntity) -> Contest:
contest_model = Contest(
title=contest_entity.title,
description=contest_entity.description,
web_url=contest_entity.web_url,
photo=contest_entity.photo,
results=contest_entity.results,
is_win=contest_entity.is_win,
project_id=contest_entity.project_id,
status_id=contest_entity.status_id
)
if contest_entity.id is not None:
contest_model.id = contest_entity.id
return contest_model

View File

@ -18,6 +18,13 @@ class ProfilesService:
self.roles_repository = RolesRepository(db) self.roles_repository = RolesRepository(db)
self.users_repository = UsersRepository(db) self.users_repository = UsersRepository(db)
async def get_all_profiles(self) -> list[ProfileEntity]:
profiles = await self.profiles_repository.get_all()
return [
self.model_to_entity(profile)
for profile in profiles
]
async def create_profile(self, profile: ProfileEntity) -> Optional[ProfileEntity]: async def create_profile(self, profile: ProfileEntity) -> Optional[ProfileEntity]:
team = await self.teams_repository.get_by_id(profile.team_id) team = await self.teams_repository.get_by_id(profile.team_id)
if team is None: if team is None:

View File

@ -11,6 +11,7 @@ from app.contollers.register_router import router as register_router
from app.contollers.rss_router import router as rss_router from app.contollers.rss_router import router as rss_router
from app.contollers.teams_router import router as team_router from app.contollers.teams_router import router as team_router
from app.contollers.users_router import router as users_router from app.contollers.users_router import router as users_router
from app.contollers.contests_router import router as contest_router
from app.settings import settings from app.settings import settings
@ -35,6 +36,7 @@ def start_app():
api_app.include_router(rss_router, prefix=f'{settings.PREFIX}/rss', tags=['rss_router']) api_app.include_router(rss_router, prefix=f'{settings.PREFIX}/rss', tags=['rss_router'])
api_app.include_router(team_router, prefix=f'{settings.PREFIX}/teams', tags=['teams']) api_app.include_router(team_router, prefix=f'{settings.PREFIX}/teams', tags=['teams'])
api_app.include_router(users_router, prefix=f'{settings.PREFIX}/users', tags=['users']) api_app.include_router(users_router, prefix=f'{settings.PREFIX}/users', tags=['users'])
api_app.include_router(contest_router,prefix=f'{settings.PREFIX}/contests', tags=['contests'])
return api_app return api_app

View File

@ -0,0 +1,24 @@
import axios from 'axios'
import CONFIG from '@/core/config.js'
const createProfile = async (profile) => {
console.log(profile)
try {
const token = localStorage.getItem('access_token') // или другой способ получения токена
const response = await axios.post(
`${CONFIG.BASE_URL}/profiles`,
profile,
{
withCredentials: true,
headers: {
Authorization: `Bearer ${token}`
}
}
)
return response.data
} catch (error) {
throw new Error(error.response?.data?.detail || error.message)
}
}
export default createProfile

View File

@ -0,0 +1,22 @@
import axios from 'axios'
import CONFIG from '@/core/config.js'
const deleteProfile = async (profileId) => {
try {
const token = localStorage.getItem('access_token') // получение токена
const response = await axios.delete(
`${CONFIG.BASE_URL}/profiles/${profileId}`,
{
withCredentials: true,
headers: {
Authorization: `Bearer ${token}`
}
}
)
return response.data
} catch (error) {
throw new Error(error.response?.data?.detail || error.message)
}
}
export default deleteProfile

View File

@ -0,0 +1,25 @@
import axios from 'axios'
import CONFIG from '../../core/config.js'
const fetchProfile = async () => {
try {
const token = localStorage.getItem("access_token");
const response = await axios.get(`${CONFIG.BASE_URL}/profiles`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
} catch (error) {
if (error.response?.status === 401) {
throw new Error("Нет доступа к пользователям");
} else if (error.response?.status === 403) {
throw new Error("Доступ запрещён");
}
throw new Error(error.message);
}
};
export default fetchProfile;

View File

@ -0,0 +1,31 @@
import axios from 'axios'
import CONFIG from '@/core/config.js'
const updateProfile = async (profile) => {
try {
const token = localStorage.getItem('access_token')
// Убираем id из тела запроса, он идет в URL
const { id, ...profileData } = profile
console.log('Отправляем на сервер:', profileData)
const response = await axios.put(
`${CONFIG.BASE_URL}/profiles/${id}`,
profileData,
{
withCredentials: true,
headers: {
Authorization: `Bearer ${token}`
}
}
)
console.log('Ответ от сервера:', response.data)
return response.data
} catch (error) {
throw new Error(error.response?.data?.detail || error.message)
}
}
export default updateProfile

View File

@ -0,0 +1,24 @@
import axios from 'axios'
import CONFIG from '@/core/config.js'
const createProfile = async (profile) => {
console.log(profile)
try {
const token = localStorage.getItem('access_token') // или другой способ получения токена
const response = await axios.post(
`${CONFIG.BASE_URL}/profiles`,
profile,
{
withCredentials: true,
headers: {
Authorization: `Bearer ${token}`
}
}
)
return response.data
} catch (error) {
throw new Error(error.response?.data?.detail || error.message)
}
}
export default createProfile

View File

@ -0,0 +1,22 @@
import axios from 'axios'
import CONFIG from '@/core/config.js'
const deleteProfile = async (profileId) => {
try {
const token = localStorage.getItem('access_token') // получение токена
const response = await axios.delete(
`${CONFIG.BASE_URL}/profiles/${profileId}`,
{
withCredentials: true,
headers: {
Authorization: `Bearer ${token}`
}
}
)
return response.data
} catch (error) {
throw new Error(error.response?.data?.detail || error.message)
}
}
export default deleteProfile

View File

@ -0,0 +1,25 @@
import axios from 'axios'
import CONFIG from '../../core/config.js'
const fetchProfile = async () => {
try {
const token = localStorage.getItem("access_token");
const response = await axios.get(`${CONFIG.BASE_URL}/profiles`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
} catch (error) {
if (error.response?.status === 401) {
throw new Error("Нет доступа к пользователям");
} else if (error.response?.status === 403) {
throw new Error("Доступ запрещён");
}
throw new Error(error.message);
}
};
export default fetchProfile;

View File

@ -1,17 +0,0 @@
import axios from 'axios'
import CONFIG from '../../core/config.js'
const getUserProfile = async (user_id, token) => {
try {
const response = await axios.get(`${CONFIG.BASE_URL}/profiles/${user_id}/`, {
headers: {
Authorization: `Bearer ${token}`
}
})
return response.data
} catch (error) {
throw new Error(error.response?.data?.detail || 'Ошибка получения профиля')
}
}
export default getUserProfile

View File

@ -0,0 +1,31 @@
import axios from 'axios'
import CONFIG from '@/core/config.js'
const updateProfile = async (profile) => {
try {
const token = localStorage.getItem('access_token')
// Убираем id из тела запроса, он идет в URL
const { id, ...profileData } = profile
console.log('Отправляем на сервер:', profileData)
const response = await axios.put(
`${CONFIG.BASE_URL}/profiles/${id}`,
profileData,
{
withCredentials: true,
headers: {
Authorization: `Bearer ${token}`
}
}
)
console.log('Ответ от сервера:', response.data)
return response.data
} catch (error) {
throw new Error(error.response?.data?.detail || error.message)
}
}
export default updateProfile

View File

@ -2,6 +2,7 @@ import axios from 'axios'
import CONFIG from '@/core/config.js' import CONFIG from '@/core/config.js'
const createProject = async (project) => { const createProject = async (project) => {
console.log(project)
try { try {
const token = localStorage.getItem('access_token') // или другой способ получения токена const token = localStorage.getItem('access_token') // или другой способ получения токена
const response = await axios.post( const response = await axios.post(

View File

@ -8,7 +8,6 @@ const updateProject = async (project) => {
// Убираем id из тела запроса, он идет в URL // Убираем id из тела запроса, он идет в URL
const { id, ...projectData } = project const { id, ...projectData } = project
console.log('Отправляем на сервер:', projectData)
const response = await axios.put( const response = await axios.put(
`${CONFIG.BASE_URL}/projects/${id}`, `${CONFIG.BASE_URL}/projects/${id}`,
@ -21,7 +20,6 @@ const updateProject = async (project) => {
} }
) )
console.log('Ответ от сервера:', response.data)
return response.data return response.data
} catch (error) { } catch (error) {
throw new Error(error.response?.data?.detail || error.message) throw new Error(error.response?.data?.detail || error.message)

View File

@ -8,7 +8,6 @@ const updateTeam = async (team) => {
// Убираем id из тела запроса, он идет в URL // Убираем id из тела запроса, он идет в URL
const { id, ...teamData } = team const { id, ...teamData } = team
console.log('Отправляем на сервер:', teamData)
const response = await axios.put( const response = await axios.put(
`${CONFIG.BASE_URL}/teams/${id}`, `${CONFIG.BASE_URL}/teams/${id}`,
@ -21,7 +20,6 @@ const updateTeam = async (team) => {
} }
) )
console.log('Ответ от сервера:', response.data)
return response.data return response.data
} catch (error) { } catch (error) {
throw new Error(error.response?.data?.detail || error.message) throw new Error(error.response?.data?.detail || error.message)

View File

@ -11,7 +11,7 @@
animated animated
@update:model-value="loadData" @update:model-value="loadData"
> >
<q-tab name="users" label="Пользователи" /> <q-tab name="profiles" label="Пользователи" />
<q-tab name="teams" label="Команды" /> <q-tab name="teams" label="Команды" />
<q-tab name="projects" label="Проекты" /> <q-tab name="projects" label="Проекты" />
<q-tab name="contests" label="Конкурсы" /> <q-tab name="contests" label="Конкурсы" />
@ -22,7 +22,7 @@
<q-tab-panels v-model="tab" animated transition-prev="slide-right" transition-next="slide-left"> <q-tab-panels v-model="tab" animated transition-prev="slide-right" transition-next="slide-left">
<!-- Пользователи --> <!-- Пользователи -->
<q-tab-panel name="users"> <q-tab-panel name="profiles">
<div class="violet-card q-pa-md"> <div class="violet-card q-pa-md">
<div class="q-gutter-sm q-mb-sm row items-center justify-between"> <div class="q-gutter-sm q-mb-sm row items-center justify-between">
<q-btn <q-btn
@ -33,11 +33,11 @@
</div> </div>
<q-table <q-table
title="Пользователи" title="Пользователи"
:rows="users" :rows="profiles"
:columns="userColumns" :columns="profileColumns"
row-key="id" row-key="id"
@row-click="onRowClick" @row-click="onRowClick"
:loading="loadingUsers" :loading="loadingProfiles"
dense dense
flat flat
/> />
@ -125,7 +125,7 @@
<template v-if="dialogType === 'teams'"> <template v-if="dialogType === 'teams'">
Редактирование команды {{ dialogData.title || '' }} Редактирование команды {{ dialogData.title || '' }}
</template> </template>
<template v-else-if="dialogType === 'users'"> <template v-else-if="dialogType === 'profiles'">
Редактирование пользователя {{ dialogData.name || dialogData.login || '' }} Редактирование пользователя {{ dialogData.name || dialogData.login || '' }}
</template> </template>
<template v-else-if="dialogType === 'projects'"> <template v-else-if="dialogType === 'projects'">
@ -151,19 +151,23 @@
<q-input v-model="dialogData.git_url" label="Git URL" dense clearable class="q-mt-sm" /> <q-input v-model="dialogData.git_url" label="Git URL" dense clearable class="q-mt-sm" />
</template> </template>
<!-- Users --> <!-- Profiles -->
<template v-else-if="dialogType === 'users'"> <template v-else-if="dialogType === 'profiles'">
<q-input v-model="dialogData.login" label="Логин" dense autofocus clearable /> <q-input v-model="dialogData.first_name" label="Имя" dense autofocus clearable />
<q-input v-model="dialogData.name" label="Имя" dense clearable class="q-mt-sm" /> <q-input v-model="dialogData.last_name" label="Фамилия" dense clearable class="q-mt-sm" />
<q-input v-model="dialogData.email" label="Email" dense clearable class="q-mt-sm" /> <q-input v-model="dialogData.patronymic" label="Отчество" dense clearable class="q-mt-sm" />
<!-- Добавь другие поля, которые нужны для пользователя --> <q-input v-model="dialogData.birthday" label="День рождения" dense clearable class="q-mt-sm" type="date" />
<q-input v-model="dialogData.email" label="Почта" dense clearable class="q-mt-sm" />
<q-input v-model="dialogData.phone" label="Телефон" dense clearable class="q-mt-sm" />
<q-input v-model="dialogData.role_id" label="Роль" dense clearable class="q-mt-sm" />
<q-input v-model="dialogData.team_id" label="Команда" dense clearable class="q-mt-sm" />
</template> </template>
<!-- Projects --> <!-- Projects -->
<template v-else-if="dialogType === 'projects'"> <template v-else-if="dialogType === 'projects'">
<q-input v-model="dialogData.title" label="Название проекта" dense autofocus clearable /> <q-input v-model="dialogData.title" label="Название проекта" dense autofocus clearable />
<q-input v-model="dialogData.description" label="Описание" dense clearable type="textarea" class="q-mt-sm" /> <q-input v-model="dialogData.description" label="Описание" dense clearable type="textarea" class="q-mt-sm" />
<q-input v-model="dialogData.repo_url" label="URL репозитория" dense clearable class="q-mt-sm" /> <q-input v-model="dialogData.repository_url" label="URL репозитория" dense clearable class="q-mt-sm" />
<!-- Другие поля проекта --> <!-- Другие поля проекта -->
</template> </template>
@ -190,7 +194,7 @@
@click="deleteItem" @click="deleteItem"
/> />
<q-btn <q-btn
v-if="dialogType === 'users'" v-if="dialogType === 'profiles'"
flat flat
label="Удалить" label="Удалить"
color="negative" color="negative"
@ -213,7 +217,7 @@
<q-space /> <q-space />
<q-btn flat label="Закрыть" color="primary" @click="closeDialog" /> <q-btn flat label="Закрыть" color="primary" @click="closeDialog" />
<q-btn <q-btn
v-if="['teams', 'users', 'projects', 'contests'].includes(dialogType)" v-if="['teams', 'profiles', 'projects', 'contests'].includes(dialogType)"
flat flat
label="Сохранить" label="Сохранить"
color="primary" color="primary"
@ -239,8 +243,32 @@ import updateProject from '@/api/projects/updateProject.js'
import deleteProjectById from '@/api/projects/deleteProject.js' import deleteProjectById from '@/api/projects/deleteProject.js'
import createProject from '@/api/projects/createProject.js' import createProject from '@/api/projects/createProject.js'
import fetchProfiles from '@/api/profiles/getProfiles.js'
import updateProfile from '@/api/profiles/updateProfile.js'
import deleteProfileById from '@/api/profiles/deleteProfile.js'
import createProfile from '@/api/profiles/createProfile.js'
// import fetchContests from '@/api/contests/getContests.js'
// import updateContest from '@/api/contests/updateContest.js'
// import deleteContestById from '@/api/contests/deleteContest.js'
// import createContest from '@/api/contests/createContest.js'
// Текущая вкладка 'teams' или 'projects' // Текущая вкладка 'teams' или 'projects'
const tab = ref('teams') const tab = ref('profiles')
// --- Profiles ---
const profiles = ref([])
const loadingProfiles = ref(false)
const profileColumns = [
{ name: 'first_name', label: 'Имя', field: 'first_name', sortable: true },
{ name: 'last_name', label: 'Фамилия', field: 'last_name', sortable: true },
{ name: 'patronymic', label: 'Отчество', field: 'patronymic', sortable: true },
{ name: 'birthday', label: 'День рождения', field: 'birthday', sortable: true },
{ name: 'email', label: 'Почта', field: 'email', sortable: true },
{ name: 'phone', label: 'Телефон', field: 'phone', sortable: true },
{ name: 'role_id', label: 'Роль', field: 'role_id', sortable: true },
{ name: 'team_id', label: 'Команда', field: 'team_id', sortable: true },
]
// --- Teams --- // --- Teams ---
const teams = ref([]) const teams = ref([])
@ -261,6 +289,17 @@ const projectColumns = [
{ name: 'repository_url', label: 'Репозиторий', field: 'repository_url', sortable: true }, { name: 'repository_url', label: 'Репозиторий', field: 'repository_url', sortable: true },
] ]
// --- Contests ---
const contests = ref([])
const loadingContests = ref(false)
const contestColumns = [
{ name: 'title', label: 'Название проекта', field: 'title', sortable: true },
{ name: 'description', label: 'Описание', field: 'description', sortable: true },
{ name: 'repository_url', label: 'Репозиторий', field: 'repository_url', sortable: true },
]
// Общие состояния для диалогов // Общие состояния для диалогов
const dialogVisible = ref(false) const dialogVisible = ref(false)
const dialogData = ref({}) const dialogData = ref({})
@ -308,6 +347,15 @@ async function saveChanges() {
const newProject = await createProject(dialogData.value) const newProject = await createProject(dialogData.value)
projects.value.push(newProject) projects.value.push(newProject)
} }
} else if (dialogType.value === 'profiles') {
if (dialogData.value.id) {
await updateProfile(dialogData.value)
const idx = profiles.value.findIndex(p => p.id === dialogData.value.id)
if (idx !== -1) profiles.value[idx] = JSON.parse(JSON.stringify(dialogData.value))
} else {
const newProfile = await createProfile(dialogData.value)
profiles.value.push(newProfile)
}
} }
closeDialog() closeDialog()
} catch (error) { } catch (error) {
@ -336,6 +384,16 @@ async function loadData(name) {
} finally { } finally {
loadingProjects.value = false loadingProjects.value = false
} }
} else if (name === 'profiles') {
loadingProfiles.value = true
try {
profiles.value = await fetchProfiles() || []
} catch (error) {
projects.value = []
console.error(error.message)
} finally {
loadingProfiles.value = false
}
} }
} }
@ -348,6 +406,9 @@ async function deleteItem() {
} else if (dialogType.value === 'projects') { } else if (dialogType.value === 'projects') {
await deleteProjectById(dialogData.value.id) await deleteProjectById(dialogData.value.id)
projects.value = projects.value.filter(p => p.id !== dialogData.value.id) projects.value = projects.value.filter(p => p.id !== dialogData.value.id)
} else if (dialogType.value === 'profiles') {
await deleteProfileById(dialogData.value.id)
profiles.value = profiles.value.filter(p => p.id !== dialogData.value.id)
} }
closeDialog() closeDialog()
} catch (error) { } catch (error) {

View File

@ -186,7 +186,7 @@ const contests = ref([
// --- Активность --- // --- Активность ---
const activityData = ref([]); const activityData = ref([]);
const dayHeight = 14; const dayHeight = 14;
const squareSize = ref(14); const squareSize = ref(12);
// Подписи месяцев (с июня 2024 по май 2025, чтобы соответствовать текущему году) // Подписи месяцев (с июня 2024 по май 2025, чтобы соответствовать текущему году)
const monthLabels = ['июн.', 'июл.', 'авг.', 'сент.', 'окт.', 'нояб.', 'дек.', 'янв.', 'февр.', 'март', 'апр.', 'май']; const monthLabels = ['июн.', 'июл.', 'авг.', 'сент.', 'окт.', 'нояб.', 'дек.', 'янв.', 'февр.', 'март', 'апр.', 'май'];
@ -241,7 +241,7 @@ function getMonthMargin(idx) {
} }
// Загрузка активности из API // Загрузка активности из API
const username = 'andrei'; const username = 'Numerum';
async function loadActivity() { async function loadActivity() {
try { try {