First Commit

This commit is contained in:
marco
2026-09-01 07:49:48 -03:00
commit 28b1044038
399 changed files with 41659 additions and 0 deletions
+397
View File
@@ -0,0 +1,397 @@
@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