Files
service-finder/frontend_old/views/AdminDashboard.vue
2026-06-04 07:26:22 +00:00

412 lines
14 KiB
Vue

<template>
<div class="admin-dashboard">
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold mb-2">Admin Dashboard</h1>
<p class="text-gray-400">Monitor system health, manage translations, and oversee platform operations.</p>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-gray-800/50 rounded-xl border border-gray-700 p-6">
<div class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-400 mb-1">Total Users</div>
<div class="text-3xl font-bold">{{ stats.totalUsers }}</div>
</div>
<div class="w-12 h-12 rounded-lg bg-blue-900/30 flex items-center justify-center">
<span class="text-2xl">👥</span>
</div>
</div>
<div class="mt-4 text-sm text-gray-400">
<span class="text-green-400">+{{ stats.newUsersToday }} today</span>
</div>
</div>
<div class="bg-gray-800/50 rounded-xl border border-gray-700 p-6">
<div class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-400 mb-1">Active Sessions</div>
<div class="text-3xl font-bold">{{ stats.activeSessions }}</div>
</div>
<div class="w-12 h-12 rounded-lg bg-green-900/30 flex items-center justify-center">
<span class="text-2xl">🔌</span>
</div>
</div>
<div class="mt-4 text-sm text-gray-400">
<span class="text-green-400">{{ stats.sessionGrowth }}% growth</span>
</div>
</div>
<div class="bg-gray-800/50 rounded-xl border border-gray-700 p-6">
<div class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-400 mb-1">Vehicles Catalog</div>
<div class="text-3xl font-bold">{{ stats.vehicleCount }}</div>
</div>
<div class="w-12 h-12 rounded-lg bg-purple-900/30 flex items-center justify-center">
<span class="text-2xl">🚗</span>
</div>
</div>
<div class="mt-4 text-sm text-gray-400">
<span class="text-blue-400">{{ stats.pendingVerification }} pending</span>
</div>
</div>
<div class="bg-gray-800/50 rounded-xl border border-gray-700 p-6">
<div class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-400 mb-1">API Health</div>
<div class="text-3xl font-bold">{{ stats.apiLatency }}ms</div>
</div>
<div class="w-12 h-12 rounded-lg bg-yellow-900/30 flex items-center justify-center">
<span class="text-2xl"></span>
</div>
</div>
<div class="mt-4 text-sm text-gray-400">
<span :class="stats.apiStatus === 'healthy' ? 'text-green-400' : 'text-red-400'">
{{ stats.apiStatus }}
</span>
</div>
</div>
</div>
<!-- Two Column Layout -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- i18n Translations Manager -->
<div class="bg-gray-800/50 rounded-xl border border-gray-700 p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-bold">i18n Translation Manager</h2>
<div class="flex space-x-2">
<button
class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors"
@click="refreshTranslations"
>
Refresh
</button>
<button
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
@click="showAddModal = true"
>
Add Key
</button>
</div>
</div>
<!-- Language Selector -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-400 mb-2">Select Language</label>
<div class="flex space-x-2">
<button
v-for="lang in languages"
:key="lang.code"
class="px-4 py-2 rounded-lg transition-colors"
:class="selectedLanguage === lang.code
? 'bg-blue-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'"
@click="selectedLanguage = lang.code"
>
{{ lang.name }}
</button>
</div>
</div>
<!-- Translations Table -->
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-700">
<th class="text-left py-3 px-4 text-sm font-medium text-gray-400">Key</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-400">Translation</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-400">Actions</th>
</tr>
</thead>
<tbody>
<tr
v-for="(translation, key) in filteredTranslations"
:key="key"
class="border-b border-gray-800 hover:bg-gray-800/30 transition-colors"
>
<td class="py-3 px-4 font-mono text-sm">{{ key }}</td>
<td class="py-3 px-4">
<input
v-model="translationEdits[key]"
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm"
:placeholder="translation"
@change="saveTranslation(key, translationEdits[key])"
/>
</td>
<td class="py-3 px-4">
<button
class="px-3 py-1 bg-red-900/30 hover:bg-red-900/50 text-red-400 rounded text-sm"
@click="deleteTranslation(key)"
>
Delete
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div v-if="Object.keys(filteredTranslations).length === 0" class="text-center py-8 text-gray-500">
No translations found for {{ selectedLanguage.toUpperCase() }}.
</div>
</div>
<!-- System Activity & Quick Actions -->
<div class="space-y-8">
<!-- Recent Activity -->
<div class="bg-gray-800/50 rounded-xl border border-gray-700 p-6">
<h2 class="text-xl font-bold mb-6">Recent System Activity</h2>
<div class="space-y-4">
<div
v-for="activity in recentActivity"
:key="activity.id"
class="flex items-start space-x-4 p-3 rounded-lg hover:bg-gray-800/30 transition-colors"
>
<div class="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center">
<span class="text-lg">{{ activity.icon }}</span>
</div>
<div class="flex-1">
<div class="font-medium">{{ activity.title }}</div>
<div class="text-sm text-gray-400">{{ activity.description }}</div>
<div class="text-xs text-gray-500 mt-1">{{ activity.time }}</div>
</div>
<div :class="`px-3 py-1 rounded-full text-xs ${activity.statusClass}`">
{{ activity.status }}
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="bg-gray-800/50 rounded-xl border border-gray-700 p-6">
<h2 class="text-xl font-bold mb-6">Quick Actions</h2>
<div class="grid grid-cols-2 gap-4">
<button
class="p-4 bg-gray-700 hover:bg-gray-600 rounded-xl transition-colors text-left"
@click="runSystemAudit"
>
<div class="text-2xl mb-2">🔍</div>
<div class="font-medium">Run System Audit</div>
<div class="text-sm text-gray-400">Check for inconsistencies</div>
</button>
<button
class="p-4 bg-gray-700 hover:bg-gray-600 rounded-xl transition-colors text-left"
@click="clearCache"
>
<div class="text-2xl mb-2">🧹</div>
<div class="font-medium">Clear Cache</div>
<div class="text-sm text-gray-400">Refresh all caches</div>
</button>
<button
class="p-4 bg-gray-700 hover:bg-gray-600 rounded-xl transition-colors text-left"
@click="exportData"
>
<div class="text-2xl mb-2">📤</div>
<div class="font-medium">Export Data</div>
<div class="text-sm text-gray-400">Backup database</div>
</button>
<button
class="p-4 bg-gray-700 hover:bg-gray-600 rounded-xl transition-colors text-left"
@click="showUserImpersonation = true"
>
<div class="text-2xl mb-2">👤</div>
<div class="font-medium">Impersonate User</div>
<div class="text-sm text-gray-400">Test as another user</div>
</button>
</div>
</div>
</div>
</div>
<!-- Add Translation Modal -->
<div v-if="showAddModal" class="fixed inset-0 bg-black/70 flex items-center justify-center z-50">
<div class="bg-gray-800 rounded-xl border border-gray-700 p-6 w-full max-w-md">
<h3 class="text-xl font-bold mb-4">Add Translation Key</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Key</label>
<input
v-model="newKey"
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2"
placeholder="e.g., dashboard.welcome"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Value</label>
<textarea
v-model="newValue"
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2"
rows="3"
placeholder="Translation text"
/>
</div>
<div class="flex justify-end space-x-3">
<button
class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg"
@click="showAddModal = false"
>
Cancel
</button>
<button
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg"
@click="addTranslation"
>
Add Translation
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
// Stats data
const stats = ref({
totalUsers: 1247,
newUsersToday: 12,
activeSessions: 89,
sessionGrowth: 4.2,
vehicleCount: 8563,
pendingVerification: 42,
apiLatency: 42,
apiStatus: 'healthy' as 'healthy' | 'degraded' | 'down'
})
// i18n translations
const languages = ref([
{ code: 'hu', name: 'Hungarian' },
{ code: 'en', name: 'English' },
{ code: 'de', name: 'German' },
{ code: 'fr', name: 'French' }
])
const selectedLanguage = ref('hu')
const translations = ref<Record<string, string>>({
'dashboard.welcome': 'Üdvözöljük az admin felületen',
'dashboard.stats': 'Statisztikák',
'users.total': 'Összes felhasználó',
'vehicles.catalog': 'Jármű katalógus',
'system.health': 'Rendszer állapot',
'navigation.home': 'Kezdőlap',
'navigation.admin': 'Adminisztráció',
'button.save': 'Mentés',
'button.cancel': 'Mégse',
'error.not_found': 'Nem található',
'success.updated': 'Sikeresen frissítve'
})
const translationEdits = ref<Record<string, string>>({})
// Filter translations for selected language (simulated)
const filteredTranslations = computed(() => {
// In a real app, this would fetch from backend API
// For now, we'll just return all translations
return translations.value
})
// Recent activity
const recentActivity = ref([
{
id: 1,
icon: '👤',
title: 'New user registration',
description: 'John Doe registered via email',
time: '5 minutes ago',
status: 'Completed',
statusClass: 'bg-green-900/30 text-green-400'
},
{
id: 2,
icon: '🚗',
title: 'Vehicle catalog update',
description: 'Robot-1-GB added 12 new vehicle models',
time: '15 minutes ago',
status: 'Processing',
statusClass: 'bg-blue-900/30 text-blue-400'
},
{
id: 3,
icon: '🔧',
title: 'System maintenance',
description: 'Database backup completed',
time: '1 hour ago',
status: 'Completed',
statusClass: 'bg-green-900/30 text-green-400'
},
{
id: 4,
icon: '⚠️',
title: 'API rate limit warning',
description: 'DVLA API approaching daily limit',
time: '2 hours ago',
status: 'Warning',
statusClass: 'bg-yellow-900/30 text-yellow-400'
}
])
// Modal state
const showAddModal = ref(false)
const newKey = ref('')
const newValue = ref('')
const showUserImpersonation = ref(false)
// Methods
function refreshTranslations() {
// Simulate API call
console.log('Refreshing translations for', selectedLanguage.value)
// In real app: fetch from /api/v1/translations/hu
}
function saveTranslation(key: string, value: string) {
translations.value[key] = value
console.log('Saving translation:', key, value)
// In real app: POST to backend
}
function deleteTranslation(key: string) {
if (confirm(`Delete translation key "${key}"?`)) {
delete translations.value[key]
console.log('Deleted translation:', key)
}
}
function addTranslation() {
if (newKey.value && newValue.value) {
translations.value[newKey.value] = newValue.value
newKey.value = ''
newValue.value = ''
showAddModal.value = false
}
}
function runSystemAudit() {
alert('System audit started. Check logs for results.')
}
function clearCache() {
alert('Cache cleared successfully.')
}
function exportData() {
alert('Data export initiated. Download will start shortly.')
}
onMounted(() => {
// Initialize translation edits
Object.keys(translations.value).forEach(key => {
translationEdits.value[key] = translations.value[key]
})
})
</script>
<style scoped>
.admin-dashboard {
font-family: 'Inter', sans-serif;
}
</style>