Compare commits
No commits in common. "00d17363f2ece6721ee487f64a21b531d8daf955" and "c812273f1b4a2ce869cec91944c4926022a7ac37" have entirely different histories.
00d17363f2
...
c812273f1b
@ -1,17 +0,0 @@
|
|||||||
from fastapi import APIRouter
|
|
||||||
|
|
||||||
from app.domain.entities.activity_item import ActivityItem
|
|
||||||
from app.infrastructure.rss_service import RSSService
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/{username}",
|
|
||||||
response_model=list[ActivityItem],
|
|
||||||
summary="Get the user's RSS activity",
|
|
||||||
description="Returns an array with the date and number of activities in the last 30 days"
|
|
||||||
)
|
|
||||||
def get_user_rss(username: str):
|
|
||||||
rss_service = RSSService()
|
|
||||||
return rss_service.get_rss_by_username(username)
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class ActivityItem(BaseModel):
|
|
||||||
date: str
|
|
||||||
count: int
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
|
||||||
import requests
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
|
|
||||||
class RSSService:
|
|
||||||
@staticmethod
|
|
||||||
def get_rss_by_username(username: str):
|
|
||||||
url = f"https://git.numerum.team/{username}.rss"
|
|
||||||
response = requests.get(url)
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise HTTPException(status_code=400, detail="Не удалось загрузить RSS")
|
|
||||||
|
|
||||||
root = ET.fromstring(response.text)
|
|
||||||
items = root.findall(".//item")
|
|
||||||
|
|
||||||
activity_map = {}
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
pub_date_elem = item.find("pubDate")
|
|
||||||
if pub_date_elem is not None:
|
|
||||||
pub_date_str = pub_date_elem.text.strip()
|
|
||||||
pub_date = datetime.strptime(pub_date_str, "%a, %d %b %Y %H:%M:%S %z")
|
|
||||||
date_str = pub_date.strftime("%Y-%m-%d")
|
|
||||||
activity_map[date_str] = activity_map.get(date_str, 0) + 1
|
|
||||||
|
|
||||||
today = datetime.now(pub_date.tzinfo)
|
|
||||||
activity_days = 365
|
|
||||||
result = []
|
|
||||||
|
|
||||||
for i in range(activity_days - 1, -1, -1):
|
|
||||||
day = today - timedelta(days=i)
|
|
||||||
date_str = day.strftime("%Y-%m-%d")
|
|
||||||
count = activity_map.get(date_str, 0)
|
|
||||||
result.append({"date": date_str, "count": count})
|
|
||||||
|
|
||||||
return result
|
|
||||||
@ -8,7 +8,6 @@ from app.contollers.project_files_router import router as project_files_router
|
|||||||
from app.contollers.project_members_router import router as project_members_router
|
from app.contollers.project_members_router import router as project_members_router
|
||||||
from app.contollers.projects_router import router as projects_router
|
from app.contollers.projects_router import router as projects_router
|
||||||
from app.contollers.register_router import router as register_router
|
from app.contollers.register_router import router as register_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.settings import settings
|
from app.settings import settings
|
||||||
@ -32,7 +31,6 @@ def start_app():
|
|||||||
api_app.include_router(project_members_router, prefix=f'{settings.PREFIX}/project_members', tags=['project_members'])
|
api_app.include_router(project_members_router, prefix=f'{settings.PREFIX}/project_members', tags=['project_members'])
|
||||||
api_app.include_router(projects_router, prefix=f'{settings.PREFIX}/projects', tags=['projects'])
|
api_app.include_router(projects_router, prefix=f'{settings.PREFIX}/projects', tags=['projects'])
|
||||||
api_app.include_router(register_router, prefix=f'{settings.PREFIX}/register', tags=['register'])
|
api_app.include_router(register_router, prefix=f'{settings.PREFIX}/register', tags=['register'])
|
||||||
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'])
|
||||||
|
|
||||||
|
|||||||
31
WEB/package-lock.json
generated
31
WEB/package-lock.json
generated
@ -12,8 +12,7 @@
|
|||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"quasar": "^2.18.1",
|
"quasar": "^2.18.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1"
|
||||||
"xml2js": "^0.6.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.2",
|
"@vitejs/plugin-vue": "^5.2.2",
|
||||||
@ -1850,12 +1849,6 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sax": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
|
||||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@ -2045,28 +2038,6 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.2.0"
|
"vue": "^3.2.0"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"node_modules/xml2js": {
|
|
||||||
"version": "0.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
|
||||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"sax": ">=0.6.0",
|
|
||||||
"xmlbuilder": "~11.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xmlbuilder": {
|
|
||||||
"version": "11.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
|
||||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,8 +13,7 @@
|
|||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"quasar": "^2.18.1",
|
"quasar": "^2.18.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1"
|
||||||
"xml2js": "^0.6.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.2",
|
"@vitejs/plugin-vue": "^5.2.2",
|
||||||
|
|||||||
@ -60,60 +60,43 @@
|
|||||||
<q-separator class="q-my-lg" color="indigo-4" style="width: 80%; margin: 0 auto;" />
|
<q-separator class="q-my-lg" color="indigo-4" style="width: 80%; margin: 0 auto;" />
|
||||||
<div class="q-mt-md"></div>
|
<div class="q-mt-md"></div>
|
||||||
|
|
||||||
<!-- Активность команды в стиле Gitea -->
|
<!-- Активность и история коммитов -->
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<q-card class="activity-card violet-card" style="max-width: 940px; width: 100%;">
|
<q-card class="activity-card violet-card q-mb-xl" style="max-width: 920px; width: 100%;">
|
||||||
<q-card-section class="q-pa-md">
|
<q-card-section class="q-pa-md">
|
||||||
<div class="text-h6 text-indigo-10 q-mb-md">Активность команды за последний год</div>
|
<div class="text-h6 text-indigo-10 q-mb-md">Активность команды и история коммитов</div>
|
||||||
|
|
||||||
<!-- Подписи месяцев -->
|
<div class="activity-container row no-wrap items-start q-mb-md">
|
||||||
<div class="months-row flex" style="margin-left: 40px; margin-bottom: 4px; user-select: none;">
|
<div class="dates-column column q-pr-md" style="min-width: 50px;">
|
||||||
<div
|
<div
|
||||||
v-for="(monthLabel, idx) in monthLabels"
|
v-for="item in activityData"
|
||||||
:key="monthLabel"
|
:key="item.date"
|
||||||
:style="{ marginLeft: getMonthMargin(idx) + 'px' }"
|
class="activity-date text-caption text-indigo-9"
|
||||||
class="month-label text-caption text-indigo-9"
|
style="height: 16px; line-height: 16px;"
|
||||||
>
|
>
|
||||||
{{ monthLabel }}
|
{{ item.date.slice(5) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="activity-grid-row row no-wrap">
|
<div class="activity-grid row wrap" style="flex: 1; gap: 4px;">
|
||||||
<!-- Дни недели слева -->
|
|
||||||
<div class="weekdays-column column q-pr-sm" style="width: 40px; user-select: none; justify-content: space-around;">
|
|
||||||
<div
|
<div
|
||||||
v-for="(day, idx) in weekDays"
|
v-for="item in activityData"
|
||||||
:key="day"
|
:key="item.date"
|
||||||
class="weekday-label text-caption text-indigo-9"
|
|
||||||
:style="{ height: dayHeight + 'px', lineHeight: dayHeight + 'px' }"
|
|
||||||
>
|
|
||||||
{{ day }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Сетка дней -->
|
|
||||||
<div class="activity-grid" :style="{ height: (dayHeight * 7) + 'px' }">
|
|
||||||
<div
|
|
||||||
v-for="week in activityGrid"
|
|
||||||
:key="week[0]?.date"
|
|
||||||
class="activity-week"
|
|
||||||
:style="{ width: (squareSize + 4) + 'px' }"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="day in week"
|
|
||||||
:key="day.date"
|
|
||||||
class="activity-square"
|
class="activity-square"
|
||||||
:title="`Дата: ${day.date}, активность: ${day.count}`"
|
:title="`Дата: ${item.date}, активность: ${item.count}`"
|
||||||
:style="{ backgroundColor: getActivityColor(day.count), width: squareSize + 'px', height: squareSize + 'px', margin: '2px 0' }"
|
:style="{ backgroundColor: getActivityColor(item.count) }"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Масштаб активности -->
|
<div class="commits-list q-pa-sm" style="max-height: 150px; overflow-y: auto;">
|
||||||
<div class="scale-labels row justify-end q-mt-sm text-caption text-indigo-9" style="user-select: none;">
|
<div
|
||||||
<span class="q-mr-md" style="cursor: pointer;" @click="decreaseScale">Меньше</span>
|
v-for="commit in commits"
|
||||||
<span style="cursor: pointer;" @click="increaseScale">Больше</span>
|
:key="commit.id"
|
||||||
|
class="commit-item text-caption text-indigo-9 q-mb-xs"
|
||||||
|
>
|
||||||
|
<strong>{{ commit.date }}:</strong> {{ commit.message }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
@ -133,12 +116,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {ref, computed, onMounted} from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useRouter} from 'vue-router'
|
import { useRouter} from 'vue-router'
|
||||||
import { Ripple, Notify } from 'quasar'
|
import { Ripple, Notify } from 'quasar'
|
||||||
import axios from "axios";
|
|
||||||
import {parseStringPromise} from 'xml2js'
|
|
||||||
import CONFIG from "@/core/config.js";
|
|
||||||
|
|
||||||
defineExpose({ directives: { ripple: Ripple } })
|
defineExpose({ directives: { ripple: Ripple } })
|
||||||
|
|
||||||
@ -184,134 +164,34 @@ const contests = ref([
|
|||||||
])
|
])
|
||||||
|
|
||||||
// --- Активность ---
|
// --- Активность ---
|
||||||
const activityData = ref([]);
|
const activityData = ref([])
|
||||||
const dayHeight = 14;
|
const today = new Date()
|
||||||
const squareSize = ref(14);
|
const activityDays = 30
|
||||||
|
function pad(num) { return num < 10 ? '0' + num : num }
|
||||||
|
|
||||||
// Подписи месяцев (с июня 2024 по май 2025, чтобы соответствовать текущему году)
|
for (let i = activityDays - 1; i >= 0; i--) {
|
||||||
const monthLabels = ['июн.', 'июл.', 'авг.', 'сент.', 'окт.', 'нояб.', 'дек.', 'янв.', 'февр.', 'март', 'апр.', 'май'];
|
const d = new Date(today)
|
||||||
|
d.setDate(d.getDate() - i)
|
||||||
// Дни недели (пн, ср, пт, как в Gitea)
|
const count = Math.floor(Math.random() * 6)
|
||||||
const weekDays = ['пн', 'ср', 'пт'];
|
activityData.value.push({ date: d.toISOString().slice(0, 10), count })
|
||||||
|
|
||||||
// Вычисляемая сетка активности (группировка по неделям)
|
|
||||||
const activityGrid = computed(() => {
|
|
||||||
const weeks = [];
|
|
||||||
let week = [];
|
|
||||||
const firstDay = new Date();
|
|
||||||
firstDay.setDate(firstDay.getDate() - 364); // Год назад от текущей даты
|
|
||||||
const dayOfWeek = firstDay.getDay();
|
|
||||||
const offset = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Смещение для выравнивания по понедельнику
|
|
||||||
|
|
||||||
// Добавляем пустые ячейки в начало
|
|
||||||
for (let i = 0; i < offset; i++) {
|
|
||||||
week.push({ date: '', count: 0 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < 365; i++) {
|
// --- Коммиты ---
|
||||||
const date = new Date(firstDay);
|
const commits = ref([
|
||||||
date.setDate(firstDay.getDate() + i);
|
{ id: 1, message: 'Добавлен график активности', date: '2025-05-28' },
|
||||||
const dateStr = date.toISOString().slice(0, 10);
|
{ id: 2, message: 'Исправлена верстка карточек', date: '2025-05-27' },
|
||||||
const dayData = activityData.value.find(d => d.date === dateStr) || { date: dateStr, count: 0 };
|
{ id: 3, message: 'Обновлены данные команды', date: '2025-05-26' },
|
||||||
|
{ id: 4, message: 'Оптимизирован компонент участников', date: '2025-05-25' },
|
||||||
|
])
|
||||||
|
|
||||||
week.push(dayData);
|
|
||||||
if (week.length === 7 || i === 364) {
|
|
||||||
weeks.push(week);
|
|
||||||
week = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return weeks;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Цвета активности (как в Gitea)
|
|
||||||
function getActivityColor(count) {
|
function getActivityColor(count) {
|
||||||
if (count === 0) return '#ede9fe'; // Светлый фон карточек
|
if (count === 0) return '#ddd'
|
||||||
if (count <= 2) return '#d8cff9'; // Светлый сиреневый
|
const opacity = 0.15 + (count / 5) * 0.6
|
||||||
if (count <= 4) return '#a287ff'; // Светлый фиолетовый
|
return `rgba(124, 58, 237, ${opacity.toFixed(2)})`
|
||||||
if (count <= 6) return '#7c3aed'; // Яркий фиолетовый
|
|
||||||
return '#4f046f'; // Темно-фиолетовый
|
|
||||||
}
|
|
||||||
|
|
||||||
// Позиционирование подписей месяцев
|
|
||||||
function getMonthMargin(idx) {
|
|
||||||
const daysInMonth = [30, 31, 31, 30, 31, 30, 31, 31, 28, 31, 30, 31]; // Дни в месяцах с июня 2024
|
|
||||||
const daysBeforeMonth = daysInMonth.slice(0, idx).reduce((sum, days) => sum + days, 0);
|
|
||||||
const weekIndex = Math.floor(daysBeforeMonth / 7);
|
|
||||||
return weekIndex * (squareSize.value + 4); // 4 = margin (2px + 2px)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загрузка активности из API
|
|
||||||
const username = 'andrei';
|
|
||||||
|
|
||||||
async function loadActivity() {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${CONFIG.BASE_URL}/rss/${username}/`);
|
|
||||||
const fetchedData = response.data.map(item => ({
|
|
||||||
date: item.date,
|
|
||||||
count: parseInt(item.count, 10) || 0
|
|
||||||
}));
|
|
||||||
const dataMap = new Map(fetchedData.map(d => [d.date, d.count]));
|
|
||||||
const lastDate = new Date();
|
|
||||||
const startDate = new Date(lastDate);
|
|
||||||
startDate.setDate(lastDate.getDate() - 364);
|
|
||||||
|
|
||||||
activityData.value = [];
|
|
||||||
for (let i = 0; i < 365; i++) {
|
|
||||||
const date = new Date(startDate);
|
|
||||||
date.setDate(startDate.getDate() + i);
|
|
||||||
const dateStr = date.toISOString().slice(0, 10);
|
|
||||||
const count = dataMap.get(dateStr) || 0;
|
|
||||||
activityData.value.push({date: dateStr, count});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка загрузки активности:', error);
|
|
||||||
Notify.create({
|
|
||||||
type: 'negative',
|
|
||||||
message: 'Ошибка загрузки данных активности',
|
|
||||||
icon: 'error',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Заполняем пустыми данными в случае ошибки
|
|
||||||
const lastDate = new Date();
|
|
||||||
const startDate = new Date(lastDate);
|
|
||||||
startDate.setDate(lastDate.getDate() - 364);
|
|
||||||
activityData.value = [];
|
|
||||||
for (let i = 0; i < 365; i++) {
|
|
||||||
const date = new Date(startDate);
|
|
||||||
date.setDate(startDate.getDate() + i);
|
|
||||||
const dateStr = date.toISOString().slice(0, 10);
|
|
||||||
activityData.value.push({date: dateStr, count: 0});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadActivity();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Масштабирование
|
|
||||||
function increaseScale() {
|
|
||||||
if (squareSize.value < 24) squareSize.value += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
function decreaseScale() {
|
|
||||||
if (squareSize.value > 8) squareSize.value -= 2;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.activity-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
.activity-card {
|
|
||||||
max-width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
.activity-card .activity-square {
|
|
||||||
border-radius: 4px !important;
|
|
||||||
}
|
|
||||||
.bg-violet-strong {
|
.bg-violet-strong {
|
||||||
background: linear-gradient(135deg, #4f046f 0%, #7c3aed 100%);
|
background: linear-gradient(135deg, #4f046f 0%, #7c3aed 100%);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@ -319,6 +199,7 @@ function decreaseScale() {
|
|||||||
color: #3e2465;
|
color: #3e2465;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.team-logo {
|
.team-logo {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@ -333,23 +214,33 @@ function decreaseScale() {
|
|||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
box-shadow: 0 0 14px #a287ffaa;
|
box-shadow: 0 0 14px #a287ffaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.team-name-card {
|
.team-name-card {
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background: #ede9fe;
|
background: #ede9fe;
|
||||||
box-shadow: 0 8px 24px rgba(124, 58, 237, 0.18), 0 2px 8px rgba(124, 58, 237, 0.12);
|
box-shadow:
|
||||||
|
0 8px 24px rgba(124, 58, 237, 0.18),
|
||||||
|
0 2px 8px rgba(124, 58, 237, 0.12);
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.violet-card {
|
.violet-card {
|
||||||
border-radius: 22px;
|
border-radius: 22px;
|
||||||
background: #ede9fe;
|
background: #ede9fe;
|
||||||
box-shadow: 0 8px 24px rgba(124, 58, 237, 0.18), 0 2px 8px rgba(124, 58, 237, 0.12);
|
box-shadow:
|
||||||
|
0 8px 24px rgba(124, 58, 237, 0.18),
|
||||||
|
0 2px 8px rgba(124, 58, 237, 0.12);
|
||||||
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.violet-card:hover {
|
.violet-card:hover {
|
||||||
box-shadow: 0 14px 40px rgba(124, 58, 237, 0.30), 0 6px 16px rgba(124, 58, 237, 0.20);
|
box-shadow:
|
||||||
|
0 14px 40px rgba(124, 58, 237, 0.30),
|
||||||
|
0 6px 16px rgba(124, 58, 237, 0.20);
|
||||||
transform: translateY(-6px);
|
transform: translateY(-6px);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-card, .contest-card {
|
.member-card, .contest-card {
|
||||||
min-height: 140px;
|
min-height: 140px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@ -357,38 +248,82 @@ function decreaseScale() {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.horizontal-scroll {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
.horizontal-scroll::-webkit-scrollbar {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
.horizontal-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(124, 58, 237, 0.4);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.activity-card {
|
.activity-card {
|
||||||
max-width: 920px;
|
max-width: 920px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
.activity-grid {
|
|
||||||
display: flex;
|
.activity-container {
|
||||||
flex-direction: row;
|
/* align-items: center; */
|
||||||
}
|
}
|
||||||
.activity-week {
|
|
||||||
|
.dates-column {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
color: #5e35b1;
|
||||||
|
font-weight: 600;
|
||||||
|
user-select: none;
|
||||||
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activity-date {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
user-select: none;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(124, 58, 237, 0.4) transparent;
|
||||||
|
}
|
||||||
|
.activity-grid::-webkit-scrollbar {
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
.activity-grid::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(124, 58, 237, 0.4);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.activity-square {
|
.activity-square {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
margin-right: 2px;
|
||||||
box-shadow: 0 0 3px rgba(124, 58, 237, 0.3);
|
box-shadow: 0 0 3px rgba(124, 58, 237, 0.3);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
.months-row {
|
|
||||||
display: flex;
|
.commits-list {
|
||||||
flex-direction: row;
|
background: #f5f3ff;
|
||||||
justify-content: flex-start;
|
border-radius: 12px;
|
||||||
margin-left: 40px; /* Синхронизация с .weekdays-column */
|
padding: 8px 12px;
|
||||||
margin-bottom: 24px !important;
|
font-family: 'Courier New', Courier, monospace;
|
||||||
position: relative;
|
color: #4b0082;
|
||||||
|
box-shadow: inset 0 0 6px #c7b3f7;
|
||||||
}
|
}
|
||||||
.month-label {
|
.commit-item strong {
|
||||||
width: auto;
|
color: #7c3aed;
|
||||||
text-align: left;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex-shrink: 0;
|
|
||||||
position: absolute; /* Для точного позиционирования */
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user