mirror of
https://github.com/lukaszraczylo/git-velocity.git
synced 2026-06-05 22:43:56 +00:00
Fix styling
This commit is contained in:
@@ -266,12 +266,12 @@ const remainingCount = computed(() => {
|
||||
<i class="fas text-white text-sm" :class="item.icon"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-gray-800 dark:text-white">
|
||||
<div class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<div class="flex items-center space-x-2 text-xs text-gray-600 dark:text-gray-400">
|
||||
<span>{{ item.category }}</span>
|
||||
<span class="text-gray-300 dark:text-gray-600">•</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">•</span>
|
||||
<span class="font-medium">Tier {{ item.tierIndex }}/{{ item.totalTiers }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -279,10 +279,10 @@ const remainingCount = computed(() => {
|
||||
<div class="text-right">
|
||||
<div class="text-sm font-bold" :class="item.isClose ? 'text-green-500' : 'text-gray-700 dark:text-gray-200'">
|
||||
{{ formatNumber(item.currentValue) }}
|
||||
<span class="text-gray-400 dark:text-gray-500 font-normal">/</span>
|
||||
<span class="text-gray-500 dark:text-gray-400 font-medium">{{ formatNumber(item.target) }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400 font-normal">/</span>
|
||||
<span class="text-gray-600 dark:text-gray-400 font-medium">{{ formatNumber(item.target) }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mt-0.5">
|
||||
{{ item.remaining > 0 ? `${formatNumber(item.remaining)} to go` : 'Ready to claim!' }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -307,11 +307,11 @@ const remainingCount = computed(() => {
|
||||
:class="idx < item.tierIndex ? 'bg-green-500' : 'bg-gray-300 dark:bg-gray-600'"
|
||||
:title="`Tier ${idx + 1}: ${t.name} (${formatNumber(t.threshold)})`"
|
||||
></span>
|
||||
<span v-if="item.totalTiers > 5" class="text-[10px] text-gray-400">+{{ item.totalTiers - 5 }}</span>
|
||||
<span v-if="item.totalTiers > 5" class="text-[10px] text-gray-600 dark:text-gray-400">+{{ item.totalTiers - 5 }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs font-semibold"
|
||||
:class="item.isClose ? 'text-green-500' : 'text-gray-400 dark:text-gray-500'"
|
||||
:class="item.isClose ? 'text-green-500' : 'text-gray-600 dark:text-gray-400'"
|
||||
>
|
||||
{{ item.progress }}%
|
||||
</span>
|
||||
@@ -319,12 +319,12 @@ const remainingCount = computed(() => {
|
||||
</div>
|
||||
|
||||
<!-- Show more indicator -->
|
||||
<div v-if="remainingCount > 0" class="text-center text-xs text-gray-500 dark:text-gray-400 pt-2">
|
||||
<div v-if="remainingCount > 0" class="text-center text-xs text-gray-600 dark:text-gray-400 pt-2">
|
||||
+{{ remainingCount }} more achievements to unlock
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-if="!progressItems.length" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
<div v-if="!progressItems.length" class="text-center py-8 text-gray-600 dark:text-gray-400">
|
||||
<div class="w-16 h-16 mx-auto mb-3 rounded-2xl bg-gradient-to-br from-yellow-400 to-amber-500 flex items-center justify-center shadow-lg">
|
||||
<i class="fas fa-trophy text-2xl text-white"></i>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||||
<div class="flex items-center space-x-2 text-sm text-gray-600 dark:text-gray-400 mb-6">
|
||||
<template v-for="(item, index) in items" :key="index">
|
||||
<RouterLink
|
||||
v-if="item.to"
|
||||
@@ -22,7 +22,7 @@ defineProps({
|
||||
</RouterLink>
|
||||
<span
|
||||
v-else
|
||||
:class="index === items.length - 1 ? 'text-gray-800 dark:text-white' : ''"
|
||||
:class="index === items.length - 1 ? 'text-gray-900 dark:text-white' : ''"
|
||||
>
|
||||
{{ item.label }}
|
||||
</span>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
padding: { type: Boolean, default: true },
|
||||
hover: { type: Boolean, default: false }
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'rounded-xl bg-white dark:bg-gray-800 text-gray-900 dark:text-white border border-gray-200 dark:border-gray-700 shadow',
|
||||
padding ? 'p-6' : '',
|
||||
hover ? 'hover:shadow-lg transition-shadow' : ''
|
||||
]"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router'
|
||||
import Card from './Card.vue'
|
||||
import Avatar from './Avatar.vue'
|
||||
import RankBadge from './RankBadge.vue'
|
||||
import AchievementBadge from './AchievementBadge.vue'
|
||||
@@ -16,63 +17,74 @@ defineProps({
|
||||
<template>
|
||||
<RouterLink
|
||||
:to="{ name: 'contributor', params: { login: contributor.login } }"
|
||||
:class="[
|
||||
'card animate-fade-in-up block cursor-pointer hover:shadow-lg transition-shadow',
|
||||
featured && rank === 1 ? 'ring-2 ring-yellow-400' : ''
|
||||
]"
|
||||
class="block group"
|
||||
>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="relative">
|
||||
<Avatar
|
||||
:src="contributor.avatar_url"
|
||||
:name="contributor.login"
|
||||
:size="featured ? 'xl' : 'lg'"
|
||||
/>
|
||||
<RankBadge
|
||||
v-if="showRank && rank > 0"
|
||||
:rank="rank"
|
||||
size="sm"
|
||||
class="absolute -top-1 -right-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<h3 class="font-semibold text-gray-800 dark:text-white group-hover:text-primary-500 transition-colors">
|
||||
{{ contributor.name || contributor.login }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<span
|
||||
class="hover:text-primary-500 transition-colors"
|
||||
@click.stop.prevent="window.open(`https://github.com/${contributor.login}`, '_blank')"
|
||||
>
|
||||
@{{ contributor.login }}
|
||||
<i class="fas fa-external-link-alt text-xs ml-0.5 opacity-50"></i>
|
||||
</span>
|
||||
</p>
|
||||
<p v-if="contributor.team" class="text-xs text-accent-500">{{ contributor.team }}</p>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<div class="text-2xl font-bold gradient-text">
|
||||
{{ formatNumber(contributor.score?.total || contributor.score || 0) }}
|
||||
<Card
|
||||
hover
|
||||
:class="[
|
||||
'animate-[fadeInUp_0.6s_ease-out] h-full',
|
||||
featured && rank === 1 ? 'ring-2 ring-yellow-400' : '',
|
||||
featured && rank === 2 ? 'ring-2 ring-gray-300' : '',
|
||||
featured && rank === 3 ? 'ring-2 ring-amber-600' : ''
|
||||
]"
|
||||
>
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Header with avatar and rank -->
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative">
|
||||
<Avatar
|
||||
:src="contributor.avatar_url"
|
||||
:name="contributor.login"
|
||||
:size="featured ? 'xl' : 'lg'"
|
||||
class="ring-2 ring-gray-100 dark:ring-gray-700"
|
||||
/>
|
||||
<RankBadge
|
||||
v-if="showRank && rank > 0"
|
||||
:rank="rank"
|
||||
size="sm"
|
||||
class="absolute -bottom-1 -right-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-bold text-lg text-gray-900 dark:text-white group-hover:text-primary-500 transition-colors">
|
||||
{{ contributor.name || contributor.login }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
@{{ contributor.login }}
|
||||
</p>
|
||||
<p v-if="contributor.team" class="text-xs text-accent-500 mt-0.5">{{ contributor.team }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">points</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="contributor.achievements?.length" class="mt-4 flex flex-wrap gap-1.5">
|
||||
<AchievementBadge
|
||||
v-for="achievement in contributor.achievements.slice(0, 6)"
|
||||
:key="achievement"
|
||||
:achievement-id="achievement"
|
||||
size="sm"
|
||||
/>
|
||||
<span
|
||||
v-if="contributor.achievements.length > 6"
|
||||
class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-bold"
|
||||
>
|
||||
+{{ contributor.achievements.length - 6 }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Score display -->
|
||||
<div class="flex items-center justify-between py-3 px-4 -mx-2 rounded-lg bg-gradient-to-r from-primary-50 to-accent-50 dark:from-primary-900/20 dark:to-accent-900/20 mb-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Score</span>
|
||||
<span class="text-2xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
|
||||
{{ formatNumber(contributor.score?.total || contributor.score || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Achievements -->
|
||||
<div v-if="contributor.achievements?.length" class="mt-auto">
|
||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">Achievements</div>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<AchievementBadge
|
||||
v-for="achievement in contributor.achievements.slice(0, 8)"
|
||||
:key="achievement"
|
||||
:achievement-id="achievement"
|
||||
size="sm"
|
||||
/>
|
||||
<span
|
||||
v-if="contributor.achievements.length > 8"
|
||||
class="inline-flex items-center justify-center px-2 h-7 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-bold"
|
||||
>
|
||||
+{{ contributor.achievements.length - 8 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
@@ -30,7 +30,7 @@ defineProps({
|
||||
class="ring-2 ring-transparent group-hover:ring-primary-500 transition-all"
|
||||
/>
|
||||
<div>
|
||||
<div class="font-medium text-gray-800 dark:text-white group-hover:text-primary-500 transition-colors">
|
||||
<div class="font-medium text-gray-900 dark:text-white group-hover:text-primary-500 transition-colors">
|
||||
{{ contributor.name || contributor.login }}
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
@@ -39,13 +39,13 @@ defineProps({
|
||||
:href="`https://github.com/${contributor.login}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-gray-500 dark:text-gray-400 hover:text-primary-500 transition-colors"
|
||||
class="font-medium text-gray-800 dark:text-gray-400 hover:text-primary-500 transition-colors"
|
||||
@click.stop
|
||||
>
|
||||
@{{ contributor.login }}
|
||||
<i class="fas fa-external-link-alt text-xs ml-1 opacity-50"></i>
|
||||
</a>
|
||||
<span v-else class="text-gray-500 dark:text-gray-400">
|
||||
<span v-else class="font-medium text-gray-800 dark:text-gray-400">
|
||||
@{{ contributor.login }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup>
|
||||
import Card from './Card.vue'
|
||||
|
||||
defineProps({
|
||||
columns: {
|
||||
type: Array,
|
||||
@@ -39,7 +41,7 @@ const getAlignClass = (align) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card overflow-hidden p-0">
|
||||
<Card :padding="false" class="overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
||||
<tr>
|
||||
@@ -47,7 +49,7 @@ const getAlignClass = (align) => {
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
:class="[
|
||||
'px-3 sm:px-6 py-3 sm: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-700 dark:text-gray-400 uppercase tracking-wider',
|
||||
getAlignClass(col.align),
|
||||
col.headerClass
|
||||
]"
|
||||
@@ -78,8 +80,8 @@ const getAlignClass = (align) => {
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="!items.length" class="text-center py-12">
|
||||
<i :class="emptyIcon" class="text-4xl text-gray-300 dark:text-gray-600 mb-4"></i>
|
||||
<p class="text-gray-500 dark:text-gray-400">{{ emptyMessage }}</p>
|
||||
<i :class="emptyIcon" class="text-4xl text-gray-400 dark:text-gray-600 mb-4"></i>
|
||||
<p class="text-gray-600 dark:text-gray-400">{{ emptyMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
@@ -16,18 +16,18 @@ const generatedAt = computed(() => {
|
||||
<template>
|
||||
<footer class="py-8 px-4 mt-16 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="container mx-auto text-center">
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
<p class="text-gray-700 dark:text-gray-400">
|
||||
Generated by
|
||||
<a
|
||||
href="https://github.com/lukaszraczylo/git-velocity"
|
||||
class="text-primary-500 hover:text-primary-600"
|
||||
class="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 font-medium"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Git Velocity
|
||||
</a>
|
||||
</p>
|
||||
<p v-if="generatedAt" class="text-sm text-gray-400 dark:text-gray-500 mt-2">
|
||||
<p v-if="generatedAt" class="text-sm text-gray-600 dark:text-gray-500 mt-2">
|
||||
{{ generatedAt }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router'
|
||||
import Card from './Card.vue'
|
||||
import Avatar from './Avatar.vue'
|
||||
import AchievementBadge from './AchievementBadge.vue'
|
||||
import { formatNumber } from '../composables/formatters'
|
||||
@@ -20,60 +21,62 @@ defineProps({
|
||||
<component
|
||||
:is="linkToProfile ? RouterLink : 'div'"
|
||||
:to="linkToProfile ? { name: 'contributor', params: { login: member.login } } : undefined"
|
||||
class="card block"
|
||||
:class="{ 'hover:shadow-lg transition cursor-pointer': linkToProfile }"
|
||||
class="block"
|
||||
:class="{ 'group': linkToProfile }"
|
||||
>
|
||||
<div class="flex items-center space-x-4 mb-4">
|
||||
<Avatar :src="member.avatar_url" :name="member.login" size="lg" />
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-800 dark:text-white">
|
||||
{{ member.name || member.login }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">@{{ member.login }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4 text-center mb-4">
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
{{ formatNumber(member.commit_count) }}
|
||||
<Card :hover="linkToProfile" :class="{ 'cursor-pointer': linkToProfile }">
|
||||
<div class="flex items-center space-x-4 mb-4">
|
||||
<Avatar :src="member.avatar_url" :name="member.login" size="lg" />
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">
|
||||
{{ member.name || member.login }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-800 dark:text-gray-400">@{{ member.login }}</p>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">Commits</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
{{ formatNumber(member.prs_opened) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">PRs</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
{{ formatNumber(member.reviews_given) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">Reviews</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">Score</span>
|
||||
<span class="text-xl font-bold gradient-text">
|
||||
{{ formatNumber(member.score?.total || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-4 text-center mb-4">
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ formatNumber(member.commit_count) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">Commits</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ formatNumber(member.prs_opened) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">PRs</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ formatNumber(member.reviews_given) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">Reviews</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="member.achievements?.length" class="mt-4 flex flex-wrap gap-2">
|
||||
<AchievementBadge
|
||||
v-for="achievement in member.achievements.slice(0, 4)"
|
||||
:key="achievement"
|
||||
:achievement-id="achievement"
|
||||
size="sm"
|
||||
/>
|
||||
<span
|
||||
v-if="member.achievements.length > 4"
|
||||
class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-bold"
|
||||
>
|
||||
+{{ member.achievements.length - 4 }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">Score</span>
|
||||
<span class="text-xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
|
||||
{{ formatNumber(member.score?.total || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="member.achievements?.length" class="mt-4 flex flex-wrap gap-2">
|
||||
<AchievementBadge
|
||||
v-for="achievement in member.achievements.slice(0, 4)"
|
||||
:key="achievement"
|
||||
:achievement-id="achievement"
|
||||
size="sm"
|
||||
/>
|
||||
<span
|
||||
v-if="member.achievements.length > 4"
|
||||
class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-bold"
|
||||
>
|
||||
+{{ member.achievements.length - 4 }}
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -11,32 +11,32 @@ const repositories = computed(() => globalData.value?.Repositories || [])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="sticky top-0 z-50 glass shadow-modern">
|
||||
<nav class="sticky top-0 z-50 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-700 shadow-lg">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- Logo -->
|
||||
<RouterLink to="/" class="flex items-center space-x-2">
|
||||
<i class="fas fa-rocket text-2xl gradient-text"></i>
|
||||
<span class="text-xl font-bold gradient-text">Git Velocity</span>
|
||||
<i class="fas fa-rocket text-2xl bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent"></i>
|
||||
<span class="text-xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">Git Velocity</span>
|
||||
</RouterLink>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="hidden md:flex items-center space-x-6">
|
||||
<RouterLink
|
||||
to="/"
|
||||
:class="route.path === '/' ? 'nav-link-active' : 'nav-link'"
|
||||
:class="route.path === '/' ? 'text-primary-500 font-medium' : 'text-gray-800 dark:text-gray-200 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors'"
|
||||
>
|
||||
Dashboard
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
to="/leaderboard"
|
||||
:class="route.path === '/leaderboard' ? 'nav-link-active' : 'nav-link'"
|
||||
:class="route.path === '/leaderboard' ? 'text-primary-500 font-medium' : 'text-gray-800 dark:text-gray-200 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors'"
|
||||
>
|
||||
Leaderboard
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
to="/how-scoring-works"
|
||||
:class="route.path === '/how-scoring-works' ? 'nav-link-active' : 'nav-link'"
|
||||
:class="route.path === '/how-scoring-works' ? 'text-primary-500 font-medium' : 'text-gray-800 dark:text-gray-200 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors'"
|
||||
>
|
||||
How Scoring Works
|
||||
</RouterLink>
|
||||
@@ -44,7 +44,7 @@ const repositories = computed(() => globalData.value?.Repositories || [])
|
||||
v-for="repo in repositories"
|
||||
:key="`${repo.Owner}/${repo.Name}`"
|
||||
:to="`/repos/${repo.Owner}/${repo.Name}`"
|
||||
:class="route.path.includes(`/repos/${repo.Owner}/${repo.Name}`) ? 'nav-link-active' : 'nav-link'"
|
||||
:class="route.path.includes(`/repos/${repo.Owner}/${repo.Name}`) ? 'text-primary-500 font-medium' : 'text-gray-800 dark:text-gray-200 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors'"
|
||||
>
|
||||
{{ repo.Name }}
|
||||
</RouterLink>
|
||||
|
||||
@@ -38,7 +38,7 @@ defineProps({
|
||||
<slot name="prefix"></slot>
|
||||
<h1 class="text-4xl font-bold mb-4">
|
||||
<i v-if="icon" :class="[icon, iconColor]" class="mr-3"></i>
|
||||
<span class="gradient-text">{{ title }}</span>
|
||||
<span class="bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">{{ title }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,17 +8,20 @@ const props = defineProps({
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'w-6 h-6 text-xs',
|
||||
md: 'w-8 h-8 text-sm'
|
||||
md: 'w-8 h-8 text-sm',
|
||||
lg: 'w-10 h-10 text-base'
|
||||
}
|
||||
|
||||
const rankClass = computed(() => {
|
||||
if (props.rank === 1) return 'rank-1'
|
||||
if (props.rank === 2) return 'rank-2'
|
||||
if (props.rank === 3) return 'rank-3'
|
||||
if (props.rank === 1) return 'bg-gradient-to-r from-yellow-400 to-amber-500'
|
||||
if (props.rank === 2) return 'bg-gradient-to-r from-slate-400 to-slate-500'
|
||||
if (props.rank === 3) return 'bg-gradient-to-r from-amber-600 to-amber-700'
|
||||
return 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300'
|
||||
})
|
||||
|
||||
const classes = computed(() => sizeClasses[props.size] || sizeClasses.md)
|
||||
|
||||
const isTopThree = computed(() => props.rank >= 1 && props.rank <= 3)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -26,6 +29,7 @@ const classes = computed(() => sizeClasses[props.size] || sizeClasses.md)
|
||||
:class="[classes, rankClass, { 'text-white': rank <= 3 }]"
|
||||
class="inline-flex items-center justify-center rounded-full font-bold"
|
||||
>
|
||||
{{ rank }}
|
||||
<i v-if="isTopThree" class="fas fa-trophy"></i>
|
||||
<template v-else>{{ rank }}</template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router'
|
||||
import Card from './Card.vue'
|
||||
import { formatNumber } from '../composables/formatters'
|
||||
|
||||
defineProps({
|
||||
@@ -13,35 +14,37 @@ defineProps({
|
||||
<template>
|
||||
<RouterLink
|
||||
:to="`/repos/${repo.owner}/${repo.name}`"
|
||||
class="card hover:shadow-lg transition group"
|
||||
class="block group"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-semibold text-gray-800 dark:text-white group-hover:text-primary-500 transition">
|
||||
{{ repo.name }}
|
||||
</h3>
|
||||
<i class="fas fa-arrow-right text-gray-400 group-hover:text-primary-500 transition"></i>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">{{ repo.owner }}/{{ repo.name }}</p>
|
||||
<Card hover>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white group-hover:text-primary-500 transition">
|
||||
{{ repo.name }}
|
||||
</h3>
|
||||
<i class="fas fa-arrow-right text-gray-600 dark:text-gray-400 group-hover:text-primary-500 transition"></i>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">{{ repo.owner }}/{{ repo.name }}</p>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4 text-center">
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
{{ formatNumber(repo.total_commits) }}
|
||||
<div class="grid grid-cols-3 gap-4 text-center">
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ formatNumber(repo.total_commits) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">Commits</div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">Commits</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
{{ formatNumber(repo.total_prs) }}
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ formatNumber(repo.total_prs) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">PRs</div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">PRs</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
{{ repo.active_contributors }}
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ repo.active_contributors }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">Contributors</div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">Contributors</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
@@ -16,7 +16,7 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
<i v-if="icon" :class="[icon, iconColor]" class="mr-2"></i>{{ title }}
|
||||
<slot name="suffix"></slot>
|
||||
</h2>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup>
|
||||
import Card from './Card.vue'
|
||||
import { formatNumber } from '../composables/formatters'
|
||||
|
||||
defineProps({
|
||||
@@ -6,23 +7,23 @@ defineProps({
|
||||
label: { type: String, required: true },
|
||||
icon: { type: String, default: '' },
|
||||
iconColor: { type: String, default: 'text-gray-500' },
|
||||
valueClass: { type: String, default: 'gradient-text' },
|
||||
valueClass: { type: String, default: 'bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent' },
|
||||
delay: { type: String, default: '0s' }
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card animate-fade-in-up" :style="{ animationDelay: delay }">
|
||||
<Card class="animate-[fadeInUp_0.6s_ease-out]" :style="{ animationDelay: delay }">
|
||||
<div class="flex items-center justify-between">
|
||||
<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-xs sm:text-sm text-gray-500 dark:text-gray-400 mt-1 truncate">{{ label }}</div>
|
||||
<div class="text-xs sm:text-sm text-gray-700 dark:text-gray-400 mt-1 truncate">{{ label }}</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router'
|
||||
import Card from './Card.vue'
|
||||
import Avatar from './Avatar.vue'
|
||||
import { formatNumber, slugify } from '../composables/formatters'
|
||||
|
||||
@@ -14,43 +15,45 @@ defineProps({
|
||||
<template>
|
||||
<RouterLink
|
||||
:to="`/teams/${slugify(team.name)}`"
|
||||
class="card hover:shadow-lg transition group"
|
||||
class="block group"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-semibold text-gray-800 dark:text-white group-hover:text-primary-500 transition">
|
||||
{{ team.name }}
|
||||
</h3>
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
:style="{ backgroundColor: team.color || '#8b5cf6' }"
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2 mb-4">
|
||||
<template v-for="(member, i) in (team.members || []).slice(0, 5)" :key="member">
|
||||
<Avatar :name="member" size="sm" />
|
||||
</template>
|
||||
<span
|
||||
v-if="(team.members?.length || 0) > 5"
|
||||
class="w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center text-gray-600 dark:text-gray-300 text-xs font-bold"
|
||||
>
|
||||
+{{ team.members.length - 5 }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 text-center">
|
||||
<div>
|
||||
<div class="text-lg font-semibold gradient-text">
|
||||
{{ formatNumber(team.total_score) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">Total Score</div>
|
||||
<Card hover>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white group-hover:text-primary-500 transition">
|
||||
{{ team.name }}
|
||||
</h3>
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
:style="{ backgroundColor: team.color || '#8b5cf6' }"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
{{ team.members?.length || 0 }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">Members</div>
|
||||
|
||||
<div class="flex items-center space-x-2 mb-4">
|
||||
<template v-for="(member, i) in (team.members || []).slice(0, 5)" :key="member">
|
||||
<Avatar :name="member" size="sm" />
|
||||
</template>
|
||||
<span
|
||||
v-if="(team.members?.length || 0) > 5"
|
||||
class="w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center text-gray-600 dark:text-gray-300 text-xs font-bold"
|
||||
>
|
||||
+{{ team.members.length - 5 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 text-center">
|
||||
<div>
|
||||
<div class="text-lg font-semibold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
|
||||
{{ formatNumber(team.total_score) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">Total Score</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ team.members?.length || 0 }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">Members</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
@@ -177,7 +177,7 @@ watch(() => props.showScore, () => {
|
||||
<div class="velocity-chart" :style="{ height }">
|
||||
<canvas ref="chartRef"></canvas>
|
||||
<div v-if="!timeline?.labels?.length" class="flex items-center justify-center h-full">
|
||||
<p class="text-gray-400">No velocity data available</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">No velocity data available</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+6
-111
@@ -26,123 +26,18 @@
|
||||
--color-accent-800: #6b21a8;
|
||||
--color-accent-900: #581c87;
|
||||
|
||||
--animate-fade-in-up: fadeInUp 0.6s ease-out;
|
||||
--animate-float: float 3s ease-in-out infinite;
|
||||
|
||||
@keyframes fadeInUp {
|
||||
0% { opacity: 0; transform: translateY(20px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
}
|
||||
|
||||
@utility glass {
|
||||
background-color: rgb(255 255 255 / 0.7);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgb(255 255 255 / 0.2);
|
||||
/* Dark mode body background - Tailwind v4 doesn't process dark: variants on body properly */
|
||||
.dark body,
|
||||
body.dark {
|
||||
background-image: linear-gradient(to bottom right, var(--color-gray-900), var(--color-gray-800));
|
||||
}
|
||||
|
||||
.dark .glass {
|
||||
background-color: rgb(17 24 39 / 0.7);
|
||||
border-color: rgb(255 255 255 / 0.1);
|
||||
}
|
||||
|
||||
@utility gradient-text {
|
||||
background-image: linear-gradient(to right, var(--color-primary-400), var(--color-accent-400));
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
@utility shadow-modern {
|
||||
box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.dark .shadow-modern {
|
||||
box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
@utility score-card {
|
||||
background-image: linear-gradient(to right, rgb(244 114 182 / 0.1), rgb(192 132 252 / 0.1));
|
||||
border: 1px solid rgb(244 114 182 / 0.2);
|
||||
}
|
||||
|
||||
.dark .score-card {
|
||||
background-image: linear-gradient(to right, rgb(244 114 182 / 0.05), rgb(192 132 252 / 0.05));
|
||||
border-color: rgb(244 114 182 / 0.1);
|
||||
}
|
||||
|
||||
@utility rank-1 {
|
||||
background-image: linear-gradient(to right, #facc15, #f59e0b);
|
||||
}
|
||||
|
||||
@utility rank-2 {
|
||||
background-image: linear-gradient(to right, #94a3b8, #64748b);
|
||||
}
|
||||
|
||||
@utility rank-3 {
|
||||
background-image: linear-gradient(to right, #d97706, #b45309);
|
||||
}
|
||||
|
||||
@utility achievement-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 9999px;
|
||||
background-image: linear-gradient(to right, var(--color-primary-400), var(--color-accent-400));
|
||||
color: white;
|
||||
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
@utility btn-primary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background-image: linear-gradient(to right, var(--color-primary-500), var(--color-accent-500));
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-image: linear-gradient(to right, var(--color-primary-600), var(--color-accent-600));
|
||||
}
|
||||
|
||||
@utility card {
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
@utility nav-link {
|
||||
color: #374151;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--color-primary-500);
|
||||
}
|
||||
|
||||
.dark .nav-link {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
.dark .nav-link:hover {
|
||||
color: var(--color-primary-400);
|
||||
}
|
||||
|
||||
@utility nav-link-active {
|
||||
color: var(--color-primary-500);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@utility animate-fade-in-up {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
html.dark {
|
||||
background-color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch, inject } from 'vue'
|
||||
import { useRoute, RouterLink } from 'vue-router'
|
||||
import Card from '../components/Card.vue'
|
||||
import PageHeader from '../components/PageHeader.vue'
|
||||
import LoadingState from '../components/LoadingState.vue'
|
||||
import ErrorState from '../components/ErrorState.vue'
|
||||
@@ -98,27 +99,27 @@ watch(globalData, loadContributor)
|
||||
:src="contributor.avatar_url"
|
||||
:name="contributor.login"
|
||||
size="2xl"
|
||||
class="shadow-modern"
|
||||
class="shadow-lg"
|
||||
/>
|
||||
|
||||
<div class="text-center md:text-left">
|
||||
<h1 class="text-4xl font-bold gradient-text">
|
||||
<h1 class="text-4xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
|
||||
{{ contributor.name || contributor.login }}
|
||||
</h1>
|
||||
<p class="text-xl text-gray-500 dark:text-gray-400 mt-1">
|
||||
<p class="text-xl text-gray-600 dark:text-gray-400 mt-1">
|
||||
<GithubLink :url="`https://github.com/${contributor.login}`">
|
||||
@{{ contributor.login }}
|
||||
</GithubLink>
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-center md:justify-start space-x-4 mt-4">
|
||||
<div class="score-card rounded-lg px-4 py-2">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">Score:</span>
|
||||
<span class="text-2xl font-bold gradient-text ml-2">
|
||||
<div class="bg-gradient-to-r from-pink-400/10 to-purple-400/10 dark:from-pink-400/5 dark:to-purple-400/5 border border-pink-400/20 dark:border-pink-400/10 rounded-lg px-4 py-2">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">Score:</span>
|
||||
<span class="text-2xl font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent ml-2">
|
||||
{{ formatNumber(contributor.score?.total || contributor.score || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="contributor.score?.rank" class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<div v-if="contributor.score?.rank" class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Rank #{{ contributor.score.rank }}
|
||||
<span v-if="contributor.score?.percentile_rank">
|
||||
(Top {{ formatPercent(contributor.score.percentile_rank) }})
|
||||
@@ -177,136 +178,136 @@ watch(globalData, loadContributor)
|
||||
<div class="container mx-auto">
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<!-- Code Stats -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">
|
||||
<Card>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<i class="fas fa-code text-green-500 mr-2"></i>Code Contributions
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Lines Added</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Lines Added</span>
|
||||
<span class="text-green-500 font-semibold">
|
||||
+{{ formatNumber(contributor.lines_added || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Lines Deleted</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Lines Deleted</span>
|
||||
<span class="text-red-500 font-semibold">
|
||||
-{{ formatNumber(contributor.lines_deleted || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="contributor.meaningful_lines_added !== undefined" class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Meaningful Lines Added</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Meaningful Lines Added</span>
|
||||
<span class="text-emerald-500 font-semibold">
|
||||
+{{ formatNumber(contributor.meaningful_lines_added || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="contributor.meaningful_lines_deleted !== undefined" class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Meaningful Lines Deleted</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Meaningful Lines Deleted</span>
|
||||
<span class="text-rose-500 font-semibold">
|
||||
-{{ formatNumber(contributor.meaningful_lines_deleted || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="contributor.comment_lines_added !== undefined" class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Comment Lines Added</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Comment Lines Added</span>
|
||||
<span class="text-cyan-500 font-semibold">
|
||||
+{{ formatNumber(contributor.comment_lines_added || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="contributor.comment_lines_deleted !== undefined" class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Comment Lines Deleted</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Comment Lines Deleted</span>
|
||||
<span class="text-amber-500 font-semibold">
|
||||
-{{ formatNumber(contributor.comment_lines_deleted || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Files Changed</span>
|
||||
<span class="text-gray-800 dark:text-white font-semibold">
|
||||
<span class="text-gray-700 dark:text-gray-300">Files Changed</span>
|
||||
<span class="text-gray-900 dark:text-white font-semibold">
|
||||
{{ formatNumber(contributor.files_changed || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="contributor.avg_pr_size" class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Avg PR Size</span>
|
||||
<span class="text-gray-800 dark:text-white font-semibold">
|
||||
<span class="text-gray-700 dark:text-gray-300">Avg PR Size</span>
|
||||
<span class="text-gray-900 dark:text-white font-semibold">
|
||||
{{ formatNumber(Math.round(contributor.avg_pr_size)) }} lines
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Review Stats -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">
|
||||
<Card>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<i class="fas fa-comments text-purple-500 mr-2"></i>Review Activity
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Reviews Given</span>
|
||||
<span class="text-gray-800 dark:text-white font-semibold">
|
||||
<span class="text-gray-700 dark:text-gray-300">Reviews Given</span>
|
||||
<span class="text-gray-900 dark:text-white font-semibold">
|
||||
{{ formatNumber(contributor.reviews_given || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Approvals</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Approvals</span>
|
||||
<span class="text-green-500 font-semibold">
|
||||
{{ formatNumber(contributor.approvals_given || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Changes Requested</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Changes Requested</span>
|
||||
<span class="text-orange-500 font-semibold">
|
||||
{{ formatNumber(contributor.changes_requested || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Review Comments</span>
|
||||
<span class="text-gray-800 dark:text-white font-semibold">
|
||||
<span class="text-gray-700 dark:text-gray-300">Review Comments</span>
|
||||
<span class="text-gray-900 dark:text-white font-semibold">
|
||||
{{ formatNumber(contributor.review_comments || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="contributor.avg_review_time_hours" class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Avg Review Time</span>
|
||||
<span class="text-gray-800 dark:text-white font-semibold">
|
||||
<span class="text-gray-700 dark:text-gray-300">Avg Review Time</span>
|
||||
<span class="text-gray-900 dark:text-white font-semibold">
|
||||
{{ formatDuration(contributor.avg_review_time_hours) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Issue Stats -->
|
||||
<div v-if="contributor.issues_opened || contributor.issues_closed || contributor.issue_comments || contributor.issue_references_in_commits" class="card">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">
|
||||
<Card v-if="contributor.issues_opened || contributor.issues_closed || contributor.issue_comments || contributor.issue_references_in_commits">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<i class="fas fa-bug text-red-500 mr-2"></i>Issue Activity
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Issues Opened</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Issues Opened</span>
|
||||
<span class="text-red-500 font-semibold">
|
||||
{{ formatNumber(contributor.issues_opened || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Issues Closed</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Issues Closed</span>
|
||||
<span class="text-green-500 font-semibold">
|
||||
{{ formatNumber(contributor.issues_closed || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Issue Comments</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Issue Comments</span>
|
||||
<span class="text-blue-500 font-semibold">
|
||||
{{ formatNumber(contributor.issue_comments || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-300">Issue References in Commits</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">Issue References in Commits</span>
|
||||
<span class="text-purple-500 font-semibold">
|
||||
{{ formatNumber(contributor.issue_references_in_commits || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -314,9 +315,9 @@ watch(globalData, loadContributor)
|
||||
<!-- Score Breakdown -->
|
||||
<section v-if="contributor.score?.breakdown" class="py-8 px-4">
|
||||
<div class="container mx-auto">
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">
|
||||
<i class="fas fa-chart-pie gradient-text mr-2"></i>Score Breakdown
|
||||
<Card>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<i class="fas fa-chart-pie bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent mr-2"></i>Score Breakdown
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-4">
|
||||
@@ -324,60 +325,60 @@ watch(globalData, loadContributor)
|
||||
<div class="text-2xl font-bold text-green-500">
|
||||
{{ formatNumber(contributor.score.breakdown.commits || 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">Commits</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">{{ contributor.commit_count || 0 }} × 10 pts</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Commits</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.commit_count || 0 }} × 10 pts</div>
|
||||
</div>
|
||||
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="text-2xl font-bold text-blue-500">
|
||||
{{ formatNumber(contributor.score.breakdown.prs || 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">PRs</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">{{ contributor.prs_opened || 0 }} opened + {{ contributor.prs_merged || 0 }} merged</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">PRs</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.prs_opened || 0 }} opened + {{ contributor.prs_merged || 0 }} merged</div>
|
||||
</div>
|
||||
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="text-2xl font-bold text-purple-500">
|
||||
{{ formatNumber(contributor.score.breakdown.reviews || 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">Reviews</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">{{ contributor.reviews_given || 0 }} × 30 pts</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Reviews</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.reviews_given || 0 }} × 30 pts</div>
|
||||
</div>
|
||||
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="text-2xl font-bold text-pink-500">
|
||||
{{ formatNumber(contributor.score.breakdown.comments || 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">Comments</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">{{ contributor.review_comments || 0 }} × 5 pts</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Comments</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.review_comments || 0 }} × 5 pts</div>
|
||||
</div>
|
||||
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="text-2xl font-bold text-red-500">
|
||||
{{ formatNumber(contributor.score.breakdown.issues || 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">Issues</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">opened, closed, comments, refs</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Issues</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">opened, closed, comments, refs</div>
|
||||
</div>
|
||||
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="text-2xl font-bold text-orange-500">
|
||||
{{ formatNumber(contributor.score.breakdown.line_changes || 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">Line Changes</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">meaningful lines × 0.1 pts</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Line Changes</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">meaningful lines × 0.1 pts</div>
|
||||
</div>
|
||||
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="text-2xl font-bold text-yellow-500">
|
||||
{{ formatNumber(contributor.score.breakdown.response_bonus || 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">Response Bonus</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">fast review bonus</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Response Bonus</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">fast review bonus</div>
|
||||
</div>
|
||||
<div class="text-center p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="text-2xl font-bold text-indigo-500">
|
||||
{{ formatNumber(contributor.score.breakdown.out_of_hours || 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">Out of Hours</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">{{ contributor.out_of_hours_count || 0 }} × 2 pts</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mt-1">Out of Hours</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">{{ contributor.out_of_hours_count || 0 }} × 2 pts</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -386,10 +387,10 @@ watch(globalData, loadContributor)
|
||||
<div class="container mx-auto">
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<!-- Earned Achievements -->
|
||||
<div v-if="contributor.achievements?.length" class="card">
|
||||
<Card v-if="contributor.achievements?.length">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
<i class="fas fa-award gradient-text mr-2"></i>Achievements Earned
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
<i class="fas fa-award bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent mr-2"></i>Achievements Earned
|
||||
</h3>
|
||||
<span class="px-2.5 py-1 rounded-full bg-gradient-to-r from-yellow-400 to-amber-500 text-white text-sm font-bold shadow-md">
|
||||
{{ contributor.achievements.length }}
|
||||
@@ -409,11 +410,11 @@ watch(globalData, loadContributor)
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Progress to Next Achievements -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-6">
|
||||
<Card>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-6">
|
||||
<i class="fas fa-chart-line text-primary-500 mr-2"></i>Next Achievements
|
||||
</h3>
|
||||
|
||||
@@ -421,7 +422,7 @@ watch(globalData, loadContributor)
|
||||
:contributor="contributor"
|
||||
:max-display="6"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -442,7 +443,7 @@ watch(globalData, loadContributor)
|
||||
:to="`/repos/${repo}`"
|
||||
class="inline-flex items-center px-3 py-1.5 rounded-full text-sm bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-primary-100 dark:hover:bg-primary-900/30 hover:text-primary-700 dark:hover:text-primary-300 transition-colors"
|
||||
>
|
||||
<i class="fas fa-code-branch text-gray-400 mr-2"></i>
|
||||
<i class="fas fa-code-branch text-gray-600 dark:text-gray-400 mr-2"></i>
|
||||
{{ repo }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { inject, computed, ref } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import Card from '../components/Card.vue'
|
||||
import StatCard from '../components/StatCard.vue'
|
||||
import ContributorCard from '../components/ContributorCard.vue'
|
||||
import RepoCard from '../components/RepoCard.vue'
|
||||
@@ -24,15 +25,15 @@ const showScoreInChart = ref(false)
|
||||
<div>
|
||||
<!-- Hero Section -->
|
||||
<header class="py-10 sm:py-16 px-4">
|
||||
<div class="container mx-auto text-center animate-fade-in-up">
|
||||
<div class="container mx-auto text-center animate-[fadeInUp_0.6s_ease-out]">
|
||||
<h1 class="text-3xl sm:text-4xl md:text-6xl font-bold mb-3 sm:mb-4">
|
||||
<span class="gradient-text">Git Velocity</span>
|
||||
<span class="bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">Git Velocity</span>
|
||||
</h1>
|
||||
<p class="text-base sm:text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto px-2">
|
||||
Celebrate your team's achievements and contributions with beautiful insights.
|
||||
</p>
|
||||
<!-- Period and Generation Info -->
|
||||
<div class="flex flex-col items-center space-y-2 mt-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<div class="flex flex-col items-center space-y-2 mt-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
<p v-if="metrics.period?.start || metrics.period?.end">
|
||||
<i class="fas fa-calendar-alt mr-1 text-primary-500"></i>
|
||||
<span class="font-medium">Period:</span>
|
||||
@@ -51,14 +52,14 @@ const showScoreInChart = ref(false)
|
||||
<!-- Velocity Timeline Chart -->
|
||||
<section v-if="velocityTimeline" class="py-6 sm:py-8 px-4">
|
||||
<div class="container mx-auto">
|
||||
<div class="card">
|
||||
<Card>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4 sm:mb-6">
|
||||
<SectionHeader title="Velocity Timeline" icon="fas fa-chart-line" icon-color="text-primary-500" />
|
||||
<label class="flex items-center space-x-2 text-sm text-gray-600 dark:text-gray-400 cursor-pointer">
|
||||
<label class="flex items-center space-x-2 text-sm text-gray-700 dark:text-gray-400 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="showScoreInChart"
|
||||
class="rounded border-gray-300 text-primary-500 focus:ring-primary-500"
|
||||
class="rounded border-gray-300 dark:border-gray-600 text-primary-500 focus:ring-primary-500"
|
||||
/>
|
||||
<span>Show Score</span>
|
||||
</label>
|
||||
@@ -66,7 +67,7 @@ const showScoreInChart = ref(false)
|
||||
<div class="h-[200px] sm:h-[280px] md:h-[320px]">
|
||||
<VelocityChart :timeline="velocityTimeline" :show-score="showScoreInChart" height="100%" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -126,7 +127,7 @@ const showScoreInChart = ref(false)
|
||||
</div>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<RouterLink to="/leaderboard" class="btn-primary">
|
||||
<RouterLink to="/leaderboard" class="inline-flex items-center px-6 py-3 bg-gradient-to-r from-primary-500 to-accent-500 text-white font-medium rounded-lg shadow-lg hover:from-primary-600 hover:to-accent-600 transition-all">
|
||||
View Full Leaderboard
|
||||
<i class="fas fa-arrow-right ml-2"></i>
|
||||
</RouterLink>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup>
|
||||
import Card from '../components/Card.vue'
|
||||
import SectionHeader from '../components/SectionHeader.vue'
|
||||
</script>
|
||||
|
||||
@@ -6,9 +7,9 @@ import SectionHeader from '../components/SectionHeader.vue'
|
||||
<div>
|
||||
<!-- Hero Section -->
|
||||
<header class="py-10 sm:py-16 px-4">
|
||||
<div class="container mx-auto text-center animate-fade-in-up">
|
||||
<h1 class="text-3xl sm:text-4xl md:text-5xl font-bold mb-3 sm:mb-4">
|
||||
How <span class="gradient-text">Scoring</span> Works
|
||||
<div class="container mx-auto text-center animate-[fadeInUp_0.6s_ease-out]">
|
||||
<h1 class="text-3xl sm:text-4xl md:text-5xl font-bold mb-3 sm:mb-4 text-gray-900 dark:text-white">
|
||||
How <span class="bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">Scoring</span> Works
|
||||
</h1>
|
||||
<p class="text-base sm:text-lg md:text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto px-2">
|
||||
Understanding the point system, leaderboard rankings, and achievement criteria that power Git Velocity.
|
||||
@@ -19,7 +20,7 @@ import SectionHeader from '../components/SectionHeader.vue'
|
||||
<!-- Overview Section -->
|
||||
<section class="py-8 px-4">
|
||||
<div class="container mx-auto">
|
||||
<div class="card glass shadow-modern mb-8">
|
||||
<Card class="shadow-lg mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center">
|
||||
<i class="fas fa-info-circle mr-3 text-blue-500"></i>
|
||||
Overview
|
||||
@@ -33,20 +34,20 @@ import SectionHeader from '../components/SectionHeader.vue'
|
||||
<div class="text-center p-4 bg-primary-50 dark:bg-primary-900/20 rounded-lg">
|
||||
<i class="fas fa-calculator text-primary-500 text-2xl mb-2"></i>
|
||||
<h3 class="font-medium text-gray-900 dark:text-gray-100">Point-Based</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Activities earn configurable points</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Activities earn configurable points</p>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-accent-50 dark:bg-accent-900/20 rounded-lg">
|
||||
<i class="fas fa-layer-group text-accent-500 text-2xl mb-2"></i>
|
||||
<h3 class="font-medium text-gray-900 dark:text-gray-100">Aggregated</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Combined across all repositories</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Combined across all repositories</p>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg">
|
||||
<i class="fas fa-trophy text-indigo-500 text-2xl mb-2"></i>
|
||||
<h3 class="font-medium text-gray-900 dark:text-gray-100">Achievement-Driven</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Unlock badges for milestones</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Unlock badges for milestones</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -57,7 +58,7 @@ import SectionHeader from '../components/SectionHeader.vue'
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Score Formula -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-lg sm:text-xl">
|
||||
<i class="fas fa-function mr-2 text-primary-500"></i>
|
||||
Score Formula
|
||||
@@ -75,14 +76,14 @@ Where:
|
||||
Response = fast review bonus (0-50 pts)
|
||||
Out of Hrs = commits outside 9-5 x 2 pts</code></pre>
|
||||
</div>
|
||||
<p class="text-xs sm:text-sm text-gray-500 dark:text-gray-400">
|
||||
<p class="text-xs sm:text-sm text-gray-600 dark:text-gray-400">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
All point values are configurable in your <code class="text-primary-600 dark:text-primary-400">.git-velocity.yaml</code> file.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Default Point Values -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-coins mr-2 text-yellow-500"></i>
|
||||
Default Point Values
|
||||
@@ -168,7 +169,7 @@ Where:
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fas fa-moon text-gray-500"></i>
|
||||
<i class="fas fa-moon text-gray-600 dark:text-gray-400"></i>
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">Out of Hours</span>
|
||||
</div>
|
||||
<span class="font-mono font-bold text-primary-600 dark:text-primary-400">2 pts</span>
|
||||
@@ -207,9 +208,9 @@ Where:
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||
<th class="text-left py-3 text-gray-600 dark:text-gray-400">Activity</th>
|
||||
<th class="text-left py-3 text-gray-600 dark:text-gray-400">Points</th>
|
||||
<th class="text-left py-3 text-gray-600 dark:text-gray-400">Description</th>
|
||||
<th class="text-left py-3 text-gray-700 dark:text-gray-400">Activity</th>
|
||||
<th class="text-left py-3 text-gray-700 dark:text-gray-400">Points</th>
|
||||
<th class="text-left py-3 text-gray-700 dark:text-gray-400">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-gray-700 dark:text-gray-300">
|
||||
@@ -296,10 +297,10 @@ Where:
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Meaningful Lines -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-filter mr-2 text-green-500"></i>
|
||||
Meaningful Lines
|
||||
@@ -332,11 +333,11 @@ Where:
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-4">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-4">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
Meaningful lines filtering is always enabled to accurately reflect code contributions.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -348,7 +349,7 @@ Where:
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Ranking Process -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-list-ol mr-2 text-accent-500"></i>
|
||||
Ranking Process
|
||||
@@ -383,10 +384,10 @@ Where:
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Top Categories -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-medal mr-2 text-yellow-500"></i>
|
||||
Top Achievers
|
||||
@@ -400,34 +401,34 @@ Where:
|
||||
<i class="fas fa-trophy text-yellow-500"></i>
|
||||
<span class="font-medium text-gray-900 dark:text-gray-100">Overall Leader</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Highest total score</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Highest total score</p>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i class="fas fa-code-commit text-primary-500"></i>
|
||||
<span class="font-medium text-gray-900 dark:text-gray-100">Top Committer</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Most commits</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Most commits</p>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i class="fas fa-eye text-accent-500"></i>
|
||||
<span class="font-medium text-gray-900 dark:text-gray-100">Top Reviewer</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Most reviews given</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Most reviews given</p>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i class="fas fa-code-pull-request text-indigo-500"></i>
|
||||
<span class="font-medium text-gray-900 dark:text-gray-100">Top PR Author</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Most PRs opened</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Most PRs opened</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Team Scoring -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-users mr-2 text-blue-500"></i>
|
||||
Team Scoring
|
||||
@@ -440,7 +441,7 @@ Where:
|
||||
<li><i class="fas fa-check text-green-500 mr-2"></i><strong>Average Score:</strong> Total score / number of members</li>
|
||||
<li><i class="fas fa-check text-green-500 mr-2"></i><strong>Member Breakdown:</strong> Individual scores and achievements per team member</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -453,7 +454,7 @@ Where:
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Achievement Categories -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-trophy mr-2 text-yellow-500"></i>
|
||||
Achievement Categories
|
||||
@@ -464,7 +465,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-code-commit text-primary-500 mr-2"></i>Commits
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 1, 10, 50, 100, 500, 1000</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 1, 10, 50, 100, 500, 1000</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
First Steps, Getting Started, Contributor, Committed, Code Machine, Code Warrior
|
||||
</div>
|
||||
@@ -474,7 +475,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-code-pull-request text-accent-500 mr-2"></i>PRs Opened
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 1, 10, 25, 50, 100, 250</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 1, 10, 25, 50, 100, 250</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
PR Pioneer, PR Regular, PR Pro, Merge Master, PR Champion, PR Legend
|
||||
</div>
|
||||
@@ -484,7 +485,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-eye text-indigo-500 mr-2"></i>Reviews Given
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 1, 10, 25, 50, 100, 250</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 1, 10, 25, 50, 100, 250</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
First Review, Reviewer, Review Regular, Review Expert, Review Guru, Review Master
|
||||
</div>
|
||||
@@ -494,7 +495,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-comment text-blue-500 mr-2"></i>Review Comments
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 10, 50, 100, 250, 500</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 10, 50, 100, 250, 500</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Commentator, Feedback Giver, Code Critic, Feedback Expert, Comment Champion
|
||||
</div>
|
||||
@@ -504,7 +505,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-plus text-green-500 mr-2"></i>Lines Added
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 100, 1K, 5K, 10K, 50K</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 100, 1K, 5K, 10K, 50K</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
First Hundred, Thousand Lines, Five Thousand, Ten Thousand, Code Mountain
|
||||
</div>
|
||||
@@ -514,7 +515,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-minus text-red-500 mr-2"></i>Lines Deleted
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 100, 500, 1K, 5K, 10K</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 100, 500, 1K, 5K, 10K</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Tidying Up, Spring Cleaning, Code Cleaner, Refactoring Hero, Deletion Master
|
||||
</div>
|
||||
@@ -524,7 +525,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-bolt text-yellow-500 mr-2"></i>Response Time
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: <24h, <4h, <1h</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: <24h, <4h, <1h</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Same Day Reviewer, Quick Responder, Speed Demon
|
||||
</div>
|
||||
@@ -534,7 +535,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-fire text-orange-500 mr-2"></i>Contribution Streaks
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 3, 7, 14, 30 days</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 3, 7, 14, 30 days</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Getting Rolling, Week Warrior, Two Week Streak, Month Master
|
||||
</div>
|
||||
@@ -544,7 +545,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-clock text-cyan-500 mr-2"></i>Activity Patterns
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Early Bird, Night Owl, Weekend Warrior</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Early Bird, Night Owl, Weekend Warrior</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Commits at different times of day unlock special badges
|
||||
</div>
|
||||
@@ -554,7 +555,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-circle-exclamation text-teal-500 mr-2"></i>Issues Opened
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 1, 5, 10, 25, 50</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 1, 5, 10, 25, 50</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Issue Opener, Reporter, Bug Hunter, Issue Tracker, Issue Master
|
||||
</div>
|
||||
@@ -564,7 +565,7 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-circle-check text-green-500 mr-2"></i>Issues Closed
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 1, 5, 10, 25, 50</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 1, 5, 10, 25, 50</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Issue Closer, Problem Solver, Resolver, Issue Crusher, Closure King
|
||||
</div>
|
||||
@@ -574,16 +575,16 @@ Where:
|
||||
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<i class="fas fa-comment-dots text-blue-500 mr-2"></i>Issue Comments
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Tiers: 5, 10, 25, 50, 100</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">Tiers: 5, 10, 25, 50, 100</p>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Issue Commenter, Discussion Starter, Feedback Provider, Issue Conversationalist, Discussion Champion
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Achievement Conditions -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-unlock mr-2 text-green-500"></i>
|
||||
How Achievements Are Earned
|
||||
@@ -596,9 +597,9 @@ Where:
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||
<th class="text-left py-2 text-gray-600 dark:text-gray-400">Condition Type</th>
|
||||
<th class="text-left py-2 text-gray-600 dark:text-gray-400">Metric Checked</th>
|
||||
<th class="text-left py-2 text-gray-600 dark:text-gray-400">Comparison</th>
|
||||
<th class="text-left py-2 text-gray-700 dark:text-gray-400">Condition Type</th>
|
||||
<th class="text-left py-2 text-gray-700 dark:text-gray-400">Metric Checked</th>
|
||||
<th class="text-left py-2 text-gray-700 dark:text-gray-400">Comparison</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-gray-700 dark:text-gray-300">
|
||||
@@ -645,14 +646,14 @@ Where:
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-4">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-4">
|
||||
<i class="fas fa-shield-halved mr-1"></i>
|
||||
Achievement definitions are hardcoded and cannot be customized to prevent manipulation.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Tiered Progression -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-layer-group mr-2 text-accent-500"></i>
|
||||
Tiered Progression
|
||||
@@ -694,10 +695,10 @@ Where:
|
||||
<div class="text-xs sm:text-sm"><span class="font-medium text-yellow-700 dark:text-yellow-400">Tier 8+</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
The leaderboard shows only the highest tier achieved per category for each contributor.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -708,7 +709,7 @@ Where:
|
||||
<SectionHeader title="Data Sources" icon="fab fa-github" icon-color="text-gray-700 dark:text-gray-300" />
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fab fa-github mr-2 text-gray-700 dark:text-gray-300"></i>
|
||||
GitHub API Data
|
||||
@@ -751,10 +752,10 @@ Where:
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Calculated Metrics -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-calculator mr-2 text-blue-500"></i>
|
||||
Derived Metrics
|
||||
@@ -765,33 +766,33 @@ Where:
|
||||
<div class="grid sm:grid-cols-2 gap-4 text-sm">
|
||||
<div class="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<strong class="text-gray-900 dark:text-gray-100">Meaningful Lines</strong>
|
||||
<p class="text-gray-500 dark:text-gray-400">Parsed from commit diffs, filtering comments/whitespace</p>
|
||||
<p class="text-gray-600 dark:text-gray-400">Parsed from commit diffs, filtering comments/whitespace</p>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<strong class="text-gray-900 dark:text-gray-100">Average Review Time</strong>
|
||||
<p class="text-gray-500 dark:text-gray-400">Time between PR creation and first review</p>
|
||||
<p class="text-gray-600 dark:text-gray-400">Time between PR creation and first review</p>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<strong class="text-gray-900 dark:text-gray-100">Contribution Streaks</strong>
|
||||
<p class="text-gray-500 dark:text-gray-400">Consecutive days with activity</p>
|
||||
<p class="text-gray-600 dark:text-gray-400">Consecutive days with activity</p>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<strong class="text-gray-900 dark:text-gray-100">Perfect PRs</strong>
|
||||
<p class="text-gray-500 dark:text-gray-400">PRs merged without "changes requested" reviews</p>
|
||||
<p class="text-gray-600 dark:text-gray-400">PRs merged without "changes requested" reviews</p>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<strong class="text-gray-900 dark:text-gray-100">Out of Hours</strong>
|
||||
<p class="text-gray-500 dark:text-gray-400">Commits outside 9am-5pm based on commit timestamp</p>
|
||||
<p class="text-gray-600 dark:text-gray-400">Commits outside 9am-5pm based on commit timestamp</p>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<strong class="text-gray-900 dark:text-gray-100">Issue References</strong>
|
||||
<p class="text-gray-500 dark:text-gray-400">Commits containing #123 patterns (fixes, closes, resolves, refs)</p>
|
||||
<p class="text-gray-600 dark:text-gray-400">Commits containing #123 patterns (fixes, closes, resolves, refs)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Bot Filtering -->
|
||||
<div class="card glass shadow-modern">
|
||||
<Card class="shadow-lg">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
|
||||
<i class="fas fa-robot mr-2 text-red-500"></i>
|
||||
Bot Filtering
|
||||
@@ -811,11 +812,11 @@ Where:
|
||||
<code class="px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded text-sm">allcontributors*</code>
|
||||
<code class="px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded text-sm">semantic-release*</code>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-4">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-4">
|
||||
<i class="fas fa-cog mr-1"></i>
|
||||
Enable with <code class="text-primary-600 dark:text-primary-400">include_bots: true</code> or add custom patterns with <code class="text-primary-600 dark:text-primary-400">additional_bot_patterns</code>.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+121
-62
@@ -1,9 +1,12 @@
|
||||
<script setup>
|
||||
import { ref, inject, computed } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import Card from '../components/Card.vue'
|
||||
import PageHeader from '../components/PageHeader.vue'
|
||||
import DataTable from '../components/DataTable.vue'
|
||||
import ContributorRow from '../components/ContributorRow.vue'
|
||||
import RankBadge from '../components/RankBadge.vue'
|
||||
import Avatar from '../components/Avatar.vue'
|
||||
import AchievementBadge from '../components/AchievementBadge.vue'
|
||||
import { formatNumber } from '../composables/formatters'
|
||||
import { getHighestTierAchievements } from '../composables/achievements'
|
||||
@@ -27,9 +30,8 @@ const leaderboard = computed(() => {
|
||||
const tableColumns = [
|
||||
{ key: 'rank', label: 'Rank', align: 'left' },
|
||||
{ key: 'contributor', label: 'Contributor', align: 'left' },
|
||||
{ key: 'achievements', label: 'Achievements', align: 'left' },
|
||||
{ key: 'team', label: 'Team', align: 'left', headerClass: 'hidden md:table-cell' },
|
||||
{ key: 'category', label: 'Best At', align: 'left', headerClass: 'hidden sm:table-cell' },
|
||||
{ key: 'achievements', label: 'Achievements', align: 'left', headerClass: 'hidden md:table-cell' },
|
||||
{ key: 'team', label: 'Team', align: 'left', headerClass: 'hidden xl:table-cell' },
|
||||
{ key: 'score', label: 'Score', align: 'right' }
|
||||
]
|
||||
|
||||
@@ -54,18 +56,18 @@ const categoryIcon = (category) => {
|
||||
centered
|
||||
/>
|
||||
|
||||
<!-- Search and Leaderboard Table -->
|
||||
<section class="py-8 px-4">
|
||||
<!-- Search and Leaderboard -->
|
||||
<section class="py-4 sm:py-8 px-4">
|
||||
<div class="container mx-auto max-w-5xl">
|
||||
<!-- Search Input -->
|
||||
<div class="mb-6">
|
||||
<div class="relative max-w-md">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<div class="mb-4 sm:mb-6">
|
||||
<div class="relative">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Search by name or username..."
|
||||
class="w-full pl-10 pr-4 py-2.5 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition"
|
||||
placeholder="Search contributors..."
|
||||
class="w-full pl-10 pr-10 py-2.5 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition text-sm sm:text-base"
|
||||
/>
|
||||
<button
|
||||
v-if="searchQuery"
|
||||
@@ -75,66 +77,123 @@ const categoryIcon = (category) => {
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="searchQuery && leaderboard.length !== allContributors.length" class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<p v-if="searchQuery && leaderboard.length !== allContributors.length" class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
Showing {{ leaderboard.length }} of {{ allContributors.length }} contributors
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
:columns="tableColumns"
|
||||
:items="leaderboard"
|
||||
empty-icon="fas fa-users"
|
||||
empty-message="No contributors found"
|
||||
row-class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition group"
|
||||
>
|
||||
<template #rank="{ item }">
|
||||
<RankBadge :rank="item.rank" />
|
||||
</template>
|
||||
<!-- Mobile Card Layout -->
|
||||
<div class="md:hidden space-y-3">
|
||||
<RouterLink
|
||||
v-for="item in leaderboard"
|
||||
:key="item.login"
|
||||
:to="{ name: 'contributor', params: { login: item.login } }"
|
||||
class="block"
|
||||
>
|
||||
<Card hover class="!p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Rank -->
|
||||
<RankBadge :rank="item.rank" size="sm" />
|
||||
|
||||
<template #contributor="{ item }">
|
||||
<ContributorRow :contributor="item" show-github-link />
|
||||
</template>
|
||||
<!-- Avatar -->
|
||||
<Avatar :src="item.avatar_url" :name="item.login" size="md" />
|
||||
|
||||
<template #achievements="{ item }">
|
||||
<div class="flex flex-wrap gap-1.5 max-w-[280px]">
|
||||
<AchievementBadge
|
||||
v-for="achievement in getHighestTierAchievements(item.achievements)"
|
||||
:key="achievement"
|
||||
:achievement-id="achievement"
|
||||
size="sm"
|
||||
/>
|
||||
<span v-if="!(item.achievements || []).length" class="text-gray-400 text-sm">-</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Info -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-semibold text-gray-900 dark:text-white truncate">
|
||||
{{ item.name || item.login }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 truncate">
|
||||
@{{ item.login }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #team="{ item }">
|
||||
<td class="hidden md:table-cell">
|
||||
<span
|
||||
v-if="item.team"
|
||||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300"
|
||||
>
|
||||
{{ item.team }}
|
||||
<!-- Score -->
|
||||
<div class="text-right">
|
||||
<div class="text-lg font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
|
||||
{{ formatNumber(item.score) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">pts</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Achievements row -->
|
||||
<div v-if="item.achievements?.length" class="mt-3 pt-3 border-t border-gray-100 dark:border-gray-700">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<AchievementBadge
|
||||
v-for="achievement in getHighestTierAchievements(item.achievements).slice(0, 6)"
|
||||
:key="achievement"
|
||||
:achievement-id="achievement"
|
||||
size="sm"
|
||||
/>
|
||||
<span
|
||||
v-if="getHighestTierAchievements(item.achievements).length > 6"
|
||||
class="inline-flex items-center justify-center px-2 h-7 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-bold"
|
||||
>
|
||||
+{{ getHighestTierAchievements(item.achievements).length - 6 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</RouterLink>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="!leaderboard.length" class="text-center py-12">
|
||||
<i class="fas fa-users text-4xl text-gray-400 dark:text-gray-600 mb-4"></i>
|
||||
<p class="text-gray-600 dark:text-gray-400">No contributors found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Table Layout -->
|
||||
<div class="hidden md:block">
|
||||
<DataTable
|
||||
:columns="tableColumns"
|
||||
:items="leaderboard"
|
||||
empty-icon="fas fa-users"
|
||||
empty-message="No contributors found"
|
||||
row-class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition group"
|
||||
>
|
||||
<template #rank="{ item }">
|
||||
<RankBadge :rank="item.rank" />
|
||||
</template>
|
||||
|
||||
<template #contributor="{ item }">
|
||||
<ContributorRow :contributor="item" show-github-link />
|
||||
</template>
|
||||
|
||||
<template #achievements="{ item }">
|
||||
<td class="hidden md:table-cell">
|
||||
<div class="flex flex-wrap gap-1.5 max-w-[280px]">
|
||||
<AchievementBadge
|
||||
v-for="achievement in getHighestTierAchievements(item.achievements)"
|
||||
:key="achievement"
|
||||
:achievement-id="achievement"
|
||||
size="sm"
|
||||
/>
|
||||
<span v-if="!(item.achievements || []).length" class="text-gray-600 dark:text-gray-400 text-sm">-</span>
|
||||
</div>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<template #team="{ item }">
|
||||
<td class="hidden xl:table-cell">
|
||||
<span
|
||||
v-if="item.team"
|
||||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300"
|
||||
>
|
||||
{{ item.team }}
|
||||
</span>
|
||||
<span v-else class="text-gray-600 dark:text-gray-400">-</span>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<template #score="{ item }">
|
||||
<span class="text-lg font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
|
||||
{{ formatNumber(item.score) }}
|
||||
</span>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<template #category="{ item }">
|
||||
<td class="hidden sm:table-cell">
|
||||
<span v-if="item.top_category" class="text-sm text-gray-600 dark:text-gray-300">
|
||||
<i :class="categoryIcon(item.top_category)" class="mr-1"></i>
|
||||
{{ item.top_category }}
|
||||
</span>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<template #score="{ item }">
|
||||
<span class="text-lg font-bold gradient-text">
|
||||
{{ formatNumber(item.score) }}
|
||||
</span>
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
</DataTable>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -122,13 +122,13 @@ watch(() => route.params, loadRepository)
|
||||
<SectionHeader title="Contributors" icon="fas fa-users" icon-color="text-blue-500" class="mb-0" />
|
||||
|
||||
<!-- Search Input -->
|
||||
<div class="relative w-full sm:w-64">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<div class="relative w-full sm:w-72 lg:w-96">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Search contributors..."
|
||||
class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition text-sm"
|
||||
class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition text-sm"
|
||||
/>
|
||||
<button
|
||||
v-if="searchQuery"
|
||||
@@ -140,7 +140,7 @@ watch(() => route.params, loadRepository)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="searchQuery && filteredContributors.length !== allContributors.length" class="mb-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<p v-if="searchQuery && filteredContributors.length !== allContributors.length" class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Showing {{ filteredContributors.length }} of {{ allContributors.length }} contributors
|
||||
</p>
|
||||
|
||||
@@ -155,21 +155,21 @@ watch(() => route.params, loadRepository)
|
||||
<ContributorRow :contributor="item" />
|
||||
</template>
|
||||
<template #commits="{ item }">
|
||||
<span class="text-gray-800 dark:text-white">{{ formatNumber(item.commit_count) }}</span>
|
||||
<span class="text-gray-900 dark:text-white">{{ formatNumber(item.commit_count) }}</span>
|
||||
</template>
|
||||
<template #prs="{ item }">
|
||||
<span class="text-gray-800 dark:text-white">{{ formatNumber(item.prs_opened) }}</span>
|
||||
<span class="text-gray-900 dark:text-white">{{ formatNumber(item.prs_opened) }}</span>
|
||||
</template>
|
||||
<template #reviews="{ item }">
|
||||
<span class="text-gray-800 dark:text-white">{{ formatNumber(item.reviews_given) }}</span>
|
||||
<span class="text-gray-900 dark:text-white">{{ formatNumber(item.reviews_given) }}</span>
|
||||
</template>
|
||||
<template #lines="{ item }">
|
||||
<span class="text-green-500">+{{ formatNumber(item.lines_added) }}</span>
|
||||
<span class="text-gray-400 mx-1">/</span>
|
||||
<span class="text-gray-600 dark:text-gray-400 mx-1">/</span>
|
||||
<span class="text-red-500">-{{ formatNumber(item.lines_deleted) }}</span>
|
||||
</template>
|
||||
<template #score="{ item }">
|
||||
<span class="text-lg font-bold gradient-text">
|
||||
<span class="text-lg font-bold bg-gradient-to-r from-primary-600 to-accent-600 dark:from-primary-400 dark:to-accent-400 bg-clip-text text-transparent">
|
||||
{{ formatNumber(item.score?.total || 0) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user