397 lines
19 KiB
PHP
Executable File
397 lines
19 KiB
PHP
Executable File
@extends('layouts.app')
|
|
|
|
@section('title', __('Dashboard'))
|
|
|
|
@section('header')
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
|
{{ __('Dashboard') }}
|
|
</h2>
|
|
<div class="flex items-center gap-4">
|
|
<x-language-selector variant="text-gray-600 hover:text-gray-800" />
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@section('content')
|
|
<div class="py-6">
|
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
|
<!-- Welcome Message -->
|
|
<div class="bg-gradient-to-r from-indigo-500 to-purple-600 rounded-xl shadow-lg p-6 mb-6 text-white">
|
|
<h1 class="text-2xl font-bold">{{ __('Welcome back, :name!', ['name' => Auth::user()->name]) }}</h1>
|
|
<p class="mt-2 text-indigo-100">{{ __('Here\'s your learning overview for today.') }}</p>
|
|
</div>
|
|
|
|
@if(session('success'))
|
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
|
{{ session('success') }}
|
|
</div>
|
|
@endif
|
|
|
|
@if(session('error'))
|
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
|
{{ session('error') }}
|
|
</div>
|
|
@endif
|
|
|
|
@if(!Auth::user()->is_approved)
|
|
<div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded-lg mb-6">
|
|
{{ __('Your account is pending approval. You will be able to access all features once approved.') }}
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
|
|
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-500">{{ __('Active Subjects') }}</p>
|
|
<p class="text-3xl font-bold text-gray-900 mt-1">{{ $activeSubjectsCount }}</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-2">{{ __('Total: :count subjects', ['count' => $subjectsCount]) }}</p>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-500">{{ __('Total Messages') }}</p>
|
|
<p class="text-3xl font-bold text-gray-900 mt-1">{{ number_format($insightsCount) }}</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-2">{{ __('AI-powered learning messages') }}</p>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-500">{{ __('Token Usage') }}</p>
|
|
<p class="text-3xl font-bold text-gray-900 mt-1">{{ number_format($totalTokens) }}</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-blue-600" 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>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-2">
|
|
{{ __('Input: :input | Output: :output', [
|
|
'input' => number_format($totalInputTokens),
|
|
'output' => number_format($totalOutputTokens),
|
|
]) }}
|
|
</p>
|
|
@php $tokenLimit = Auth::user()->token_limit; @endphp
|
|
@if($tokenLimit)
|
|
<div class="mt-2">
|
|
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
|
<span>{{ __('Limit: :limit', ['limit' => number_format($tokenLimit)]) }}</span>
|
|
<span>{{ round(($totalTokens / $tokenLimit) * 100) }}%</span>
|
|
</div>
|
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
|
<div class="h-2 rounded-full {{ ($totalTokens / $tokenLimit) > 0.9 ? 'bg-red-500' : (($totalTokens / $tokenLimit) > 0.7 ? 'bg-yellow-500' : 'bg-blue-500') }}"
|
|
style="width: {{ min(100, ($totalTokens / $tokenLimit) * 100) }}%">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@else
|
|
<p class="text-xs text-gray-400 mt-2">{{ __('Unlimited') }}</p>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-500">{{ __('Quick Actions') }}</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-purple-600" 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>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2 space-y-1">
|
|
<a href="{{ route('admin.subjects.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800 block">
|
|
{{ __('Manage Subjects') }} →
|
|
</a>
|
|
<a href="{{ route('admin.insights.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800 block">
|
|
{{ __('View Insights') }} →
|
|
</a>
|
|
<a href="{{ route('admin.telegram.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800 block">
|
|
{{ __('Telegram Settings') }} →
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Row -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
<!-- Token Usage Chart -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('Token Usage (Last 30 Days)') }}</h3>
|
|
<div id="tokenChart" style="min-height: 300px;"></div>
|
|
</div>
|
|
|
|
<!-- Messages per Subject Chart -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('Messages per Subject') }}</h3>
|
|
<div id="subjectChart" style="min-height: 300px;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Weekly Activity Chart -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100 mb-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('Weekly Activity') }}</h3>
|
|
<div id="weeklyChart" style="min-height: 200px;"></div>
|
|
</div>
|
|
|
|
<!-- Active Subjects and Recent Insights -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
<!-- Active Subjects -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-900">{{ __('Active Subjects') }}</h3>
|
|
<a href="{{ route('admin.subjects.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800">
|
|
{{ __('Manage') }} →
|
|
</a>
|
|
</div>
|
|
@forelse($activeSubjects as $userSubject)
|
|
<div class="flex items-center justify-between py-3 border-b border-gray-100 last:border-0">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-2 h-2 rounded-full {{ $userSubject->is_active ? 'bg-green-500' : 'bg-gray-300' }}"></div>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-900">{{ $userSubject->subject->name }}</p>
|
|
<p class="text-xs text-gray-500">
|
|
{{ ucfirst($userSubject->frequency) }} |
|
|
{{ $userSubject->formatted_times }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<form action="{{ route('admin.subjects.toggle', $userSubject) }}" method="POST">
|
|
@csrf
|
|
<input type="hidden" name="is_active" value="{{ $userSubject->is_active ? '0' : '1' }}">
|
|
<button type="submit" class="text-xs {{ $userSubject->is_active ? 'text-red-600 hover:text-red-800' : 'text-green-600 hover:text-green-800' }}">
|
|
{{ $userSubject->is_active ? __('Deactivate') : __('Activate') }}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
@empty
|
|
<p class="text-gray-500 text-sm">{{ __('No active subjects. Subscribe to subjects to start learning!') }}</p>
|
|
<a href="{{ route('admin.subjects.index') }}" class="text-indigo-600 hover:text-indigo-800 text-sm mt-2 inline-block">
|
|
{{ __('Browse Subjects') }} →
|
|
</a>
|
|
@endforelse
|
|
</div>
|
|
|
|
<!-- Recent Messages -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-900">{{ __('Recent Messages') }}</h3>
|
|
<a href="{{ route('admin.insights.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800">
|
|
{{ __('View All') }} →
|
|
</a>
|
|
</div>
|
|
<div class="space-y-4 max-h-96 overflow-y-auto">
|
|
@forelse($recentInsights as $insight)
|
|
<div class="border-b border-gray-100 pb-3 last:border-0">
|
|
<div class="flex justify-between items-start">
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full">
|
|
{{ $insight->subject->name }}
|
|
</span>
|
|
<span class="text-xs text-gray-400">
|
|
{{ $insight->type->label() }}
|
|
</span>
|
|
<span class="text-xs text-gray-400">
|
|
{{ $insight->created_at->diffForHumans() }}
|
|
</span>
|
|
</div>
|
|
<div class="text-sm text-gray-700 line-clamp-2 prose prose-sm max-w-none">
|
|
{!! \App\Services\MarkdownService::parse($insight->content) !!}
|
|
</div>
|
|
</div>
|
|
<form action="{{ route('admin.insights.translate', $insight) }}" method="POST" class="flex items-center gap-1 ml-2">
|
|
@csrf
|
|
<select name="target_language" class="text-xs border border-gray-300 rounded-lg px-2 py-1 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
|
<option value="pt_BR" {{ app()->getLocale() === 'pt_BR' ? 'selected' : '' }}>PT</option>
|
|
<option value="en" {{ app()->getLocale() === 'en' ? 'selected' : '' }}>EN</option>
|
|
<option value="es" {{ app()->getLocale() === 'es' ? 'selected' : '' }}>ES</option>
|
|
</select>
|
|
<button type="submit" class="text-xs text-indigo-500 hover:text-indigo-700 font-medium" title="{{ __('Translate') }}">
|
|
{{ __('Translate') }}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<p class="text-gray-500 text-sm">{{ __('No messages yet. Generate your first message!') }}</p>
|
|
<a href="{{ route('admin.insights.index') }}" class="text-indigo-600 hover:text-indigo-800 text-sm mt-2 inline-block">
|
|
{{ __('Generate Message') }} →
|
|
</a>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Token Usage Chart
|
|
var tokenOptions = {
|
|
series: [
|
|
{
|
|
name: '{{ __("Input Tokens") }}',
|
|
data: @json($tokenChartData['input'] ?? [])
|
|
},
|
|
{
|
|
name: '{{ __("Output Tokens") }}',
|
|
data: @json($tokenChartData['output'] ?? [])
|
|
}
|
|
],
|
|
chart: {
|
|
type: 'area',
|
|
height: 300,
|
|
toolbar: { show: false },
|
|
fontFamily: 'Figtree, sans-serif',
|
|
foreColor: '#6B7280'
|
|
},
|
|
colors: ['#6366F1', '#8B5CF6'],
|
|
dataLabels: { enabled: false },
|
|
stroke: { curve: 'smooth', width: 2 },
|
|
fill: {
|
|
type: 'gradient',
|
|
gradient: {
|
|
shadeIntensity: 1,
|
|
opacityFrom: 0.3,
|
|
opacityTo: 0.1
|
|
}
|
|
},
|
|
xaxis: {
|
|
categories: @json($tokenChartData['labels'] ?? []),
|
|
labels: { style: { fontSize: '11px' } }
|
|
},
|
|
yaxis: {
|
|
labels: {
|
|
formatter: function(val) { return val.toLocaleString(); }
|
|
}
|
|
},
|
|
tooltip: {
|
|
y: {
|
|
formatter: function(val) { return val.toLocaleString() + ' tokens'; }
|
|
}
|
|
},
|
|
legend: { position: 'top' },
|
|
grid: { borderColor: '#f1f5f9' }
|
|
};
|
|
|
|
var tokenChart = new ApexCharts(document.querySelector("#tokenChart"), tokenOptions);
|
|
tokenChart.render();
|
|
|
|
// Subject Chart (Donut)
|
|
var subjectOptions = {
|
|
series: @json($subjectChartData['values'] ?? []),
|
|
chart: {
|
|
type: 'donut',
|
|
height: 300,
|
|
fontFamily: 'Figtree, sans-serif',
|
|
foreColor: '#6B7280'
|
|
},
|
|
labels: @json($subjectChartData['labels'] ?? []),
|
|
colors: ['#6366F1', '#8B5CF6', '#EC4899', '#F59E0B', '#10B981', '#3B82F6', '#EF4444'],
|
|
dataLabels: { enabled: false },
|
|
legend: {
|
|
position: 'bottom',
|
|
fontSize: '12px'
|
|
},
|
|
plotOptions: {
|
|
pie: {
|
|
donut: {
|
|
size: '65%',
|
|
labels: {
|
|
show: true,
|
|
total: {
|
|
show: true,
|
|
label: '{{ __("Total") }}',
|
|
formatter: function(w) {
|
|
return w.globals.seriesTotals.reduce((a, b) => a + b, 0).toLocaleString();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
tooltip: {
|
|
y: {
|
|
formatter: function(val) { return val.toLocaleString() + ' messages'; }
|
|
}
|
|
},
|
|
responsive: [{
|
|
breakpoint: 480,
|
|
options: {
|
|
chart: { height: 250 },
|
|
legend: { position: 'bottom' }
|
|
}
|
|
}]
|
|
};
|
|
|
|
var subjectChart = new ApexCharts(document.querySelector("#subjectChart"), subjectOptions);
|
|
subjectChart.render();
|
|
|
|
// Weekly Activity Chart (Bar)
|
|
var weeklyOptions = {
|
|
series: [{
|
|
name: '{{ __("Messages") }}',
|
|
data: @json($weeklyActivity['values'] ?? [])
|
|
}],
|
|
chart: {
|
|
type: 'bar',
|
|
height: 200,
|
|
toolbar: { show: false },
|
|
fontFamily: 'Figtree, sans-serif',
|
|
foreColor: '#6B7280'
|
|
},
|
|
colors: ['#6366F1'],
|
|
plotOptions: {
|
|
bar: {
|
|
borderRadius: 4,
|
|
columnWidth: '60%',
|
|
distributed: true
|
|
}
|
|
},
|
|
dataLabels: { enabled: false },
|
|
xaxis: {
|
|
categories: @json($weeklyActivity['labels'] ?? []),
|
|
labels: { style: { fontSize: '12px' } }
|
|
},
|
|
yaxis: {
|
|
labels: {
|
|
formatter: function(val) { return Math.floor(val).toLocaleString(); }
|
|
}
|
|
},
|
|
grid: { borderColor: '#f1f5f9' },
|
|
tooltip: {
|
|
y: {
|
|
formatter: function(val) { return Math.floor(val).toLocaleString() + ' messages'; }
|
|
}
|
|
}
|
|
};
|
|
|
|
var weeklyChart = new ApexCharts(document.querySelector("#weeklyChart"), weeklyOptions);
|
|
weeklyChart.render();
|
|
});
|
|
</script>
|
|
@endpush |