i18n-dashboard 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -314,8 +314,11 @@
314
314
  "profile.name_label": "Full name",
315
315
  "profile.name_placeholder": "John Doe",
316
316
  "profile.total_translations": "Total translations",
317
- "profile.this_month": "This month",
318
- "profile.this_week": "This week",
317
+ "profile.period_1d": "Last 24 hours",
318
+ "profile.period_7d": "Last 7 days",
319
+ "profile.period_30d": "Last 30 days",
320
+ "profile.period_365d": "Last year",
321
+ "profile.period_all": "Since account creation",
319
322
  "profile.general": "General",
320
323
  "profile.name_email_required": "Name and email are required",
321
324
  "dashboard.title": "Dashboard",
@@ -1,6 +1,7 @@
1
1
  import { profileService } from '../services/profile.service'
2
2
  import { authService } from '../services/auth.service'
3
3
  import { userService } from '../services/user.service'
4
+ import type { ProfilePeriod } from '../server/interfaces/profile.interface'
4
5
 
5
6
  export function useProfile(userId?: MaybeRefOrGetter<number | string>) {
6
7
  const toast = useToast()
@@ -8,16 +9,15 @@ export function useProfile(userId?: MaybeRefOrGetter<number | string>) {
8
9
  const { fetchMe } = useAuth()
9
10
 
10
11
  // ── Profile data ─────────────────────────────────────────────────────────
11
- // Load the target user's profile (or own profile if no userId provided)
12
-
13
12
  const targetId = computed(() => userId ? toValue(userId) : null)
13
+ const period = ref<ProfilePeriod>('all')
14
14
 
15
15
  const { data: profile, pending, refresh } = useAsyncData(
16
- () => targetId.value ? `user-profile-${targetId.value}` : 'user-profile',
16
+ () => targetId.value ? `user-profile-${targetId.value}-${period.value}` : `user-profile-${period.value}`,
17
17
  () => targetId.value
18
- ? profileService.getUserProfile(targetId.value)
19
- : profileService.getProfile(),
20
- { watch: [targetId] },
18
+ ? profileService.getUserProfile(targetId.value, period.value)
19
+ : profileService.getProfile(period.value),
20
+ { watch: [targetId, period] },
21
21
  )
22
22
 
23
23
  // ── Own account editing (current logged-in user) ─────────────────────────
@@ -66,6 +66,7 @@ export function useProfile(userId?: MaybeRefOrGetter<number | string>) {
66
66
 
67
67
  return {
68
68
  profile,
69
+ period,
69
70
  pending,
70
71
  refresh,
71
72
  editSaving,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18n-dashboard",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "A web dashboard to manage vue-i18n translation keys with database persistence",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,8 +4,8 @@
4
4
  <!-- Loading -->
5
5
  <div v-if="pending" class="space-y-4">
6
6
  <USkeleton class="h-24" />
7
- <div class="grid grid-cols-3 gap-4">
8
- <USkeleton v-for="i in 3" :key="i" class="h-20" />
7
+ <div class="grid grid-cols-2 gap-4">
8
+ <USkeleton v-for="i in 2" :key="i" class="h-20" />
9
9
  </div>
10
10
  </div>
11
11
 
@@ -51,18 +51,27 @@
51
51
  </div>
52
52
 
53
53
  <!-- Stats -->
54
- <div class="grid grid-cols-3 gap-4">
55
- <UCard v-for="stat in statCards" :key="stat.label">
56
- <div class="flex items-center gap-3">
57
- <div class="p-2 rounded-lg" :class="stat.bg">
58
- <UIcon :name="stat.icon" class="text-xl" :class="stat.color" />
59
- </div>
60
- <div>
61
- <p class="text-xs text-gray-500 dark:text-gray-400">{{ stat.label }}</p>
62
- <p class="text-2xl font-bold text-gray-900 dark:text-white">{{ stat.value }}</p>
54
+ <div class="flex items-center justify-between gap-4">
55
+ <div class="grid grid-cols-2 gap-4 flex-1">
56
+ <UCard v-for="stat in statCards" :key="stat.label">
57
+ <div class="flex items-center gap-3">
58
+ <div class="p-2 rounded-lg" :class="stat.bg">
59
+ <UIcon :name="stat.icon" class="text-xl" :class="stat.color" />
60
+ </div>
61
+ <div>
62
+ <p class="text-xs text-gray-500 dark:text-gray-400">{{ stat.label }}</p>
63
+ <p class="text-2xl font-bold text-gray-900 dark:text-white">{{ stat.value }}</p>
64
+ </div>
63
65
  </div>
64
- </div>
65
- </UCard>
66
+ </UCard>
67
+ </div>
68
+ <USelect
69
+ v-model="period"
70
+ :items="periodOptions"
71
+ class="w-52 shrink-0"
72
+ value-key="value"
73
+ label-key="label"
74
+ />
66
75
  </div>
67
76
 
68
77
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 items-start">
@@ -258,7 +267,7 @@ const { projects } = useProject()
258
267
  const { t } = useT()
259
268
 
260
269
  const userId = computed(() => Number(route.params.id))
261
- const { profile, pending, refresh, editSaving: selfEditSaving, editError, updateProfile, rolesSaving, saveRoles } = useProfile(userId)
270
+ const { profile, period, pending, refresh, editSaving: selfEditSaving, editError, updateProfile, rolesSaving, saveRoles } = useProfile(userId)
262
271
 
263
272
  const isSelf = computed(() => currentUser.value?.id === userId.value)
264
273
 
@@ -355,6 +364,16 @@ async function doSaveRoles() {
355
364
  }
356
365
 
357
366
  // ── Stats ──────────────────────────────────────────────────────────────────────
367
+ const periodOptions = computed(() => [
368
+ { label: t('profile.period_1d', 'Last 24 hours'), value: '1d' },
369
+ { label: t('profile.period_7d', 'Last 7 days'), value: '7d' },
370
+ { label: t('profile.period_30d', 'Last 30 days'), value: '30d' },
371
+ { label: t('profile.period_365d', 'Last year'), value: '365d' },
372
+ { label: t('profile.period_all', 'Since account creation'), value: 'all' },
373
+ ])
374
+
375
+ const periodLabel = computed(() => periodOptions.value.find(o => o.value === period.value)?.label ?? '')
376
+
358
377
  const statCards = computed(() => [
359
378
  {
360
379
  label: t('profile.total_translations', 'Total translations'),
@@ -364,19 +383,12 @@ const statCards = computed(() => [
364
383
  bg: 'bg-primary-50 dark:bg-primary-900/20',
365
384
  },
366
385
  {
367
- label: t('profile.this_month', 'This month'),
368
- value: profile.value?.stats.thisMonth ?? 0,
386
+ label: periodLabel.value,
387
+ value: profile.value?.stats.periodCount ?? 0,
369
388
  icon: 'i-heroicons-calendar-days',
370
389
  color: 'text-blue-600',
371
390
  bg: 'bg-blue-50 dark:bg-blue-900/20',
372
391
  },
373
- {
374
- label: t('profile.this_week', 'This week'),
375
- value: profile.value?.stats.thisWeek ?? 0,
376
- icon: 'i-heroicons-bolt',
377
- color: 'text-green-600',
378
- bg: 'bg-green-50 dark:bg-green-900/20',
379
- },
380
392
  ])
381
393
 
382
394
  // ── Languages grouped by project ───────────────────────────────────────────────
@@ -1,8 +1,18 @@
1
1
  import { getDb } from '../db/index'
2
- import type { UserProfile } from '../interfaces/profile.interface'
2
+ import type { UserProfile, ProfilePeriod } from '../interfaces/profile.interface'
3
+
4
+ const PERIOD_MS: Record<ProfilePeriod, number | null> = {
5
+ '1d': 24 * 60 * 60 * 1000,
6
+ '7d': 7 * 24 * 60 * 60 * 1000,
7
+ '30d': 30 * 24 * 60 * 60 * 1000,
8
+ '365d': 365 * 24 * 60 * 60 * 1000,
9
+ 'all': null,
10
+ }
3
11
 
4
12
  export default defineEventHandler(async (event): Promise<UserProfile> => {
5
13
  const user = event.context.user
14
+ const query = getQuery(event)
15
+ const period = (query.period as ProfilePeriod) || 'all'
6
16
  const db = getDb()
7
17
 
8
18
  // ── Roles with project info ───────────────────────────────────────────────
@@ -31,13 +41,15 @@ export default defineEventHandler(async (event): Promise<UserProfile> => {
31
41
  }
32
42
 
33
43
  // ── Stats ─────────────────────────────────────────────────────────────────
34
- const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
35
- const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
44
+ const ms = PERIOD_MS[period] ?? null
45
+ const since = ms ? new Date(Date.now() - ms).toISOString() : null
46
+
47
+ const baseQ = db('translation_history').where('changed_by', user.name)
48
+ const periodQ = since ? db('translation_history').where('changed_by', user.name).where('changed_at', '>=', since) : baseQ.clone()
36
49
 
37
- const [countTotal, countWeek, countMonth] = await Promise.all([
50
+ const [countTotal, countPeriod] = await Promise.all([
38
51
  db('translation_history').where('changed_by', user.name).count('id as n').first(),
39
- db('translation_history').where('changed_by', user.name).where('changed_at', '>=', weekAgo).count('id as n').first(),
40
- db('translation_history').where('changed_by', user.name).where('changed_at', '>=', monthAgo).count('id as n').first(),
52
+ periodQ.count('id as n').first(),
41
53
  ])
42
54
 
43
55
  // ── Recent translations ───────────────────────────────────────────────────
@@ -72,8 +84,8 @@ export default defineEventHandler(async (event): Promise<UserProfile> => {
72
84
  roles,
73
85
  stats: {
74
86
  total: Number(countTotal?.n ?? 0),
75
- thisWeek: Number(countWeek?.n ?? 0),
76
- thisMonth: Number(countMonth?.n ?? 0),
87
+ periodCount: Number(countPeriod?.n ?? 0),
88
+ period,
77
89
  },
78
90
  languages,
79
91
  recentTranslations,
@@ -35,7 +35,7 @@ export default defineEventHandler(async (event) => {
35
35
  translation_id: existing.id,
36
36
  old_value: oldValue,
37
37
  new_value: value,
38
- changed_by: 'user',
38
+ changed_by: event.context.user?.name ?? 'user',
39
39
  })
40
40
  }
41
41
 
@@ -1,10 +1,20 @@
1
1
  import { getDb } from '../../../db/index'
2
2
  import { getUserRole } from '../../../utils/auth.util'
3
- import type { UserProfile } from '../../../interfaces/profile.interface'
3
+ import type { UserProfile, ProfilePeriod } from '../../../interfaces/profile.interface'
4
+
5
+ const PERIOD_MS: Record<ProfilePeriod, number | null> = {
6
+ '1d': 24 * 60 * 60 * 1000,
7
+ '7d': 7 * 24 * 60 * 60 * 1000,
8
+ '30d': 30 * 24 * 60 * 60 * 1000,
9
+ '365d': 365 * 24 * 60 * 60 * 1000,
10
+ 'all': null,
11
+ }
4
12
 
5
13
  export default defineEventHandler(async (event): Promise<UserProfile> => {
6
14
  const currentUser = event.context.user
7
15
  const targetId = Number(getRouterParam(event, 'id'))
16
+ const query = getQuery(event)
17
+ const period = (query.period as ProfilePeriod) || 'all'
8
18
  const db = getDb()
9
19
 
10
20
  const target = await db('users')
@@ -56,13 +66,16 @@ export default defineEventHandler(async (event): Promise<UserProfile> => {
56
66
  }
57
67
 
58
68
  // ── Stats ──────────────────────────────────────────────────────────────────
59
- const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
60
- const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
69
+ const ms = PERIOD_MS[period] ?? null
70
+ const since = ms ? new Date(Date.now() - ms).toISOString() : null
71
+
72
+ const periodQ = since
73
+ ? db('translation_history').where('changed_by', target.name).where('changed_at', '>=', since)
74
+ : db('translation_history').where('changed_by', target.name)
61
75
 
62
- const [countTotal, countWeek, countMonth] = await Promise.all([
76
+ const [countTotal, countPeriod] = await Promise.all([
63
77
  db('translation_history').where('changed_by', target.name).count('id as n').first(),
64
- db('translation_history').where('changed_by', target.name).where('changed_at', '>=', weekAgo).count('id as n').first(),
65
- db('translation_history').where('changed_by', target.name).where('changed_at', '>=', monthAgo).count('id as n').first(),
78
+ periodQ.count('id as n').first(),
66
79
  ])
67
80
 
68
81
  // ── Recent translations ────────────────────────────────────────────────────
@@ -98,8 +111,8 @@ export default defineEventHandler(async (event): Promise<UserProfile> => {
98
111
  roles,
99
112
  stats: {
100
113
  total: Number(countTotal?.n ?? 0),
101
- thisWeek: Number(countWeek?.n ?? 0),
102
- thisMonth: Number(countMonth?.n ?? 0),
114
+ periodCount: Number(countPeriod?.n ?? 0),
115
+ period,
103
116
  },
104
117
  languages,
105
118
  recentTranslations,
@@ -1,7 +1,9 @@
1
+ export type ProfilePeriod = '1d' | '7d' | '30d' | '365d' | 'all'
2
+
1
3
  export interface ProfileStats {
2
4
  total: number
3
- thisWeek: number
4
- thisMonth: number
5
+ periodCount: number
6
+ period: ProfilePeriod
5
7
  }
6
8
 
7
9
  export interface ProfileRole {
@@ -1,13 +1,13 @@
1
1
  import { BaseService } from './base.service'
2
- import type { UserProfile } from '../server/interfaces/profile.interface'
2
+ import type { UserProfile, ProfilePeriod } from '../server/interfaces/profile.interface'
3
3
 
4
4
  class ProfileService extends BaseService {
5
- async getProfile(): Promise<UserProfile> {
6
- return this.get<UserProfile>('/api/profile')
5
+ async getProfile(period: ProfilePeriod = 'all'): Promise<UserProfile> {
6
+ return this.get<UserProfile>('/api/profile', { query: { period } })
7
7
  }
8
8
 
9
- async getUserProfile(id: number | string): Promise<UserProfile> {
10
- return this.get<UserProfile>(`/api/users/${id}/profile`)
9
+ async getUserProfile(id: number | string, period: ProfilePeriod = 'all'): Promise<UserProfile> {
10
+ return this.get<UserProfile>(`/api/users/${id}/profile`, { query: { period } })
11
11
  }
12
12
  }
13
13