mirror of
https://github.com/lukaszraczylo/git-velocity.git
synced 2026-06-06 22:49:27 +00:00
Improve mobile responsiveness, explain scoring, simple search.
This commit is contained in:
@@ -47,7 +47,7 @@ const getAlignClass = (align) => {
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
:class="[
|
||||
'px-6 py-4 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider',
|
||||
'px-3 sm:px-6 py-3 sm:py-4 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider',
|
||||
getAlignClass(col.align),
|
||||
col.headerClass
|
||||
]"
|
||||
@@ -66,7 +66,7 @@ const getAlignClass = (align) => {
|
||||
<td
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
:class="['px-6 py-4', getAlignClass(col.align), col.class]"
|
||||
:class="['px-3 sm:px-6 py-3 sm:py-4', getAlignClass(col.align), col.class]"
|
||||
>
|
||||
<slot :name="col.key" :item="item" :index="index">
|
||||
{{ item[col.key] }}
|
||||
|
||||
@@ -34,6 +34,12 @@ const repositories = computed(() => globalData.value?.Repositories || [])
|
||||
>
|
||||
Leaderboard
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
to="/how-scoring-works"
|
||||
:class="route.path === '/how-scoring-works' ? 'nav-link-active' : 'nav-link'"
|
||||
>
|
||||
How Scoring Works
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-for="repo in repositories"
|
||||
:key="`${repo.Owner}/${repo.Name}`"
|
||||
@@ -58,30 +64,57 @@ const repositories = computed(() => globalData.value?.Repositories || [])
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
<div v-if="mobileMenuOpen" class="md:hidden py-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="flex flex-col space-y-3">
|
||||
<div v-if="mobileMenuOpen" class="md:hidden py-2 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="flex flex-col space-y-1">
|
||||
<RouterLink
|
||||
to="/"
|
||||
@click="mobileMenuOpen = false"
|
||||
:class="route.path === '/' ? 'nav-link-active' : 'nav-link'"
|
||||
:class="[
|
||||
'block px-4 py-3 rounded-lg text-base font-medium transition-colors',
|
||||
route.path === '/'
|
||||
? 'bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
]"
|
||||
>
|
||||
Dashboard
|
||||
<i class="fas fa-home mr-3 w-5 text-center"></i>Dashboard
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
to="/leaderboard"
|
||||
@click="mobileMenuOpen = false"
|
||||
:class="route.path === '/leaderboard' ? 'nav-link-active' : 'nav-link'"
|
||||
:class="[
|
||||
'block px-4 py-3 rounded-lg text-base font-medium transition-colors',
|
||||
route.path === '/leaderboard'
|
||||
? 'bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
]"
|
||||
>
|
||||
Leaderboard
|
||||
<i class="fas fa-trophy mr-3 w-5 text-center"></i>Leaderboard
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
to="/how-scoring-works"
|
||||
@click="mobileMenuOpen = false"
|
||||
:class="[
|
||||
'block px-4 py-3 rounded-lg text-base font-medium transition-colors',
|
||||
route.path === '/how-scoring-works'
|
||||
? 'bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
]"
|
||||
>
|
||||
<i class="fas fa-calculator mr-3 w-5 text-center"></i>How Scoring Works
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-for="repo in repositories"
|
||||
:key="`${repo.Owner}/${repo.Name}`"
|
||||
:to="`/repos/${repo.Owner}/${repo.Name}`"
|
||||
@click="mobileMenuOpen = false"
|
||||
:class="route.path.includes(`/repos/${repo.Owner}/${repo.Name}`) ? 'nav-link-active' : 'nav-link'"
|
||||
:class="[
|
||||
'block px-4 py-3 rounded-lg text-base font-medium transition-colors',
|
||||
route.path.includes(`/repos/${repo.Owner}/${repo.Name}`)
|
||||
? 'bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
]"
|
||||
>
|
||||
{{ repo.Name }}
|
||||
<i class="fas fa-code-branch mr-3 w-5 text-center"></i>{{ repo.Name }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,13 +14,13 @@ defineProps({
|
||||
<template>
|
||||
<div class="card animate-fade-in-up" :style="{ animationDelay: delay }">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-3xl font-bold" :class="valueClass">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-xl sm:text-2xl md:text-3xl font-bold truncate" :class="valueClass">
|
||||
{{ typeof value === 'number' ? formatNumber(value) : value }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ label }}</div>
|
||||
<div class="text-xs sm:text-sm text-gray-500 dark:text-gray-400 mt-1 truncate">{{ label }}</div>
|
||||
</div>
|
||||
<div v-if="icon" class="text-3xl opacity-50" :class="iconColor">
|
||||
<div v-if="icon" class="text-2xl sm:text-3xl opacity-50 ml-2 flex-shrink-0" :class="iconColor">
|
||||
<i :class="icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { Chart, registerables } from 'chart.js'
|
||||
|
||||
Chart.register(...registerables)
|
||||
@@ -49,7 +49,9 @@ const chartData = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const chartOptions = {
|
||||
const isMobile = ref(window.innerWidth < 640)
|
||||
|
||||
const chartOptions = computed(() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
@@ -61,20 +63,21 @@ const chartOptions = {
|
||||
position: 'top',
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
padding: 20,
|
||||
padding: isMobile.value ? 10 : 20,
|
||||
boxWidth: isMobile.value ? 8 : 12,
|
||||
font: {
|
||||
size: 12
|
||||
size: isMobile.value ? 10 : 12
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
padding: 12,
|
||||
padding: isMobile.value ? 8 : 12,
|
||||
titleFont: {
|
||||
size: 14
|
||||
size: isMobile.value ? 12 : 14
|
||||
},
|
||||
bodyFont: {
|
||||
size: 13
|
||||
size: isMobile.value ? 11 : 13
|
||||
},
|
||||
callbacks: {
|
||||
label: (context) => {
|
||||
@@ -90,8 +93,11 @@ const chartOptions = {
|
||||
},
|
||||
ticks: {
|
||||
font: {
|
||||
size: 11
|
||||
}
|
||||
size: isMobile.value ? 9 : 11
|
||||
},
|
||||
maxRotation: isMobile.value ? 45 : 0,
|
||||
autoSkip: true,
|
||||
maxTicksLimit: isMobile.value ? 6 : 12
|
||||
}
|
||||
},
|
||||
y: {
|
||||
@@ -101,7 +107,7 @@ const chartOptions = {
|
||||
},
|
||||
ticks: {
|
||||
font: {
|
||||
size: 11
|
||||
size: isMobile.value ? 9 : 11
|
||||
},
|
||||
callback: (value) => {
|
||||
if (value >= 1000) {
|
||||
@@ -112,7 +118,7 @@ const chartOptions = {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
function createChart() {
|
||||
if (!chartRef.value || !chartData.value.labels.length) return
|
||||
@@ -125,7 +131,7 @@ function createChart() {
|
||||
chartInstance = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: chartData.value,
|
||||
options: chartOptions
|
||||
options: chartOptions.value
|
||||
})
|
||||
}
|
||||
|
||||
@@ -138,8 +144,24 @@ function updateChart() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleResize() {
|
||||
const newIsMobile = window.innerWidth < 640
|
||||
if (newIsMobile !== isMobile.value) {
|
||||
isMobile.value = newIsMobile
|
||||
createChart() // Recreate chart with new options
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
createChart()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.timeline, () => {
|
||||
|
||||
Reference in New Issue
Block a user