Zum Hauptinhalt springen

Dashboard System

The Hoffnungsträger Sprachbrücke platform features a role-based dashboard system with different interfaces for interpreters and requesters.

Overview

File: apps/web/app/dashboard/page.tsx

The dashboard provides:

  • ✅ Role-based content (Interpreter vs Requester)
  • ✅ Authentication protection
  • ✅ User profile management
  • ✅ Quick actions and statistics
  • ✅ Responsive design

User Roles

Interpreter Dashboard

Role: interpreter

Features:

  • 📋 Active assignments overview
  • 📅 Calendar integration
  • 💰 Earnings tracking
  • 🔍 Available orders search
  • 👤 Profile management

Quick Stats:

- Active Orders: Number of currently assigned orders
- This Month Earnings: Financial tracking
- Completed Orders: Historical performance
- Average Rating: Client feedback scores

Quick Actions:

  • View Available Orders
  • Update Availability
  • Submit Travel Costs
  • View Earnings Report

Requester Dashboard

Role: requester (client)

Features:

  • 📝 Order request management
  • 📊 Request status tracking
  • 💳 Billing overview
  • 🏢 Organization settings
  • 📋 Order history

Quick Stats:

- Pending Requests: Open order requests
- Active Orders: Currently in progress
- This Month Costs: Budget tracking
- Completed Orders: Service history

Quick Actions:

  • Create New Request
  • View Active Orders
  • Download Invoices
  • Manage Organization

Authentication Flow

Protected Route

'use client'

import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { auth, User } from '@/lib/api'

export default function Dashboard() {
const [user, setUser] = useState<User | null>(null)
const [isLoading, setIsLoading] = useState(true)
const router = useRouter()

useEffect(() => {
const checkAuth = async () => {
try {
if (!auth.isAuthenticated()) {
router.push('/login')
return
}

const profile = await auth.getProfile()
setUser(profile)
} catch (error) {
console.error('Authentication failed:', error)
router.push('/login')
} finally {
setIsLoading(false)
}
}

checkAuth()
}, [router])

if (isLoading) {
return <LoadingSpinner />
}

if (!user) {
return null // Redirecting...
}

// Role-based rendering
const isInterpreter = user.roles?.some(role => role.name === 'interpreter')

return isInterpreter ? (
<InterpreterDashboard user={user} />
) : (
<RequesterDashboard user={user} />
)
}

Role Detection

User Roles Array

Laravel uses Spatie roles/permissions. Users can have multiple roles:

interface User {
roles: Array<{
id: number
name: string // 'interpreter', 'requester', 'admin'
guardName: string
createdAt: string
updatedAt: string
}>
}

// Role checking utility
const hasRole = (user: User, roleName: string): boolean => {
return user.roles?.some(role => role.name === roleName) ?? false
}

// Usage
const isInterpreter = hasRole(user, 'interpreter')
const isRequester = hasRole(user, 'requester')
const isAdmin = hasRole(user, 'admin')

Dashboard Components

Header Integration

File: apps/web/components/header.tsx

// Dynamic header based on authentication
{user ? (
<div className="flex items-center space-x-4">
<span className="text-gray-700">
Willkommen, {user.firstName}!
</span>
<Button
variant="outline"
onClick={() => router.push('/dashboard')}
>
Dashboard
</Button>
<Button
variant="outline"
onClick={handleLogout}
>
Abmelden
</Button>
</div>
) : (
// Login/Register buttons
)}

Responsive Layout

// Mobile-first responsive design
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Welcome Section */}
<div className="lg:col-span-3">
<WelcomeCard user={user} />
</div>

{/* Quick Stats */}
<div className="lg:col-span-2">
<StatsGrid stats={roleBasedStats} />
</div>

{/* Quick Actions */}
<div className="lg:col-span-1">
<QuickActionsCard actions={roleBasedActions} />
</div>

{/* Main Content */}
<div className="lg:col-span-3">
{isInterpreter ? (
<InterpreterContent />
) : (
<RequesterContent />
)}
</div>
</div>
</div>
</div>

Data Management

State Management

const [stats, setStats] = useState({
activeOrders: 0,
earnings: 0,
completedOrders: 0,
rating: 0
})

const [recentActivity, setRecentActivity] = useState([])
const [quickActions, setQuickActions] = useState([])

// Load dashboard data
useEffect(() => {
const loadDashboardData = async () => {
try {
// Parallel API calls for performance
const [statsData, activityData] = await Promise.all([
fetchUserStats(user.id),
fetchRecentActivity(user.id)
])

setStats(statsData)
setRecentActivity(activityData)
} catch (error) {
console.error('Failed to load dashboard data:', error)
}
}

if (user) {
loadDashboardData()
}
}, [user])

API Integration

// Dashboard-specific API calls
const fetchUserStats = async (userId: string) => {
const response = await fetch(`/api/users/${userId}/stats`, {
headers: {
'Authorization': `Bearer ${auth.getToken()}`
}
})
return response.json()
}

const fetchRecentActivity = async (userId: string) => {
const response = await fetch(`/api/users/${userId}/activity`, {
headers: {
'Authorization': `Bearer ${auth.getToken()}`
}
})
return response.json()
}

UI Components

Stats Cards

interface StatCard {
title: string
value: string | number
change?: string
trend?: 'up' | 'down' | 'neutral'
icon: React.ComponentType
}

const StatsGrid = ({ stats }: { stats: StatCard[] }) => (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{stats.map((stat, index) => (
<Card key={index} className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
{stat.title}
</p>
<p className="text-2xl font-bold text-gray-900">
{stat.value}
</p>
{stat.change && (
<p className={`text-sm ${
stat.trend === 'up' ? 'text-green-600' :
stat.trend === 'down' ? 'text-red-600' :
'text-gray-600'
}`}>
{stat.change}
</p>
)}
</div>
<stat.icon className="h-8 w-8 text-gray-400" />
</div>
</Card>
))}
</div>
)

Quick Actions

interface QuickAction {
title: string
description: string
href?: string
onClick?: () => void
icon: React.ComponentType
variant: 'primary' | 'secondary' | 'outline'
}

const QuickActionsCard = ({ actions }: { actions: QuickAction[] }) => (
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Schnellaktionen
</h3>
<div className="space-y-3">
{actions.map((action, index) => (
<Button
key={index}
variant={action.variant}
className="w-full justify-start"
onClick={action.onClick}
>
<action.icon className="mr-2 h-4 w-4" />
{action.title}
</Button>
))}
</div>
</Card>
)
const DashboardBreadcrumbs = ({ user }: { user: User }) => {
const role = user.roles?.[0]?.name || 'user'

return (
<nav className="flex mb-6" aria-label="Breadcrumb">
<ol className="flex items-center space-x-2">
<li>
<Link href="/" className="text-gray-500 hover:text-gray-700">
Home
</Link>
</li>
<li>
<span className="text-gray-400">/</span>
</li>
<li>
<span className="text-gray-900 font-medium">
{role === 'interpreter' ? 'Sprachmittler' : 'Auftraggeber'} Dashboard
</span>
</li>
</ol>
</nav>
)
}

Future Enhancements

  • 📱 Progressive Web App: Mobile app-like experience
  • 🔔 Real-time Notifications: WebSocket integration
  • 📊 Advanced Analytics: Charts and reporting
  • 🌙 Dark Mode: Theme switching
  • 🌍 Multi-language: i18n support
  • 🔒 Two-Factor Auth: Enhanced security

Security Considerations

  1. Route Protection: All dashboard routes require authentication
  2. Role Validation: Server-side role checking
  3. Token Refresh: Automatic token renewal
  4. CSRF Protection: Laravel Sanctum integration
  5. XSS Prevention: Input sanitization