admin frontend elkezdése, járművek tisztázása, frontend fejleszts
This commit is contained in:
@@ -27,15 +27,34 @@
|
||||
<span class="font-medium">Dashboard</span>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
to="/admin/users"
|
||||
<router-link
|
||||
to="/admin/users"
|
||||
class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-gray-700 transition-colors"
|
||||
:class="{ 'bg-blue-900/50 hover:bg-blue-900/70': $route.path.startsWith('/admin/users') }"
|
||||
>
|
||||
<span class="text-xl">👥</span>
|
||||
<span class="font-medium">User Management</span>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
|
||||
<router-link
|
||||
to="/admin/packages"
|
||||
class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-gray-700 transition-colors"
|
||||
:class="{ 'bg-blue-900/50 hover:bg-blue-900/70': $route.path.startsWith('/admin/packages') }"
|
||||
>
|
||||
<span class="text-xl">📦</span>
|
||||
<span class="font-medium">Subscription Packages</span>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
to="/admin/services"
|
||||
class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-gray-700 transition-colors"
|
||||
:class="{ 'bg-blue-900/50 hover:bg-blue-900/70': $route.path.startsWith('/admin/services') }"
|
||||
>
|
||||
<span class="text-xl">🛠️</span>
|
||||
<span class="font-medium">Services</span>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
to="/admin/vehicles"
|
||||
class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
@@ -59,24 +78,23 @@
|
||||
<span class="font-medium">System Settings</span>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
to="/admin/audit"
|
||||
<router-link
|
||||
to="/admin/audit"
|
||||
class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<span class="text-xl">📋</span>
|
||||
<span class="font-medium">Audit Logs</span>
|
||||
</router-link>
|
||||
|
||||
|
||||
<div class="pt-6 border-t border-gray-700">
|
||||
<div class="px-4 py-2 text-xs text-gray-500 uppercase tracking-wider">Tools</div>
|
||||
<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-gray-700 transition-colors">
|
||||
<span class="text-xl">🔍</span>
|
||||
<span class="font-medium">Database Explorer</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-gray-700 transition-colors">
|
||||
<span class="text-xl">📈</span>
|
||||
<span class="font-medium">Analytics</span>
|
||||
</a>
|
||||
<div class="px-4 py-2 text-xs text-gray-500 uppercase tracking-wider">Navigation</div>
|
||||
<router-link
|
||||
to="/dashboard"
|
||||
class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<span class="text-xl">🏠</span>
|
||||
<span class="font-medium">Back to Dashboard</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -84,15 +102,12 @@
|
||||
<div class="p-4 border-t border-gray-700">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-gray-600 to-gray-800 flex items-center justify-center">
|
||||
<span class="text-lg">AD</span>
|
||||
<span class="text-lg">{{ userInitials }}</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">Admin User</div>
|
||||
<div class="text-xs text-gray-400">Super Administrator</div>
|
||||
<div class="font-medium truncate">{{ userName }}</div>
|
||||
<div class="text-xs text-gray-400 capitalize">{{ userRoleLabel }}</div>
|
||||
</div>
|
||||
<button class="p-2 hover:bg-gray-700 rounded-lg transition-colors">
|
||||
<span class="text-xl">⚙️</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,9 +131,8 @@
|
||||
<ModeSwitcher />
|
||||
</slot>
|
||||
|
||||
<button class="px-4 py-2 bg-gradient-to-r from-blue-600 to-indigo-600 text-white rounded-lg hover:opacity-90 transition-opacity">
|
||||
Quick Actions
|
||||
</button>
|
||||
<!-- HeaderProfile komponens: profil, admin center, kilépés -->
|
||||
<HeaderProfile />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -126,7 +140,7 @@
|
||||
<!-- Content Area -->
|
||||
<main class="p-8">
|
||||
<div class="bg-gray-800/50 rounded-xl border border-gray-700 p-6">
|
||||
<slot />
|
||||
<router-view />
|
||||
</div>
|
||||
|
||||
<!-- Footer Stats -->
|
||||
@@ -156,10 +170,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher.vue'
|
||||
import ModeSwitcher from '../components/ModeSwitcher.vue'
|
||||
import HeaderProfile from '../components/header/HeaderProfile.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const currentTime = ref('')
|
||||
|
||||
const pageTitle = computed(() => {
|
||||
@@ -167,9 +184,29 @@ const pageTitle = computed(() => {
|
||||
return name.charAt(0).toUpperCase() + name.slice(1)
|
||||
})
|
||||
|
||||
const userName = computed(() => {
|
||||
if (!authStore.user) return 'Admin User'
|
||||
const { first_name, last_name } = authStore.user
|
||||
if (first_name && last_name) return `${first_name} ${last_name}`
|
||||
return authStore.user.email || 'Admin User'
|
||||
})
|
||||
|
||||
const userInitials = computed(() => {
|
||||
if (!authStore.user) return 'AD'
|
||||
const { first_name, last_name } = authStore.user
|
||||
if (first_name && last_name) {
|
||||
return (first_name[0] + last_name[0]).toUpperCase()
|
||||
}
|
||||
return authStore.user.email?.[0]?.toUpperCase() || 'A'
|
||||
})
|
||||
|
||||
const userRoleLabel = computed(() => {
|
||||
return authStore.user?.role?.replace(/_/g, ' ') || 'Administrator'
|
||||
})
|
||||
|
||||
function updateTime() {
|
||||
const now = new Date()
|
||||
currentTime.value = now.toLocaleTimeString('en-US', {
|
||||
currentTime.value = now.toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
|
||||
@@ -11,6 +11,47 @@
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<!-- Hamburger Menu (Company Settings) -->
|
||||
<div class="relative hamburger-container">
|
||||
<button
|
||||
@click.stop="isHamburgerOpen = !isHamburgerOpen"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-lg text-white/60 hover:text-white hover:bg-white/10 transition-all duration-200 cursor-pointer"
|
||||
:aria-label="t('company.companyDataMenu')"
|
||||
:title="t('company.companyDataMenu')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Hamburger Dropdown -->
|
||||
<Transition
|
||||
enter-active-class="transition duration-150 ease-out"
|
||||
enter-from-class="scale-95 opacity-0 -translate-y-2"
|
||||
enter-to-class="scale-100 opacity-100 translate-y-0"
|
||||
leave-active-class="transition duration-100 ease-in"
|
||||
leave-from-class="scale-100 opacity-100 translate-y-0"
|
||||
leave-to-class="scale-95 opacity-0 -translate-y-2"
|
||||
>
|
||||
<div
|
||||
v-if="isHamburgerOpen"
|
||||
class="absolute right-0 top-full mt-2 w-56 z-50 rounded-xl border border-white/10 bg-[#04151F]/95 backdrop-blur-xl shadow-2xl shadow-black/40 overflow-hidden"
|
||||
>
|
||||
<div class="py-1">
|
||||
<button
|
||||
@click="openCompanyData"
|
||||
class="flex w-full items-center gap-3 px-4 py-2.5 text-sm text-white/80 transition-all duration-150 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
<svg class="w-4 h-4 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
{{ t('company.companyDataMenu') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<HeaderCompanySwitcher />
|
||||
<HeaderProfile />
|
||||
</template>
|
||||
@@ -20,11 +61,18 @@
|
||||
<main class="relative z-10">
|
||||
<router-view />
|
||||
</main>
|
||||
|
||||
<!-- Company Data Modal -->
|
||||
<CompanyDataModal
|
||||
v-if="showCompanyDataModal"
|
||||
@close="showCompanyDataModal = false"
|
||||
@saved="showCompanyDataModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
@@ -33,12 +81,17 @@ import BaseHeader from '../components/layout/BaseHeader.vue'
|
||||
import HeaderLogo from '../components/header/HeaderLogo.vue'
|
||||
import HeaderCompanySwitcher from '../components/header/HeaderCompanySwitcher.vue'
|
||||
import HeaderProfile from '../components/header/HeaderProfile.vue'
|
||||
import CompanyDataModal from '../components/organization/CompanyDataModal.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
const themeStore = useThemeStore()
|
||||
|
||||
// ── Hamburger Menu State ──────────────────────────────────────────
|
||||
const isHamburgerOpen = ref(false)
|
||||
const showCompanyDataModal = ref(false)
|
||||
|
||||
// ── Resolve organization display name from route params ────────────
|
||||
const orgDisplayName = computed(() => {
|
||||
const id = Number(route.params.id)
|
||||
@@ -54,6 +107,20 @@ const currentOrganization = computed(() => {
|
||||
return authStore.myOrganizations.find((o) => o.organization_id === id) || null
|
||||
})
|
||||
|
||||
// ── Hamburger Menu Handlers ───────────────────────────────────────
|
||||
function openCompanyData() {
|
||||
isHamburgerOpen.value = false
|
||||
showCompanyDataModal.value = true
|
||||
}
|
||||
|
||||
// ── Close hamburger on outside click ──────────────────────────────
|
||||
function handleOutsideClick(e: MouseEvent) {
|
||||
const target = e.target as HTMLElement
|
||||
if (isHamburgerOpen.value && !target.closest('.hamburger-container')) {
|
||||
isHamburgerOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Apply theme when organization loads or changes ────────────────
|
||||
function applyOrgTheme() {
|
||||
const org = currentOrganization.value
|
||||
@@ -73,10 +140,12 @@ watch(
|
||||
// Apply theme on mount
|
||||
onMounted(() => {
|
||||
applyOrgTheme()
|
||||
document.addEventListener('mousedown', handleOutsideClick)
|
||||
})
|
||||
|
||||
// Reset theme when leaving the organization view
|
||||
onUnmounted(() => {
|
||||
themeStore.resetTheme()
|
||||
document.removeEventListener('mousedown', handleOutsideClick)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -14,6 +14,71 @@
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<!-- Hamburger Menu (Private Garage Navigation) -->
|
||||
<div class="relative hamburger-container">
|
||||
<button
|
||||
@click.stop="isHamburgerOpen = !isHamburgerOpen"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-lg text-white/60 hover:text-white hover:bg-white/10 transition-all duration-200 cursor-pointer"
|
||||
:aria-label="t('header.menu')"
|
||||
:title="t('header.menu')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Hamburger Dropdown -->
|
||||
<Transition
|
||||
enter-active-class="transition duration-150 ease-out"
|
||||
enter-from-class="scale-95 opacity-0 -translate-y-2"
|
||||
enter-to-class="scale-100 opacity-100 translate-y-0"
|
||||
leave-active-class="transition duration-100 ease-in"
|
||||
leave-from-class="scale-100 opacity-100 translate-y-0"
|
||||
leave-to-class="scale-95 opacity-0 -translate-y-2"
|
||||
>
|
||||
<div
|
||||
v-if="isHamburgerOpen"
|
||||
class="absolute right-0 top-full mt-2 w-56 z-50 rounded-xl border border-white/10 bg-[#04151F]/95 backdrop-blur-xl shadow-2xl shadow-black/40 overflow-hidden"
|
||||
>
|
||||
<div class="py-1">
|
||||
<!-- Járművek -->
|
||||
<button
|
||||
@click="navigateToCard('vehicles')"
|
||||
class="flex w-full items-center gap-3 px-4 py-2.5 text-sm text-white/80 transition-all duration-150 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
<svg class="w-4 h-4 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
{{ t('menu.garage') }}
|
||||
</button>
|
||||
|
||||
<!-- Költségek -->
|
||||
<button
|
||||
@click="navigateToCard('costs')"
|
||||
class="flex w-full items-center gap-3 px-4 py-2.5 text-sm text-white/80 transition-all duration-150 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
<svg class="w-4 h-4 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{{ t('menu.finance') }}
|
||||
</button>
|
||||
|
||||
<!-- Szerviz kereső -->
|
||||
<button
|
||||
@click="navigateToCard('service')"
|
||||
class="flex w-full items-center gap-3 px-4 py-2.5 text-sm text-white/80 transition-all duration-150 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
<svg class="w-4 h-4 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
{{ t('menu.features') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<HeaderCompanySwitcher />
|
||||
<HeaderProfile />
|
||||
</template>
|
||||
@@ -27,7 +92,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import BaseHeader from '../components/layout/BaseHeader.vue'
|
||||
@@ -35,9 +101,14 @@ import HeaderLogo from '../components/header/HeaderLogo.vue'
|
||||
import HeaderCompanySwitcher from '../components/header/HeaderCompanySwitcher.vue'
|
||||
import HeaderProfile from '../components/header/HeaderProfile.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// ── Hamburger Menu State ──────────────────────────────────────────
|
||||
const isHamburgerOpen = ref(false)
|
||||
|
||||
// ── Center label: shows "{firstName}_garage" ───────────────────────
|
||||
const centerLabel = computed(() => {
|
||||
const firstName = authStore.user?.first_name ||
|
||||
@@ -45,4 +116,27 @@ const centerLabel = computed(() => {
|
||||
t('header.user')
|
||||
return t('header.personalGarageLabel', { name: firstName })
|
||||
})
|
||||
|
||||
// ── Navigate to dashboard with card query param ────────────────────
|
||||
function navigateToCard(cardId: string) {
|
||||
isHamburgerOpen.value = false
|
||||
// 'vehicles' opens the flip modal with PrivateVehicleManager
|
||||
// 'costs' and 'service' navigate to dashboard where their cards are already visible inline
|
||||
if (cardId === 'costs' || cardId === 'service') {
|
||||
router.push('/dashboard')
|
||||
} else {
|
||||
router.push({ path: '/dashboard', query: { card: cardId } })
|
||||
}
|
||||
}
|
||||
|
||||
// ── Close hamburger on outside click ──────────────────────────────
|
||||
function handleOutsideClick(e: MouseEvent) {
|
||||
const target = e.target as HTMLElement
|
||||
if (isHamburgerOpen.value && !target.closest('.hamburger-container')) {
|
||||
isHamburgerOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener('mousedown', handleOutsideClick))
|
||||
onUnmounted(() => document.removeEventListener('mousedown', handleOutsideClick))
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user