newest version of timetracker

This commit is contained in:
Daniel Goc
2026-03-11 11:46:25 +01:00
parent 9ef4bb219b
commit 5921987ad7
32 changed files with 422 additions and 275 deletions

View File

@@ -62,7 +62,7 @@ const PrivacyComponent = computed(() =>
<component :is="TermsComponent" />
</template>
<template #footer>
<UButton @click="showTherms = false" class="mx-auto px-12">close</UButton>
<UButton @click="showTherms = false" class="mx-auto px-12">{{ $t('general.close') }}</UButton>
</template>
</UDrawer>
<!-- PrivacyPolicyView -->
@@ -71,7 +71,7 @@ const PrivacyComponent = computed(() =>
<component :is="PrivacyComponent" />
</template>
<template #footer>
<UButton @click="showPrivacy = false" class="mx-auto px-12">close</UButton>
<UButton @click="showPrivacy = false" class="mx-auto px-12">{{ $t('general.close') }}</UButton>
</template>
</UDrawer>
<div class="h-[100vh] flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
@@ -120,7 +120,7 @@ const PrivacyComponent = computed(() =>
<!-- Divider -->
<div class="flex items-center gap-3 my-1">
<div class="flex-1 h-px bg-gray-200 dark:bg-gray-700" />
<span class="text-xs text-gray-400 dark:text-gray-500">or</span>
<span class="text-xs text-gray-400 dark:text-gray-500">{{ $t('general.or') }}</span>
<div class="flex-1 h-px bg-gray-200 dark:bg-gray-700" />
</div>

View File

@@ -14,11 +14,13 @@ import { getRepos, getYears, getQuarters, getIssues, type QuarterData, type Issu
import { useAuthStore } from '@/stores/auth'
import { i18n } from '@/plugins/02_i18n'
import type { TableColumn } from '@nuxt/ui'
import { useI18n } from 'vue-i18n'
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
const authStore = useAuthStore()
const { t } = useI18n()
const repos = ref<number[]>([])
const years = ref<number[]>([])
const quarters = ref<QuarterData[]>([])
@@ -47,7 +49,7 @@ async function loadData<T>(fetchFn: () => Promise<any>, target: Ref<T[]>, errorM
}
}
onMounted(() => loadData(() => getRepos(), repos, i18n.t('repo_chart.failed_to_load_repositories')))
onMounted(() => loadData(() => getRepos(), repos, t('repo_chart.failed_to_load_repositories')))
watch(selectedRepo, async (newRepo) => {
selectedYear.value = null
@@ -55,7 +57,7 @@ watch(selectedRepo, async (newRepo) => {
quarters.value = []
issues.value = []
if (newRepo) {
await loadData(() => getYears(newRepo), years, i18n.t('repo_chart.failed_to_load_years'))
await loadData(() => getYears(newRepo), years, t('repo_chart.failed_to_load_years'))
}
})
@@ -63,7 +65,7 @@ watch(selectedYear, async (newYear) => {
selectedQuarter.value = null
issues.value = []
if (newYear && selectedRepo.value) {
await loadData(() => getQuarters(selectedRepo.value!, newYear), quarters, i18n.t('repo_chart.failed_to_load_quarters'))
await loadData(() => getQuarters(selectedRepo.value!, newYear), quarters, t('repo_chart.failed_to_load_quarters'))
}
})
@@ -91,7 +93,7 @@ async function loadIssues(repoID: number, year: number, quarterStr: string) {
issues.value = response.items || []
totalItems.value = response.items_count || 0
} catch (e: any) {
error.value = e?.message || i18n.t('repo_chart.failed_to_load_issues')
error.value = e?.message || t('repo_chart.failed_to_load_issues')
} finally {
loading.value = false
}
@@ -101,24 +103,36 @@ const chartData = computed(() => ({
labels: quarters.value.map((q) => q.quarter),
datasets: [
{
label: i18n.t('repo_chart.hours_worked'),
label: t('repo_chart.hours_worked'),
backgroundColor: '#3b82f6',
data: quarters.value.map((q) => q.time),
},
],
}))
const chartOptions = {
const chartOptions = computed(() => ({
responsive: true,
maintainAspectRatio: false,
onClick: (_event: any, elements: any[]) => {
if (elements.length > 0) {
const index = elements[0].index
const quarter = quarters.value[index]
if (quarter) {
selectedQuarter.value = quarter.quarter
}
}
},
plugins: {
legend: { position: 'top' as const },
title: { display: true, text: i18n.t('repo_chart.work_by_quarter') },
title: { display: true, text: t('repo_chart.work_by_quarter') },
},
scales: {
y: { beginAtZero: true, title: { display: true, text: i18n.t('repo_chart.hours') } },
y: {
beginAtZero: true,
title: { display: true, text: t('repo_chart.hours') },
},
},
}
}))
const hasData = computed(() => quarters.value.length > 0)
const hasIssues = computed(() => issues.value.length > 0)
@@ -126,31 +140,27 @@ const hasIssues = computed(() => issues.value.length > 0)
const items = computed(() => repos.value.map(r => ({ value: r, label: `Repo ${r}` })))
const yearItems = computed(() => [
{ value: null, label: i18n.t('repo_chart.select_a_year') },
{ value: null, label: t('repo_chart.select_a_year') },
...years.value.map(y => ({ value: y, label: String(y) }))
])
const quarterItems = computed(() => [
{ value: null, label: i18n.t('repo_chart.all_quarters') },
{ value: null, label: t('repo_chart.all_quarters') },
...quarters.value.map(q => ({
value: q.quarter,
label: `${q.quarter} (${q.time.toFixed(1)}h)`
}))
])
const columns: TableColumn<IssueTimeSummary>[] = [
const columns = computed<TableColumn<IssueTimeSummary>[]>(() => [
{
accessorKey: 'IssueID',
header: 'ID',
header: 'ID'
},
{
accessorKey: 'IssueName',
header: i18n.t('repo_chart.issue_name'),
},
// {
// accessorKey: 'Initials',
// header: i18n.t('repo_chart.user_initials'),
// },
{
accessorKey: 'CreatedDate',
header: i18n.t('repo_chart.created_on'),
@@ -159,29 +169,15 @@ const columns: TableColumn<IssueTimeSummary>[] = [
return date.toLocaleDateString(i18n.locale.value)
}
},
{
accessorKey: 'CreatedDate',
header: i18n.t('repo_chart.created_on'),
cell: ({ row }) => {
const date = new Date(row.getValue('CreatedDate'))
return date.toLocaleTimeString(i18n.locale.value)
}
},
{
accessorKey: 'TotalHoursSpent',
header: i18n.t('repo_chart.hours_spent'),
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
class: { th: 'text-right', td: 'text-right font-medium' }
},
cell: ({ row }) => {
const hours = row.getValue('TotalHoursSpent')
return `${hours}h`
}
cell: ({ row }) => `${row.getValue('TotalHoursSpent')}h`
},
]
])
</script>
<template>
@@ -211,14 +207,16 @@ const columns: TableColumn<IssueTimeSummary>[] = [
}}</label>
<USelect v-model="selectedYear" :items="yearItems"
:disabled="loading || !selectedRepo || years.length === 0"
:placeholder="$t('repo_chart.select_a_year')" class="dark:text-white text-black placeholder:text-(--placeholder)" />
:placeholder="$t('repo_chart.select_a_year')"
class="dark:text-white text-black placeholder:text-(--placeholder)" />
</div>
<div class="flex flex-col min-w-[192px]">
<label class="mb-1 text-sm font-medium text-black dark:text-white">{{ $t('repo_chart.quarter')
}}</label>
<USelect v-model="selectedQuarter" :items="quarterItems"
:disabled="loading || !selectedYear || quarters.length === 0"
:placeholder="$t('repo_chart.all_quarters')" class="dark:text-white text-black placeholder:text-(--placeholder)" />
:placeholder="$t('repo_chart.all_quarters')"
class="dark:text-white text-black placeholder:text-(--placeholder)" />
</div>
</div>