mirror of
https://github.com/lukaszraczylo/git-velocity.git
synced 2026-06-19 03:51:45 +00:00
7ba4d438dd
* feat(scoring): add tests bonus and fix average calculations - [x] Add CommitsWithTests metric to track commits with test file changes - [x] Add TestsBonus to score breakdown (15 points per commit with tests) - [x] Fix AvgTimeToMerge calculation to use count of PRs with valid data - [x] Fix AvgReviewTime calculation to use count of reviews with valid data - [x] Fix AvgPRSize calculation to only include merged PRs - [x] Add trackActivityDay helper to deduplicate activity tracking code - [x] Track activity days for PR creation, reviews, and issue comments - [x] Separate issue close tracking from issue open tracking - [x] Update early bird window from 5am-9am to 6am-9am - [x] Add time-based multipliers to velocity timeline scoring - [x] Update GraphQL query to fetch OPEN, MERGED, CLOSED PRs - [x] Fix PR filtering logic to handle all PR states correctly - [x] Improve watch handlers in Vue components to prevent double-loading - [x] Fix formatDuration to handle zero and negative values - [x] Update scoring documentation to include Tests component * refactor: use standard library and consolidate constants - [x] Replace custom contains function with slices.Contains - [x] Remove duplicate contains function implementations - [x] Extract magic numbers to named constants in formatters - [x] Create constants composable for app-wide values - [x] Add ESLint configuration with browser globals - [x] Add lint npm scripts to package.json - [x] Reorder Vue template attributes for consistency - [x] Remove unused variable in AchievementProgress - [x] Add pnpm lock file
126 lines
3.6 KiB
Vue
126 lines
3.6 KiB
Vue
<script setup>
|
|
import { ref, computed, onMounted, watch, inject } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import PageHeader from '../components/PageHeader.vue'
|
|
import LoadingState from '../components/LoadingState.vue'
|
|
import ErrorState from '../components/ErrorState.vue'
|
|
import StatCard from '../components/StatCard.vue'
|
|
import MemberCard from '../components/MemberCard.vue'
|
|
import SectionHeader from '../components/SectionHeader.vue'
|
|
import { slugify } from '../composables/formatters'
|
|
import { DEFAULT_TEAM_COLOR } from '../composables/constants'
|
|
|
|
const route = useRoute()
|
|
const globalData = inject('globalData')
|
|
const team = ref(null)
|
|
const loading = ref(true)
|
|
const error = ref(null)
|
|
|
|
const breadcrumbs = computed(() => [
|
|
{ label: 'Dashboard', to: '/' },
|
|
{ label: 'Teams' },
|
|
{ label: team.value?.name || route.params.slug }
|
|
])
|
|
|
|
function loadTeam() {
|
|
loading.value = true
|
|
error.value = null
|
|
|
|
const teams = globalData.value?.teams || []
|
|
const found = teams.find(t => slugify(t.name) === route.params.slug)
|
|
|
|
if (found) {
|
|
team.value = found
|
|
} else {
|
|
error.value = 'Team not found'
|
|
}
|
|
|
|
loading.value = false
|
|
}
|
|
|
|
onMounted(loadTeam)
|
|
|
|
// Watch for route changes (navigation to different team)
|
|
watch(() => route.params.slug, (newSlug, oldSlug) => {
|
|
if (newSlug && newSlug !== oldSlug) {
|
|
loadTeam()
|
|
}
|
|
})
|
|
|
|
// Watch for globalData changes, but only reload if we don't have team data yet
|
|
watch(globalData, (newData, oldData) => {
|
|
if (newData && !oldData && (error.value || !team.value)) {
|
|
loadTeam()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<LoadingState v-if="loading" message="Loading team..." />
|
|
<ErrorState v-else-if="error" :message="error" />
|
|
|
|
<template v-else-if="team">
|
|
<PageHeader
|
|
:title="team.name"
|
|
:breadcrumbs="breadcrumbs"
|
|
:subtitle="`${team.members?.length || 0} team members`"
|
|
>
|
|
<template #prefix>
|
|
<div
|
|
class="w-4 h-4 rounded-full mr-4"
|
|
:style="{ backgroundColor: team.color || DEFAULT_TEAM_COLOR }"
|
|
></div>
|
|
</template>
|
|
</PageHeader>
|
|
|
|
<!-- Team Stats -->
|
|
<section class="py-8 px-4">
|
|
<div class="container mx-auto">
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<StatCard
|
|
:value="team.total_score"
|
|
label="Total Score"
|
|
icon="fas fa-star"
|
|
icon-color="text-yellow-500"
|
|
/>
|
|
<StatCard
|
|
:value="team.aggregated_metrics?.commit_count || 0"
|
|
label="Commits"
|
|
icon="fas fa-code-commit"
|
|
icon-color="text-green-500"
|
|
/>
|
|
<StatCard
|
|
:value="team.aggregated_metrics?.prs_merged || 0"
|
|
label="PRs Merged"
|
|
icon="fas fa-code-merge"
|
|
icon-color="text-purple-500"
|
|
/>
|
|
<StatCard
|
|
:value="team.aggregated_metrics?.reviews_given || 0"
|
|
label="Reviews"
|
|
icon="fas fa-eye"
|
|
icon-color="text-blue-500"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Team Members -->
|
|
<section class="py-8 px-4">
|
|
<div class="container mx-auto">
|
|
<SectionHeader title="Team Members" icon="fas fa-users" icon-color="text-blue-500" />
|
|
|
|
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<MemberCard
|
|
v-for="member in team.member_metrics"
|
|
:key="member.login"
|
|
:member="member"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
</div>
|
|
</template>
|