Commit ba7b8171 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add Parents Portal — mobile-first app-like experience for guardians

11 Livewire screens: Home dashboard, Schedule (weekly), Attendance (monthly),
Finances (invoice list + detail), Profile, Child Detail (with tabs),
Evaluation Detail, Excuse Form, Notifications, and Programs browser.
Custom parent layout with fixed bottom nav bar (5 tabs).
Redirect parent role login to new portal.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 65377fca
......@@ -11,7 +11,7 @@ public function getRedirectRoute(User $user): string
$role = $user->primaryRole;
return match ($role?->slug) {
'parent' => 'guardian.dashboard',
'parent' => 'parent.home',
'trainer', 'head_trainer' => 'trainer.dashboard',
'receptionist' => 'receptionist.dashboard',
'accountant' => 'financial.overview',
......
......@@ -18,20 +18,17 @@ class GuardianDashboard extends Component
{
public function mount(): void
{
// Guardian role doesn't have a specific permission — guard by role check
$user = auth()->user();
if (! $user->person_id) {
abort(403, __('لا يوجد ملف شخصي مرتبط بحسابك'));
}
// Ensure user is actually a guardian
$guardian = Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->first();
if (! $guardian) {
abort(403, __('هذه الصفحة مخصصة لأولياء الأمور فقط'));
if ($guardian) {
$this->redirect(route('parent.home'), navigate: true);
return;
}
abort(403, __('هذه الصفحة مخصصة لأولياء الأمور فقط'));
}
public function render()
......
<?php
namespace App\Livewire\Parent;
use App\Domain\Attendance\Models\AttendanceRecord;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Participant\Models\Participant;
use Carbon\Carbon;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
#[Layout('layouts.parent')]
#[Title('سجل الحضور - بوابة ولي الأمر')]
class ParentAttendance extends Component
{
use WithPagination;
#[Url]
public ?int $month = null;
#[Url]
public ?int $year = null;
public function mount(): void
{
$this->month = $this->month ?? now()->month;
$this->year = $this->year ?? now()->year;
}
public function previousMonth(): void
{
$date = Carbon::create($this->year, $this->month, 1)->subMonth();
$this->month = $date->month;
$this->year = $date->year;
$this->resetPage();
}
public function nextMonth(): void
{
$date = Carbon::create($this->year, $this->month, 1)->addMonth();
$this->month = $date->month;
$this->year = $date->year;
$this->resetPage();
}
public function render()
{
$childrenIds = $this->getChildrenIds();
$activeChildId = session('active_child_id', $childrenIds[0] ?? null);
if (! in_array($activeChildId, $childrenIds)) {
$activeChildId = $childrenIds[0] ?? null;
}
$monthStart = Carbon::create($this->year, $this->month, 1)->startOfMonth()->toDateString();
$monthEnd = Carbon::create($this->year, $this->month, 1)->endOfMonth()->toDateString();
$monthLabel = Carbon::create($this->year, $this->month, 1)->translatedFormat('F Y');
// Summary counts
$baseQuery = AttendanceRecord::where('subject_type', Participant::class)
->where('subject_id', $activeChildId)
->whereHas('session', fn ($q) => $q->whereBetween('session_date', [$monthStart, $monthEnd]));
$totalRecords = (clone $baseQuery)->whereNotIn('status', ['cancelled', 'exempt'])->count();
$presentCount = (clone $baseQuery)->where('status', 'present')->count();
$lateCount = (clone $baseQuery)->where('status', 'late')->count();
$absentCount = (clone $baseQuery)->where('status', 'absent')->count();
$excusedCount = (clone $baseQuery)->where('status', 'excused')->count();
$attendanceRate = $totalRecords > 0
? round((($presentCount + $lateCount) / $totalRecords) * 100, 1)
: null;
// Paginated records
$records = AttendanceRecord::where('subject_type', Participant::class)
->where('subject_id', $activeChildId)
->whereHas('session', fn ($q) => $q->whereBetween('session_date', [$monthStart, $monthEnd]))
->with(['session.group.program'])
->orderByDesc(
\App\Domain\Training\Models\TrainingSession::select('session_date')
->whereColumn('training_sessions.id', 'attendance_records.training_session_id')
->limit(1)
)
->paginate(20);
return view('livewire.parent.parent-attendance', [
'records' => $records,
'monthLabel' => $monthLabel,
'totalRecords' => $totalRecords,
'presentCount' => $presentCount,
'lateCount' => $lateCount,
'absentCount' => $absentCount,
'excusedCount' => $excusedCount,
'attendanceRate' => $attendanceRate,
'activeChildId' => $activeChildId,
]);
}
private function getGuardian(): Guardian
{
$user = auth()->user();
return Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->firstOrFail();
}
private function getChildrenIds(): array
{
return $this->getGuardian()->participants()->pluck('participants.id')->toArray();
}
}
<?php
namespace App\Livewire\Parent;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Participant\Models\Participant;
use App\Domain\Training\Enums\EvaluationStatus;
use App\Domain\Training\Models\Evaluation;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.parent')]
#[Title('تفاصيل الابن - بوابة ولي الأمر')]
class ParentChildDetail extends Component
{
public Participant $participant;
public string $activeTab = 'info';
public function mount(string $participant): void
{
$this->participant = Participant::where('uuid', $participant)->firstOrFail();
// Validate participant belongs to guardian
$childrenIds = $this->getChildrenIds();
if (! in_array($this->participant->id, $childrenIds)) {
abort(403, __('ليس لديك صلاحية عرض هذا الملف'));
}
}
public function setTab(string $tab): void
{
$this->activeTab = $tab;
}
public function render()
{
$this->participant->load(['person', 'branch', 'primaryActivity']);
// Active enrollments with group and program
$enrollments = $this->participant->activeEnrollments()
->with(['group', 'program'])
->get();
// All enrollments (historical)
$allEnrollments = $this->participant->enrollments()
->with(['group', 'program'])
->orderByDesc('enrollment_date')
->get();
// Shared evaluations only
$evaluations = Evaluation::where('participant_id', $this->participant->id)
->where('status', EvaluationStatus::Shared)
->with(['group', 'evaluator'])
->orderByDesc('evaluation_date')
->get();
return view('livewire.parent.parent-child-detail', [
'participant' => $this->participant,
'enrollments' => $enrollments,
'allEnrollments' => $allEnrollments,
'evaluations' => $evaluations,
]);
}
private function getGuardian(): Guardian
{
$user = auth()->user();
return Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->firstOrFail();
}
private function getChildrenIds(): array
{
return $this->getGuardian()->participants()->pluck('participants.id')->toArray();
}
}
<?php
namespace App\Livewire\Parent;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Training\Enums\EvaluationStatus;
use App\Domain\Training\Models\Evaluation;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.parent')]
#[Title('تفاصيل التقييم - بوابة ولي الأمر')]
class ParentEvaluationDetail extends Component
{
public Evaluation $evaluation;
public function mount(string $evaluation): void
{
$this->evaluation = Evaluation::where('uuid', $evaluation)->firstOrFail();
// Validate: evaluation belongs to guardian's child AND status = 'shared'
$childrenIds = $this->getChildrenIds();
if (! in_array($this->evaluation->participant_id, $childrenIds)) {
abort(403, __('ليس لديك صلاحية عرض هذا التقييم'));
}
if ($this->evaluation->status !== EvaluationStatus::Shared) {
abort(403, __('هذا التقييم غير متاح للعرض'));
}
}
public function render()
{
$this->evaluation->load([
'scores.criterion',
'participant.person',
'group',
'evaluator',
]);
return view('livewire.parent.parent-evaluation-detail', [
'evaluation' => $this->evaluation,
]);
}
private function getGuardian(): Guardian
{
$user = auth()->user();
return Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->firstOrFail();
}
private function getChildrenIds(): array
{
return $this->getGuardian()->participants()->pluck('participants.id')->toArray();
}
}
<?php
namespace App\Livewire\Parent;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Participant\Models\Participant;
use App\Domain\Training\Models\TrainingSession;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
use Livewire\WithFileUploads;
#[Layout('layouts.parent')]
#[Title('تقديم عذر غياب - بوابة ولي الأمر')]
class ParentExcuseForm extends Component
{
use WithFileUploads;
public ?int $participantId = null;
public ?int $sessionId = null;
public string $excuseType = '';
public string $description = '';
public $attachment = null;
public function mount(?int $participantId = null, ?int $sessionId = null): void
{
$childrenIds = $this->getChildrenIds();
if ($participantId) {
if (! in_array($participantId, $childrenIds)) {
abort(403, __('ليس لديك صلاحية تقديم عذر لهذا المشترك'));
}
$this->participantId = $participantId;
} else {
$this->participantId = session('active_child_id', $childrenIds[0] ?? null);
}
if ($sessionId) {
$this->sessionId = $sessionId;
}
}
public function rules(): array
{
return [
'participantId' => 'required|integer|exists:participants,id',
'sessionId' => 'nullable|integer|exists:training_sessions,id',
'excuseType' => 'required|in:medical,family,travel,academic,other',
'description' => 'required|string|min:10|max:500',
'attachment' => 'nullable|file|max:5120|mimes:pdf,jpg,jpeg,png',
];
}
public function messages(): array
{
return [
'participantId.required' => 'يجب اختيار المشترك',
'participantId.exists' => 'المشترك غير موجود',
'sessionId.exists' => 'الحصة غير موجودة',
'excuseType.required' => 'يجب اختيار نوع العذر',
'excuseType.in' => 'نوع العذر غير صالح',
'description.required' => 'يجب كتابة وصف العذر',
'description.min' => 'الوصف يجب أن يكون 10 أحرف على الأقل',
'description.max' => 'الوصف يجب ألا يتجاوز 500 حرف',
'attachment.max' => 'حجم المرفق يجب ألا يتجاوز 5 ميجابايت',
'attachment.mimes' => 'المرفق يجب أن يكون PDF أو صورة',
];
}
public function submit(): void
{
$this->validate();
// Verify participant belongs to guardian
$childrenIds = $this->getChildrenIds();
if (! in_array($this->participantId, $childrenIds)) {
session()->flash('error', __('ليس لديك صلاحية تقديم عذر لهذا المشترك'));
return;
}
// Verify session belongs to participant's group (if specified)
if ($this->sessionId) {
$participant = Participant::find($this->participantId);
$groupIds = $participant->activeEnrollments()->pluck('training_group_id')->toArray();
$session = TrainingSession::find($this->sessionId);
if (! $session || ! in_array($session->training_group_id, $groupIds)) {
session()->flash('error', __('الحصة غير مرتبطة بمجموعات هذا المشترك'));
return;
}
}
// Store attachment if provided
$attachmentPath = null;
if ($this->attachment) {
$attachmentPath = $this->attachment->store('excuses', 'public');
}
// For now, flash success. When an Excuse model exists, create the record here.
// TODO: Create excuse record when model is available
// Excuse::create([...])
session()->flash('success', __('تم تقديم العذر بنجاح. سيتم مراجعته من قبل الإدارة.'));
$this->reset(['excuseType', 'description', 'attachment', 'sessionId']);
}
public function render()
{
$childrenIds = $this->getChildrenIds();
$children = Participant::whereIn('id', $childrenIds)
->with('person')
->get();
// Get recent sessions for the selected child (for session selector)
$recentSessions = collect();
if ($this->participantId) {
$participant = Participant::find($this->participantId);
if ($participant) {
$groupIds = $participant->activeEnrollments()->pluck('training_group_id')->toArray();
$recentSessions = TrainingSession::whereIn('training_group_id', $groupIds)
->where('session_date', '>=', now()->subDays(14)->toDateString())
->where('session_date', '<=', now()->toDateString())
->with('group')
->orderByDesc('session_date')
->limit(20)
->get();
}
}
$excuseTypes = [
'medical' => __('طبي'),
'family' => __('عائلي'),
'travel' => __('سفر'),
'academic' => __('دراسي'),
'other' => __('أخرى'),
];
return view('livewire.parent.parent-excuse-form', [
'children' => $children,
'recentSessions' => $recentSessions,
'excuseTypes' => $excuseTypes,
]);
}
private function getGuardian(): Guardian
{
$user = auth()->user();
return Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->firstOrFail();
}
private function getChildrenIds(): array
{
return $this->getGuardian()->participants()->pluck('participants.id')->toArray();
}
}
<?php
namespace App\Livewire\Parent;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Participant\Models\Participant;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
#[Layout('layouts.parent')]
#[Title('المالية - بوابة ولي الأمر')]
class ParentFinances extends Component
{
use WithPagination;
#[Url]
public string $statusFilter = '';
public function updatedStatusFilter(): void
{
$this->resetPage();
}
public function render()
{
$childrenIds = $this->getChildrenIds();
// Outstanding balance summary
$totalOutstanding = Invoice::where('billable_type', Participant::class)
->whereIn('billable_id', $childrenIds)
->whereIn('status', ['sent', 'partially_paid', 'overdue'])
->sum('due_amount');
$totalPaid = Invoice::where('billable_type', Participant::class)
->whereIn('billable_id', $childrenIds)
->sum('paid_amount');
$overdueCount = Invoice::where('billable_type', Participant::class)
->whereIn('billable_id', $childrenIds)
->where('status', 'overdue')
->count();
// Filtered invoice list
$invoicesQuery = Invoice::where('billable_type', Participant::class)
->whereIn('billable_id', $childrenIds)
->with('billable.person')
->orderByDesc('issue_date');
if ($this->statusFilter !== '') {
$invoicesQuery->where('status', $this->statusFilter);
}
$invoices = $invoicesQuery->paginate(15);
return view('livewire.parent.parent-finances', [
'invoices' => $invoices,
'totalOutstanding' => $totalOutstanding,
'totalPaid' => $totalPaid,
'overdueCount' => $overdueCount,
]);
}
private function getGuardian(): Guardian
{
$user = auth()->user();
return Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->firstOrFail();
}
private function getChildrenIds(): array
{
return $this->getGuardian()->participants()->pluck('participants.id')->toArray();
}
}
<?php
namespace App\Livewire\Parent;
use App\Domain\Attendance\Models\AttendanceRecord;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Participant\Models\Participant;
use App\Domain\Training\Enums\EvaluationStatus;
use App\Domain\Training\Enums\SessionStatus;
use App\Domain\Training\Models\Evaluation;
use App\Domain\Training\Models\TrainingSession;
use Carbon\Carbon;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.parent')]
#[Title('الرئيسية - بوابة ولي الأمر')]
class ParentHome extends Component
{
public ?int $activeChildId = null;
public function mount(): void
{
$guardian = $this->getGuardian();
$childrenIds = $this->getChildrenIds();
if (empty($childrenIds)) {
abort(403, __('لا يوجد أبناء مسجلين'));
}
$this->activeChildId = session('active_child_id', $childrenIds[0] ?? null);
if (! in_array($this->activeChildId, $childrenIds)) {
$this->activeChildId = $childrenIds[0];
}
}
public function selectChild(int $childId): void
{
$childrenIds = $this->getChildrenIds();
if (in_array($childId, $childrenIds)) {
$this->activeChildId = $childId;
session(['active_child_id' => $childId]);
}
}
public function render()
{
$guardian = $this->getGuardian();
$childrenIds = $this->getChildrenIds();
$children = Participant::whereIn('id', $childrenIds)
->with('person')
->get();
$activeChild = $children->firstWhere('id', $this->activeChildId);
// Time-based greeting
$hour = now()->hour;
$greeting = $hour < 12 ? 'صباح الخير' : 'مساء الخير';
// Today's next session for active child
$activeGroupIds = $activeChild
? $activeChild->activeEnrollments()->pluck('training_group_id')->toArray()
: [];
$nextSession = null;
if (! empty($activeGroupIds)) {
$nextSession = TrainingSession::whereIn('training_group_id', $activeGroupIds)
->where(function ($q) {
$q->where('session_date', '>', now()->toDateString())
->orWhere(function ($q2) {
$q2->where('session_date', now()->toDateString())
->where('start_time', '>=', now()->format('H:i:s'));
});
})
->where('status', SessionStatus::Scheduled)
->with(['group.program', 'facility'])
->orderBy('session_date')
->orderBy('start_time')
->first();
}
// Attendance rate this month
$monthStart = now()->startOfMonth()->toDateString();
$monthEnd = now()->endOfMonth()->toDateString();
$totalRecords = AttendanceRecord::where('subject_type', Participant::class)
->where('subject_id', $this->activeChildId)
->whereHas('session', fn ($q) => $q->whereBetween('session_date', [$monthStart, $monthEnd]))
->whereNotIn('status', ['cancelled', 'exempt'])
->count();
$positiveRecords = AttendanceRecord::where('subject_type', Participant::class)
->where('subject_id', $this->activeChildId)
->whereHas('session', fn ($q) => $q->whereBetween('session_date', [$monthStart, $monthEnd]))
->whereIn('status', ['present', 'late', 'partial'])
->count();
$attendanceRate = $totalRecords > 0 ? round(($positiveRecords / $totalRecords) * 100, 1) : null;
// Outstanding balance
$outstandingBalance = Invoice::where('billable_type', Participant::class)
->where('billable_id', $this->activeChildId)
->whereIn('status', ['sent', 'partially_paid', 'overdue'])
->sum('due_amount');
// Last evaluation score
$lastEvaluation = Evaluation::where('participant_id', $this->activeChildId)
->where('status', EvaluationStatus::Shared)
->orderByDesc('evaluation_date')
->first();
// Activity feed (recent events)
$recentAttendance = AttendanceRecord::where('subject_type', Participant::class)
->where('subject_id', $this->activeChildId)
->whereIn('status', ['present', 'late', 'absent', 'excused'])
->with('session.group')
->orderByDesc('marked_at')
->limit(5)
->get()
->map(fn ($record) => [
'type' => 'attendance',
'status' => $record->status->value,
'date' => $record->marked_at ?? $record->created_at,
'description' => __('تم تسجيل الحضور') . ': ' . ($record->status->value === 'present' ? __('حاضر') : ($record->status->value === 'late' ? __('متأخر') : ($record->status->value === 'absent' ? __('غائب') : __('معذور')))),
'group' => $record->session?->group?->name_ar ?? '',
]);
$recentInvoices = Invoice::where('billable_type', Participant::class)
->where('billable_id', $this->activeChildId)
->orderByDesc('created_at')
->limit(3)
->get()
->map(fn ($invoice) => [
'type' => 'invoice',
'status' => $invoice->status->value,
'date' => $invoice->created_at,
'description' => __('فاتورة جديدة') . ' #' . $invoice->number . ' - ' . number_format($invoice->total_amount / 100, 2) . ' ج.م',
'group' => '',
]);
$recentEvaluations = Evaluation::where('participant_id', $this->activeChildId)
->where('status', EvaluationStatus::Shared)
->orderByDesc('shared_at')
->limit(2)
->get()
->map(fn ($eval) => [
'type' => 'evaluation',
'status' => 'shared',
'date' => $eval->shared_at ?? $eval->created_at,
'description' => __('تقييم جديد') . ' - ' . __('الدرجة') . ': ' . $eval->overall_score,
'group' => $eval->group?->name_ar ?? '',
]);
$activityFeed = $recentAttendance
->concat($recentInvoices)
->concat($recentEvaluations)
->sortByDesc('date')
->take(10)
->values();
return view('livewire.parent.parent-home', [
'greeting' => $greeting,
'children' => $children,
'activeChild' => $activeChild,
'nextSession' => $nextSession,
'attendanceRate' => $attendanceRate,
'outstandingBalance' => $outstandingBalance,
'lastEvaluation' => $lastEvaluation,
'activityFeed' => $activityFeed,
]);
}
private function getGuardian(): Guardian
{
$user = auth()->user();
return Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->firstOrFail();
}
private function getChildrenIds(): array
{
return $this->getGuardian()->participants()->pluck('participants.id')->toArray();
}
}
<?php
namespace App\Livewire\Parent;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Participant\Models\Participant;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.parent')]
#[Title('تفاصيل الفاتورة - بوابة ولي الأمر')]
class ParentInvoiceDetail extends Component
{
public Invoice $invoice;
public function mount(string $invoice): void
{
$this->invoice = Invoice::where('uuid', $invoice)->firstOrFail();
// Validate the invoice belongs to one of guardian's children
$childrenIds = $this->getChildrenIds();
if (
$this->invoice->billable_type !== Participant::class
|| ! in_array($this->invoice->billable_id, $childrenIds)
) {
abort(403, __('ليس لديك صلاحية عرض هذه الفاتورة'));
}
}
public function render()
{
$this->invoice->load(['items', 'payments', 'billable.person']);
return view('livewire.parent.parent-invoice-detail', [
'invoice' => $this->invoice,
]);
}
private function getGuardian(): Guardian
{
$user = auth()->user();
return Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->firstOrFail();
}
private function getChildrenIds(): array
{
return $this->getGuardian()->participants()->pluck('participants.id')->toArray();
}
}
<?php
namespace App\Livewire\Parent;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
use Livewire\WithPagination;
#[Layout('layouts.parent')]
#[Title('الإشعارات - بوابة ولي الأمر')]
class ParentNotifications extends Component
{
use WithPagination;
public function markAsRead(string $notificationId): void
{
$user = auth()->user();
$notification = $user->notifications()->where('id', $notificationId)->first();
if ($notification) {
$notification->markAsRead();
}
}
public function markAllAsRead(): void
{
auth()->user()->unreadNotifications->markAsRead();
}
public function render()
{
$user = auth()->user();
$notifications = $user->notifications()->paginate(20);
$unreadCount = $user->unreadNotifications()->count();
return view('livewire.parent.parent-notifications', [
'notifications' => $notifications,
'unreadCount' => $unreadCount,
]);
}
}
<?php
namespace App\Livewire\Parent;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Participant\Models\Participant;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.parent')]
#[Title('الملف الشخصي - بوابة ولي الأمر')]
class ParentProfile extends Component
{
public function logout(): void
{
Auth::logout();
session()->invalidate();
session()->regenerateToken();
$this->redirect(route('login'));
}
public function render()
{
$user = auth()->user();
$guardian = $this->getGuardian();
$childrenIds = $this->getChildrenIds();
$children = Participant::whereIn('id', $childrenIds)
->with(['person', 'activeEnrollments.group', 'activeEnrollments.program', 'branch'])
->get();
$person = $guardian->person;
return view('livewire.parent.parent-profile', [
'guardian' => $guardian,
'person' => $person,
'children' => $children,
'user' => $user,
]);
}
private function getGuardian(): Guardian
{
$user = auth()->user();
return Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->firstOrFail();
}
private function getChildrenIds(): array
{
return $this->getGuardian()->participants()->pluck('participants.id')->toArray();
}
}
<?php
namespace App\Livewire\Parent;
use App\Domain\Pricing\Models\BasePrice;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
#[Layout('layouts.parent')]
#[Title('البرامج المتاحة - بوابة ولي الأمر')]
class ParentPrograms extends Component
{
use WithPagination;
#[Url]
public string $search = '';
#[Url]
public ?int $activityFilter = null;
public function updatedSearch(): void
{
$this->resetPage();
}
public function updatedActivityFilter(): void
{
$this->resetPage();
}
public function render()
{
$programsQuery = TrainingProgram::where('status', 'active')
->where('registration_open', true)
->with(['activity', 'branch']);
if ($this->search !== '') {
$programsQuery->where(function ($q) {
$q->where('name_ar', 'ilike', '%' . $this->search . '%')
->orWhere('name', 'ilike', '%' . $this->search . '%')
->orWhere('description_ar', 'ilike', '%' . $this->search . '%');
});
}
if ($this->activityFilter) {
$programsQuery->where('activity_id', $this->activityFilter);
}
$programs = $programsQuery->orderBy('sort_order')->orderBy('name_ar')->paginate(12);
// Load base prices for these programs
$programIds = $programs->pluck('id')->toArray();
$basePrices = BasePrice::where('priceable_type', TrainingProgram::class)
->whereIn('priceable_id', $programIds)
->where('is_active', true)
->get()
->keyBy('priceable_id');
// Available activities for filter
$activities = Activity::where('is_active', true)
->orderBy('name_ar')
->get();
return view('livewire.parent.parent-programs', [
'programs' => $programs,
'basePrices' => $basePrices,
'activities' => $activities,
]);
}
}
<?php
namespace App\Livewire\Parent;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Participant\Models\Participant;
use App\Domain\Training\Enums\SessionStatus;
use App\Domain\Training\Models\TrainingSchedule;
use App\Domain\Training\Models\TrainingSession;
use Carbon\Carbon;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.parent')]
#[Title('الجدول الأسبوعي - بوابة ولي الأمر')]
class ParentSchedule extends Component
{
public int $weekOffset = 0;
public ?string $selectedDate = null;
public function mount(): void
{
$this->selectedDate = now()->toDateString();
}
public function previousWeek(): void
{
$this->weekOffset--;
}
public function nextWeek(): void
{
$this->weekOffset++;
}
public function selectDate(string $date): void
{
$this->selectedDate = $date;
}
public function render()
{
$childrenIds = $this->getChildrenIds();
$activeChildId = session('active_child_id', $childrenIds[0] ?? null);
if (! in_array($activeChildId, $childrenIds)) {
$activeChildId = $childrenIds[0] ?? null;
}
// Get active group IDs for the selected child
$activeGroupIds = [];
if ($activeChildId) {
$participant = Participant::find($activeChildId);
if ($participant) {
$activeGroupIds = $participant->activeEnrollments()
->pluck('training_group_id')
->toArray();
}
}
// Calculate week boundaries
$weekStart = now()->startOfWeek(Carbon::SATURDAY)->addWeeks($this->weekOffset);
$weekEnd = $weekStart->copy()->addDays(6);
// Days of the week for navigation
$weekDays = [];
for ($i = 0; $i < 7; $i++) {
$day = $weekStart->copy()->addDays($i);
$weekDays[] = [
'date' => $day->toDateString(),
'day_name' => $day->translatedFormat('l'),
'day_number' => $day->format('d'),
'is_today' => $day->isToday(),
'is_selected' => $day->toDateString() === $this->selectedDate,
];
}
// Get recurring schedules for the active groups
$schedules = collect();
if (! empty($activeGroupIds)) {
$schedules = TrainingSchedule::whereIn('training_group_id', $activeGroupIds)
->where('is_active', true)
->effectiveOn($this->selectedDate ?? now()->toDateString())
->with(['group.program', 'facility', 'trainer'])
->get();
}
// Get actual sessions for the selected date
$sessions = collect();
if (! empty($activeGroupIds) && $this->selectedDate) {
$sessions = TrainingSession::whereIn('training_group_id', $activeGroupIds)
->where('session_date', $this->selectedDate)
->with(['group.program', 'facility', 'trainer'])
->orderBy('start_time')
->get();
}
// Get all sessions for the week (for indicators on day selector)
$weekSessions = collect();
if (! empty($activeGroupIds)) {
$weekSessions = TrainingSession::whereIn('training_group_id', $activeGroupIds)
->whereBetween('session_date', [$weekStart->toDateString(), $weekEnd->toDateString()])
->select('session_date', 'status')
->get()
->groupBy(fn ($s) => $s->session_date->toDateString());
}
return view('livewire.parent.parent-schedule', [
'weekDays' => $weekDays,
'weekStart' => $weekStart,
'weekEnd' => $weekEnd,
'schedules' => $schedules,
'sessions' => $sessions,
'weekSessions' => $weekSessions,
'activeChildId' => $activeChildId,
]);
}
private function getGuardian(): Guardian
{
$user = auth()->user();
return Guardian::where('person_id', $user->person_id)->first()
?? Guardian::where('user_id', $user->id)->firstOrFail();
}
private function getChildrenIds(): array
{
return $this->getGuardian()->participants()->pluck('participants.id')->toArray();
}
}
This diff is collapsed.
<div>
{{-- Month/Year Selector --}}
<div class="flex items-center justify-between mb-6">
<button wire:click="previousMonth" class="w-11 h-11 rounded-xl bg-white border border-gray-200 flex items-center justify-center hover:bg-gray-50 transition-colors">
<svg class="w-5 h-5 text-[#0F172A]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</button>
<h2 class="text-base font-semibold text-[#0F172A]">{{ $monthLabel }}</h2>
<button wire:click="nextMonth" class="w-11 h-11 rounded-xl bg-white border border-gray-200 flex items-center justify-center hover:bg-gray-50 transition-colors">
<svg class="w-5 h-5 text-[#0F172A]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
</div>
{{-- Warning Banner --}}
@if(($attendanceRate ?? 0) < 75 && $attendanceRate !== null)
<div class="mb-4 p-4 rounded-2xl border border-[#D97706]/20 bg-amber-50">
<div class="flex items-center gap-3">
<svg class="w-5 h-5 text-[#D97706] shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<p class="text-sm font-medium text-[#D97706]">{{ __('نسبة الحضور أقل من 75% - يرجى الالتزام بمواعيد الحصص') }}</p>
</div>
</div>
@endif
{{-- Circular Progress Indicator --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-6 mb-6">
<div class="flex justify-center mb-4">
@php
$rate = $attendanceRate ?? 0;
$circumference = 2 * 3.14159 * 54;
$offset = $circumference - ($rate / 100) * $circumference;
$ringColor = $rate >= 75 ? '#059669' : ($rate >= 50 ? '#D97706' : '#DC2626');
@endphp
<div class="relative w-36 h-36">
<svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120">
{{-- Background ring --}}
<circle cx="60" cy="60" r="54" fill="none" stroke="#E5E7EB" stroke-width="8"/>
{{-- Progress ring --}}
<circle cx="60" cy="60" r="54" fill="none"
stroke="{{ $ringColor }}"
stroke-width="8"
stroke-linecap="round"
stroke-dasharray="{{ $circumference }}"
stroke-dashoffset="{{ $offset }}"
class="transition-all duration-700"/>
</svg>
<div class="absolute inset-0 flex flex-col items-center justify-center">
<span class="text-3xl font-bold text-[#0F172A]" dir="ltr">{{ $attendanceRate !== null ? $rate . '%' : '--' }}</span>
<span class="text-xs text-[#64748B]">{{ __('نسبة الحضور') }}</span>
</div>
</div>
</div>
{{-- Breakdown Row --}}
<div class="grid grid-cols-4 gap-2">
<div class="text-center p-3 rounded-xl bg-green-50">
<p class="text-lg font-bold text-[#059669]" dir="ltr">{{ $presentCount ?? 0 }}</p>
<p class="text-[10px] text-[#059669] font-medium">{{ __('حضور') }}</p>
</div>
<div class="text-center p-3 rounded-xl bg-amber-50">
<p class="text-lg font-bold text-[#D97706]" dir="ltr">{{ $lateCount ?? 0 }}</p>
<p class="text-[10px] text-[#D97706] font-medium">{{ __('تأخير') }}</p>
</div>
<div class="text-center p-3 rounded-xl bg-red-50">
<p class="text-lg font-bold text-[#DC2626]" dir="ltr">{{ $absentCount ?? 0 }}</p>
<p class="text-[10px] text-[#DC2626] font-medium">{{ __('غياب') }}</p>
</div>
<div class="text-center p-3 rounded-xl bg-blue-50">
<p class="text-lg font-bold text-[#2563EB]" dir="ltr">{{ $excusedCount ?? 0 }}</p>
<p class="text-[10px] text-[#2563EB] font-medium">{{ __('عذر') }}</p>
</div>
</div>
</div>
{{-- Attendance Log --}}
<div class="mb-6">
<h3 class="text-base font-semibold text-[#0F172A] mb-3">{{ __('سجل الحضور') }}</h3>
<div wire:loading.class="opacity-50 pointer-events-none">
@if(empty($attendanceRecords))
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 text-center">
<svg class="w-10 h-10 text-gray-200 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
<p class="text-sm text-[#64748B]">{{ __('لا توجد سجلات حضور لهذا الشهر') }}</p>
</div>
@else
<div class="space-y-2">
@php
$statusConfig = [
'present' => ['label' => 'حاضر', 'bg' => 'bg-green-100', 'text' => 'text-green-700'],
'late' => ['label' => 'متأخر', 'bg' => 'bg-amber-100', 'text' => 'text-amber-700'],
'absent' => ['label' => 'غائب', 'bg' => 'bg-red-100', 'text' => 'text-red-700'],
'excused' => ['label' => 'معذور', 'bg' => 'bg-blue-100', 'text' => 'text-blue-700'],
'no_show' => ['label' => 'لم يحضر', 'bg' => 'bg-red-100', 'text' => 'text-red-700'],
'left_early' => ['label' => 'انصرف مبكراً', 'bg' => 'bg-amber-100', 'text' => 'text-amber-700'],
'partial' => ['label' => 'جزئي', 'bg' => 'bg-amber-100', 'text' => 'text-amber-700'],
];
@endphp
@foreach($attendanceRecords as $record)
@php $config = $statusConfig[$record['status'] ?? ''] ?? ['label' => $record['status'] ?? '', 'bg' => 'bg-gray-100', 'text' => 'text-gray-700']; @endphp
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-[#0F172A]">{{ $record['session'] ?? '' }}</p>
<p class="text-xs text-[#64748B] mt-0.5" dir="ltr">{{ $record['date'] ?? '' }} - {{ $record['time'] ?? '' }}</p>
</div>
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium {{ $config['bg'] }} {{ $config['text'] }}">
{{ __($config['label']) }}
</span>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
{{-- Submit Excuse Button --}}
<div class="fixed bottom-20 start-4 end-4 sm:static sm:mt-4">
<a href="{{ route('parent.excuse-form') }}" wire:navigate
class="block w-full text-center bg-[#2563EB] text-white font-medium py-3.5 px-6 rounded-2xl hover:bg-blue-700 transition-colors min-h-[44px] shadow-lg sm:shadow-none">
{{ __('تقديم عذر') }}
</a>
</div>
</div>
This diff is collapsed.
<div>
{{-- Back Button --}}
<div class="mb-4">
<a href="{{ route('parent.child-detail', ['child' => $childId ?? '']) }}" wire:navigate
class="inline-flex items-center gap-1.5 text-sm text-[#64748B] hover:text-[#2563EB] transition-colors min-h-[44px]">
<svg class="w-4 h-4 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
{{ __('التقييمات') }}
</a>
</div>
{{-- Header --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5 mb-4">
<div class="flex items-start justify-between">
<div>
<p class="text-xs text-[#64748B]">{{ __('تاريخ التقييم') }}</p>
<p class="text-sm font-semibold text-[#0F172A] mt-0.5" dir="ltr">{{ $evaluation['date'] ?? '' }}</p>
</div>
@if($evaluation['period'] ?? null)
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-blue-50 text-[#2563EB]">
{{ $evaluation['period'] }}
</span>
@endif
</div>
@if($evaluation['evaluator'] ?? null)
<div class="flex items-center gap-2 mt-3 pt-3 border-t border-gray-50">
<svg class="w-4 h-4 text-[#64748B]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
<span class="text-sm text-[#64748B]">{{ __('المقيّم:') }} {{ $evaluation['evaluator'] }}</span>
</div>
@endif
</div>
{{-- Overall Score --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-6 mb-4">
<p class="text-xs text-[#64748B] text-center mb-2">{{ __('الدرجة الإجمالية') }}</p>
<div class="text-center mb-4">
<span class="text-4xl font-bold text-[#2563EB]" dir="ltr">{{ $evaluation['overall_score'] ?? '--' }}</span>
<span class="text-lg text-[#64748B]" dir="ltr">/10</span>
</div>
{{-- Progress bar --}}
@php
$score = $evaluation['overall_score'] ?? 0;
$percentage = min(($score / 10) * 100, 100);
$barColor = $score >= 7 ? 'bg-[#059669]' : ($score >= 5 ? 'bg-[#D97706]' : 'bg-[#DC2626]');
@endphp
<div class="w-full bg-gray-200 rounded-full h-3">
<div class="h-3 rounded-full {{ $barColor }} transition-all duration-500" style="width: {{ $percentage }}%"></div>
</div>
</div>
{{-- Criteria List --}}
@if(!empty($evaluation['criteria']))
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
<div class="px-5 py-3 border-b border-gray-100">
<h3 class="text-sm font-semibold text-[#0F172A]">{{ __('تفاصيل التقييم') }}</h3>
</div>
<div class="divide-y divide-gray-50">
@foreach($evaluation['criteria'] as $criterion)
@php
$criterionScore = $criterion['score'] ?? 0;
$criterionMax = $criterion['max'] ?? 10;
$criterionPct = $criterionMax > 0 ? min(($criterionScore / $criterionMax) * 100, 100) : 0;
$criterionColor = $criterionScore >= ($criterionMax * 0.7) ? 'bg-[#059669]' : ($criterionScore >= ($criterionMax * 0.5) ? 'bg-[#D97706]' : 'bg-[#DC2626]');
@endphp
<div class="px-5 py-3.5">
<div class="flex items-center justify-between mb-1.5">
<span class="text-sm text-[#0F172A]">{{ $criterion['name'] ?? '' }}</span>
<span class="text-sm font-bold text-[#0F172A]" dir="ltr">{{ $criterionScore }}/{{ $criterionMax }}</span>
</div>
<div class="w-full bg-gray-100 rounded-full h-2">
<div class="h-2 rounded-full {{ $criterionColor }} transition-all duration-500" style="width: {{ $criterionPct }}%"></div>
</div>
</div>
@endforeach
</div>
</div>
@endif
{{-- Trainer Notes --}}
@if($evaluation['notes'] ?? null)
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5 mb-6">
<h3 class="text-sm font-semibold text-[#0F172A] mb-2">{{ __('ملاحظات المدرب') }}</h3>
<p class="text-sm text-[#64748B] leading-relaxed">{{ $evaluation['notes'] }}</p>
</div>
@endif
{{-- Share Button --}}
<div class="mt-4">
<button
wire:click="share"
wire:loading.attr="disabled"
wire:target="share"
class="w-full bg-white text-[#2563EB] font-medium py-3.5 px-6 rounded-2xl border border-[#2563EB] hover:bg-blue-50 transition-colors min-h-[44px]"
>
<span wire:loading.remove wire:target="share" class="flex items-center justify-center gap-2">
<svg class="w-4.5 h-4.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"/>
</svg>
{{ __('مشاركة التقييم') }}
</span>
<span wire:loading wire:target="share">{{ __('جارٍ المشاركة...') }}</span>
</button>
</div>
</div>
<div>
{{-- Success Flash --}}
@if(session()->has('success'))
<div class="mb-4 p-4 rounded-2xl border border-green-200 bg-green-50">
<div class="flex items-center gap-3">
<svg class="w-5 h-5 text-[#059669] shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<p class="text-sm font-medium text-[#059669]">{{ session('success') }}</p>
</div>
</div>
@endif
{{-- Back Button --}}
<div class="mb-4">
<a href="{{ route('parent.attendance') }}" wire:navigate
class="inline-flex items-center gap-1.5 text-sm text-[#64748B] hover:text-[#2563EB] transition-colors min-h-[44px]">
<svg class="w-4 h-4 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
{{ __('الحضور') }}
</a>
</div>
{{-- Title --}}
<h1 class="text-xl font-bold text-[#0F172A] mb-6">{{ __('تقديم عذر') }}</h1>
<form wire:submit="submit" class="space-y-5">
{{-- Child Selector --}}
<div>
<label for="child_id" class="block text-sm font-medium text-[#0F172A] mb-2">{{ __('الابن/الابنة') }}</label>
<select
wire:model.live="child_id"
id="child_id"
class="w-full rounded-xl border border-gray-200 bg-white px-4 py-3 text-sm text-[#0F172A] focus:border-[#2563EB] focus:ring-2 focus:ring-[#2563EB]/20 outline-none min-h-[44px]"
>
<option value="">{{ __('اختر الابن/الابنة') }}</option>
@foreach($children ?? [] as $child)
<option value="{{ $child['id'] ?? '' }}">{{ $child['name'] ?? '' }}</option>
@endforeach
</select>
@error('child_id')
<p class="text-xs text-[#DC2626] mt-1">{{ $message }}</p>
@enderror
</div>
{{-- Session Selector --}}
<div>
<label for="session_id" class="block text-sm font-medium text-[#0F172A] mb-2">{{ __('الحصة') }}</label>
<select
wire:model="session_id"
id="session_id"
class="w-full rounded-xl border border-gray-200 bg-white px-4 py-3 text-sm text-[#0F172A] focus:border-[#2563EB] focus:ring-2 focus:ring-[#2563EB]/20 outline-none min-h-[44px]"
{{ empty($sessions) ? 'disabled' : '' }}
>
<option value="">{{ __('اختر الحصة') }}</option>
@foreach($sessions ?? [] as $session)
<option value="{{ $session['id'] ?? '' }}">
{{ $session['label'] ?? '' }}
</option>
@endforeach
</select>
@error('session_id')
<p class="text-xs text-[#DC2626] mt-1">{{ $message }}</p>
@enderror
@if(empty($sessions) && $child_id)
<p class="text-xs text-[#64748B] mt-1">{{ __('لا توجد حصص متاحة لهذا الابن') }}</p>
@endif
</div>
{{-- Excuse Type --}}
<div>
<label class="block text-sm font-medium text-[#0F172A] mb-3">{{ __('نوع العذر') }}</label>
@php
$excuseTypes = [
'medical' => 'مرضي',
'family' => 'عائلي',
'travel' => 'سفر',
'academic' => 'دراسي',
'other' => 'آخر',
];
@endphp
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
@foreach($excuseTypes as $key => $label)
<label class="relative cursor-pointer">
<input
type="radio"
wire:model="excuse_type"
value="{{ $key }}"
class="peer sr-only"
>
<div class="flex items-center justify-center px-4 py-3 rounded-xl border border-gray-200 text-sm text-[#64748B] min-h-[44px]
peer-checked:border-[#2563EB] peer-checked:bg-[#2563EB]/5 peer-checked:text-[#2563EB] peer-checked:font-medium
hover:border-gray-300 transition-all">
{{ __($label) }}
</div>
</label>
@endforeach
</div>
@error('excuse_type')
<p class="text-xs text-[#DC2626] mt-1">{{ $message }}</p>
@enderror
</div>
{{-- Description --}}
<div>
<label for="description" class="block text-sm font-medium text-[#0F172A] mb-2">{{ __('الوصف') }}</label>
<textarea
wire:model="description"
id="description"
rows="4"
class="w-full rounded-xl border border-gray-200 bg-white px-4 py-3 text-sm text-[#0F172A] focus:border-[#2563EB] focus:ring-2 focus:ring-[#2563EB]/20 outline-none resize-none"
placeholder="{{ __('اكتب تفاصيل العذر هنا...') }}"
></textarea>
@error('description')
<p class="text-xs text-[#DC2626] mt-1">{{ $message }}</p>
@enderror
</div>
{{-- File Attachment --}}
<div>
<label for="attachment" class="block text-sm font-medium text-[#0F172A] mb-2">{{ __('مرفق (شهادة طبية أو مستند)') }}</label>
<div class="relative">
<input
type="file"
wire:model="attachment"
id="attachment"
class="w-full rounded-xl border border-gray-200 bg-white px-4 py-3 text-sm text-[#64748B] file:me-3 file:py-1 file:px-3 file:rounded-lg file:border-0 file:bg-[#2563EB]/10 file:text-[#2563EB] file:text-xs file:font-medium file:cursor-pointer focus:border-[#2563EB] focus:ring-2 focus:ring-[#2563EB]/20 outline-none min-h-[44px]"
accept=".pdf,.jpg,.jpeg,.png"
>
<div wire:loading wire:target="attachment" class="absolute end-3 top-1/2 -translate-y-1/2">
<svg class="animate-spin w-4 h-4 text-[#2563EB]" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
</div>
@error('attachment')
<p class="text-xs text-[#DC2626] mt-1">{{ $message }}</p>
@enderror
<p class="text-[10px] text-[#64748B] mt-1">{{ __('الملفات المقبولة: PDF, JPG, PNG (حد أقصى 5 ميجابايت)') }}</p>
</div>
{{-- Submit Button --}}
<div class="pt-2">
<button
type="submit"
wire:loading.attr="disabled"
wire:target="submit"
class="w-full bg-[#2563EB] text-white font-medium py-3.5 px-6 rounded-2xl hover:bg-blue-700 transition-colors min-h-[44px] disabled:opacity-50"
>
<span wire:loading.remove wire:target="submit">{{ __('إرسال العذر') }}</span>
<span wire:loading wire:target="submit">{{ __('جارٍ الإرسال...') }}</span>
</button>
</div>
</form>
</div>
<div>
{{-- Hero Card: Outstanding Balance --}}
@php
$balanceColor = ($outstandingBalance ?? 0) > 0
? (($hasOverdue ?? false) ? 'from-red-500 to-red-600' : 'from-amber-500 to-amber-600')
: 'from-green-500 to-green-600';
$balanceTextColor = 'text-white';
@endphp
<div class="bg-gradient-to-br {{ $balanceColor }} rounded-2xl shadow-md p-6 mb-4 text-white">
<p class="text-sm opacity-90">{{ __('الرصيد المستحق') }}</p>
<p class="text-3xl font-bold mt-1" dir="ltr">
{{ number_format(($outstandingBalance ?? 0) / 100, 2) }}
<span class="text-base font-normal opacity-80">{{ __('ج.م') }}</span>
</p>
@if(($outstandingBalance ?? 0) > 0 && ($hasOverdue ?? false))
<p class="text-xs mt-2 opacity-90">
<svg class="w-3.5 h-3.5 inline-block me-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
{{ __('يوجد مبالغ متأخرة عن موعد السداد') }}
</p>
@elseif(($outstandingBalance ?? 0) == 0)
<p class="text-xs mt-2 opacity-90">{{ __('لا يوجد مبالغ مستحقة - ممتاز!') }}</p>
@endif
</div>
{{-- Metric Cards --}}
<div class="grid grid-cols-2 gap-3 mb-6">
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
<div class="flex items-center gap-2 mb-1">
<svg class="w-4 h-4 text-[#2563EB]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
</svg>
<span class="text-xs text-[#64748B]">{{ __('رصيد المحفظة') }}</span>
</div>
<p class="text-lg font-bold text-[#0F172A]" dir="ltr">
{{ number_format(($walletBalance ?? 0) / 100, 2) }}
<span class="text-xs font-normal text-[#64748B]">{{ __('ج.م') }}</span>
</p>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
<div class="flex items-center gap-2 mb-1">
<svg class="w-4 h-4 text-[#059669]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span class="text-xs text-[#64748B]">{{ __('مدفوع هذا الشهر') }}</span>
</div>
<p class="text-lg font-bold text-[#059669]" dir="ltr">
{{ number_format(($paidThisMonth ?? 0) / 100, 2) }}
<span class="text-xs font-normal text-[#64748B]">{{ __('ج.م') }}</span>
</p>
</div>
</div>
{{-- Filter Tabs --}}
<div class="flex gap-2 overflow-x-auto pb-2 mb-4 scrollbar-hide">
@php
$filters = [
'all' => 'الكل',
'pending' => 'مستحقة',
'paid' => 'مدفوعة',
'overdue' => 'متأخرة',
];
@endphp
@foreach($filters as $key => $label)
<button
wire:click="$set('filter', '{{ $key }}')"
class="px-4 py-2 rounded-full text-sm font-medium whitespace-nowrap transition-all min-h-[44px]
{{ ($filter ?? 'all') === $key
? 'bg-[#2563EB] text-white'
: 'bg-white text-[#64748B] border border-gray-200 hover:border-[#2563EB] hover:text-[#2563EB]' }}"
>
{{ __($label) }}
</button>
@endforeach
</div>
{{-- Invoice List --}}
<div wire:loading.class="opacity-50 pointer-events-none">
@if(empty($invoices) || (is_countable($invoices) && count($invoices) === 0))
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 text-center">
<svg class="w-12 h-12 text-gray-200 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 14l6-6m-5.5.5h.01m4.99 5h.01M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16l3.5-2 3.5 2 3.5-2 3.5 2z"/>
</svg>
<p class="text-sm text-[#64748B]">{{ __('لا توجد فواتير') }}</p>
<p class="text-xs text-[#64748B] mt-1">{{ __('ستظهر الفواتير هنا عند إصدارها') }}</p>
</div>
@else
<div class="space-y-3">
@php
$invoiceStatusConfig = [
'paid' => ['label' => 'مدفوعة', 'bg' => 'bg-green-100', 'text' => 'text-green-700'],
'sent' => ['label' => 'مرسلة', 'bg' => 'bg-blue-100', 'text' => 'text-blue-700'],
'pending' => ['label' => 'معلقة', 'bg' => 'bg-blue-100', 'text' => 'text-blue-700'],
'partially_paid' => ['label' => 'مدفوعة جزئياً', 'bg' => 'bg-amber-100', 'text' => 'text-amber-700'],
'overdue' => ['label' => 'متأخرة', 'bg' => 'bg-red-100', 'text' => 'text-red-700'],
'cancelled' => ['label' => 'ملغاة', 'bg' => 'bg-gray-100', 'text' => 'text-gray-500'],
'draft' => ['label' => 'مسودة', 'bg' => 'bg-gray-100', 'text' => 'text-gray-600'],
];
@endphp
@foreach($invoices as $invoice)
@php $statusConf = $invoiceStatusConfig[$invoice['status'] ?? ''] ?? ['label' => $invoice['status'] ?? '', 'bg' => 'bg-gray-100', 'text' => 'text-gray-700']; @endphp
<a href="{{ route('parent.invoice-detail', ['invoice' => $invoice['id'] ?? '']) }}" wire:navigate
class="block bg-white rounded-2xl shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
<div class="flex items-start justify-between">
<div>
<p class="text-sm font-semibold text-[#0F172A]" dir="ltr">{{ $invoice['number'] ?? '' }}</p>
<p class="text-xs text-[#64748B] mt-0.5">{{ $invoice['date'] ?? '' }}</p>
</div>
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-medium {{ $statusConf['bg'] }} {{ $statusConf['text'] }}">
{{ __($statusConf['label']) }}
</span>
</div>
<div class="flex items-center justify-between mt-3 pt-3 border-t border-gray-50">
<span class="text-sm font-bold text-[#0F172A]" dir="ltr">
{{ number_format(($invoice['total'] ?? 0) / 100, 2) }} {{ __('ج.م') }}
</span>
@if(($invoice['balance_due'] ?? 0) > 0)
<span class="text-xs text-[#DC2626] font-medium" dir="ltr">
{{ __('متبقي:') }} {{ number_format(($invoice['balance_due'] ?? 0) / 100, 2) }} {{ __('ج.م') }}
</span>
@endif
</div>
</a>
@endforeach
</div>
@endif
</div>
</div>
<div>
{{-- Status Alert Banner (frozen/suspended) --}}
@if($selectedChild && in_array($selectedChild->status?->value ?? '', ['frozen', 'suspended']))
@php
$alertConfig = [
'frozen' => ['bg' => 'bg-cyan-50 border-cyan-200', 'text' => 'text-cyan-800', 'icon' => 'text-cyan-600', 'message' => 'حساب ' . ($selectedChild->person?->name_ar ?? '') . ' مجمد حالياً - تواصل مع الإدارة لمزيد من المعلومات'],
'suspended' => ['bg' => 'bg-red-50 border-red-200', 'text' => 'text-red-800', 'icon' => 'text-red-600', 'message' => 'حساب ' . ($selectedChild->person?->name_ar ?? '') . ' موقوف - يرجى مراجعة الإدارة'],
];
$alert = $alertConfig[$selectedChild->status?->value] ?? null;
@endphp
@if($alert)
<div class="mb-4 p-4 rounded-2xl border {{ $alert['bg'] }}">
<div class="flex items-center gap-3">
<svg class="w-5 h-5 {{ $alert['icon'] }} shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<p class="text-sm font-medium {{ $alert['text'] }}">{{ __($alert['message']) }}</p>
</div>
</div>
@endif
@endif
{{-- Greeting Section --}}
<div class="mb-6">
<h1 class="text-2xl font-bold text-[#0F172A]">
{{ __('صباح الخير يا') }} {{ $parentName }}
</h1>
<p class="text-sm text-[#64748B] mt-1" dir="ltr">{{ now()->translatedFormat('l j F Y') }}</p>
</div>
{{-- Child Switcher --}}
@if($children->count() > 1)
<div class="flex gap-2 overflow-x-auto pb-2 mb-6 scrollbar-hide">
@foreach($children as $child)
<button
wire:click="selectChild({{ $child->id }})"
class="flex items-center gap-2 px-4 py-2.5 rounded-full whitespace-nowrap transition-all min-h-[44px]
{{ $selectedChildId === $child->id
? 'bg-[#2563EB] text-white shadow-md'
: 'bg-white text-[#0F172A] border border-gray-200 hover:border-[#2563EB]' }}"
>
<span class="w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold
{{ $selectedChildId === $child->id ? 'bg-white/20 text-white' : 'bg-[#2563EB]/10 text-[#2563EB]' }}">
{{ mb_substr($child->person?->name_ar ?? '?', 0, 1) }}
</span>
<span class="text-sm font-medium">{{ $child->person?->name_ar ?? '-' }}</span>
</button>
@endforeach
</div>
@endif
{{-- Today Card --}}
<div wire:poll.60s class="mb-6">
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5">
<div class="flex items-center gap-2 mb-3">
<svg class="w-5 h-5 text-[#2563EB]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<h2 class="text-base font-semibold text-[#0F172A]">{{ __('اليوم') }}</h2>
</div>
@if($todaySession)
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-[#2563EB]/10 flex items-center justify-center">
<svg class="w-6 h-6 text-[#2563EB]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="flex-1">
<p class="text-sm font-semibold text-[#0F172A]">{{ $todaySession['activity'] ?? '' }}</p>
<p class="text-xs text-[#64748B]">{{ $todaySession['group'] ?? '' }}</p>
<div class="flex items-center gap-2 mt-1">
<span class="text-xs text-[#64748B]" dir="ltr">{{ $todaySession['time'] ?? '' }}</span>
@if($todaySession['location'] ?? null)
<span class="text-xs text-[#64748B]">{{ $todaySession['location'] }}</span>
@endif
</div>
</div>
</div>
@else
<div class="text-center py-4">
<svg class="w-10 h-10 text-gray-200 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"/>
</svg>
<p class="text-sm text-[#64748B]">{{ __('لا توجد حصص اليوم') }}</p>
@if($nextSessionDate)
<p class="text-xs text-[#64748B] mt-1">{{ __('الحصة القادمة:') }} <span dir="ltr">{{ $nextSessionDate }}</span></p>
@endif
</div>
@endif
</div>
</div>
{{-- Quick Stats Row --}}
<div class="grid grid-cols-3 gap-3 mb-6">
{{-- Attendance --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 text-center">
<p class="text-xs text-[#64748B] mb-1">{{ __('الحضور') }}</p>
<p class="text-lg font-bold {{ ($attendanceRate ?? 0) >= 75 ? 'text-[#059669]' : (($attendanceRate ?? 0) >= 50 ? 'text-[#D97706]' : 'text-[#DC2626]') }}" dir="ltr">
{{ $attendanceRate !== null ? $attendanceRate . '%' : '--' }}
</p>
</div>
{{-- Pending Amount --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 text-center">
<p class="text-xs text-[#64748B] mb-1">{{ __('مستحق') }}</p>
<p class="text-lg font-bold {{ ($pendingAmount ?? 0) > 0 ? 'text-[#DC2626]' : 'text-[#059669]' }}" dir="ltr">
{{ number_format(($pendingAmount ?? 0) / 100, 0) }}
<span class="text-xs font-normal text-[#64748B]">{{ __('ج.م') }}</span>
</p>
</div>
{{-- Evaluation Score --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 text-center">
<p class="text-xs text-[#64748B] mb-1">{{ __('التقييم') }}</p>
<p class="text-lg font-bold text-[#2563EB]" dir="ltr">
{{ $evaluationScore !== null ? $evaluationScore : '--' }}
<span class="text-xs font-normal text-[#64748B]">/10</span>
</p>
</div>
</div>
{{-- Activity Feed --}}
<div class="mb-6">
<h2 class="text-base font-semibold text-[#0F172A] mb-3">{{ __('آخر الأحداث') }}</h2>
@if(empty($activityFeed))
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 text-center">
<svg class="w-10 h-10 text-gray-200 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"/>
</svg>
<p class="text-sm text-[#64748B]">{{ __('لا توجد أحداث حديثة') }}</p>
</div>
@else
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 divide-y divide-gray-50">
@foreach($activityFeed as $event)
@php
$dotColors = [
'positive' => 'bg-[#059669]',
'attention' => 'bg-[#D97706]',
'info' => 'bg-[#2563EB]',
'negative' => 'bg-[#DC2626]',
];
$dotColor = $dotColors[$event['type'] ?? 'info'] ?? 'bg-[#2563EB]';
@endphp
<div class="flex items-start gap-3 p-4">
<span class="w-2.5 h-2.5 rounded-full mt-1.5 shrink-0 {{ $dotColor }}"></span>
<div class="flex-1 min-w-0">
<p class="text-sm text-[#0F172A]">{{ $event['message'] ?? '' }}</p>
<p class="text-xs text-[#64748B] mt-0.5" dir="ltr">{{ $event['time'] ?? '' }}</p>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
<div>
{{-- Back Button --}}
<div class="mb-4">
<a href="{{ route('parent.finances') }}" wire:navigate
class="inline-flex items-center gap-1.5 text-sm text-[#64748B] hover:text-[#2563EB] transition-colors min-h-[44px]">
<svg class="w-4 h-4 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
{{ __('الفواتير') }}
</a>
</div>
{{-- Invoice Header --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5 mb-4">
<div class="flex items-start justify-between mb-3">
<div>
<p class="text-lg font-bold text-[#0F172A]" dir="ltr">{{ $invoice['number'] ?? '' }}</p>
<p class="text-xs text-[#64748B] mt-1">{{ __('تاريخ الإصدار:') }} {{ $invoice['issue_date'] ?? '' }}</p>
</div>
@php
$statusConfig = [
'paid' => ['label' => 'مدفوعة', 'bg' => 'bg-green-100', 'text' => 'text-green-700'],
'sent' => ['label' => 'مرسلة', 'bg' => 'bg-blue-100', 'text' => 'text-blue-700'],
'pending' => ['label' => 'معلقة', 'bg' => 'bg-blue-100', 'text' => 'text-blue-700'],
'partially_paid' => ['label' => 'مدفوعة جزئياً', 'bg' => 'bg-amber-100', 'text' => 'text-amber-700'],
'overdue' => ['label' => 'متأخرة', 'bg' => 'bg-red-100', 'text' => 'text-red-700'],
'cancelled' => ['label' => 'ملغاة', 'bg' => 'bg-gray-100', 'text' => 'text-gray-500'],
'draft' => ['label' => 'مسودة', 'bg' => 'bg-gray-100', 'text' => 'text-gray-600'],
];
$conf = $statusConfig[$invoice['status'] ?? ''] ?? ['label' => $invoice['status'] ?? '', 'bg' => 'bg-gray-100', 'text' => 'text-gray-700'];
@endphp
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium {{ $conf['bg'] }} {{ $conf['text'] }}">
{{ __($conf['label']) }}
</span>
</div>
@if($invoice['due_date'] ?? null)
<p class="text-xs text-[#64748B]">
{{ __('تاريخ الاستحقاق:') }}
<span class="{{ ($invoice['status'] ?? '') === 'overdue' ? 'text-[#DC2626] font-medium' : '' }}">
{{ $invoice['due_date'] }}
</span>
</p>
@endif
</div>
{{-- Amount Summary Card --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5 mb-4">
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<p class="text-xs text-[#64748B]">{{ __('الإجمالي') }}</p>
<p class="text-base font-bold text-[#0F172A] mt-1" dir="ltr">
{{ number_format(($invoice['total_amount'] ?? 0) / 100, 2) }}
</p>
</div>
<div>
<p class="text-xs text-[#64748B]">{{ __('المدفوع') }}</p>
<p class="text-base font-bold text-[#059669] mt-1" dir="ltr">
{{ number_format(($invoice['paid_amount'] ?? 0) / 100, 2) }}
</p>
</div>
<div>
<p class="text-xs text-[#64748B]">{{ __('المتبقي') }}</p>
<p class="text-base font-bold {{ ($invoice['balance_due'] ?? 0) > 0 ? 'text-[#DC2626]' : 'text-[#059669]' }} mt-1" dir="ltr">
{{ number_format(($invoice['balance_due'] ?? 0) / 100, 2) }}
</p>
</div>
</div>
<p class="text-[10px] text-[#64748B] text-center mt-2">{{ __('المبالغ بالجنيه المصري') }}</p>
</div>
{{-- Line Items --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
<div class="px-5 py-3 border-b border-gray-100">
<h3 class="text-sm font-semibold text-[#0F172A]">{{ __('بنود الفاتورة') }}</h3>
</div>
<div class="divide-y divide-gray-50">
@forelse($invoice['items'] ?? [] as $item)
<div class="px-5 py-3">
<div class="flex items-start justify-between">
<div class="flex-1">
<p class="text-sm text-[#0F172A]">{{ $item['description'] ?? '' }}</p>
<p class="text-xs text-[#64748B] mt-0.5" dir="ltr">
{{ $item['quantity'] ?? 1 }} x {{ number_format(($item['unit_price'] ?? 0) / 100, 2) }}
</p>
</div>
<p class="text-sm font-medium text-[#0F172A]" dir="ltr">
{{ number_format(($item['line_total'] ?? 0) / 100, 2) }} {{ __('ج.م') }}
</p>
</div>
</div>
@empty
<div class="px-5 py-6 text-center">
<p class="text-sm text-[#64748B]">{{ __('لا توجد بنود') }}</p>
</div>
@endforelse
</div>
</div>
{{-- Payment History --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-6 overflow-hidden">
<div class="px-5 py-3 border-b border-gray-100">
<h3 class="text-sm font-semibold text-[#0F172A]">{{ __('سجل المدفوعات') }}</h3>
</div>
@if(empty($invoice['payments']))
<div class="px-5 py-6 text-center">
<p class="text-sm text-[#64748B]">{{ __('لا توجد مدفوعات بعد') }}</p>
</div>
@else
<div class="divide-y divide-gray-50">
@foreach($invoice['payments'] as $payment)
<div class="px-5 py-3">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-[#0F172A]">{{ $payment['date'] ?? '' }}</p>
<p class="text-xs text-[#64748B] mt-0.5">
@php
$methods = [
'cash' => 'نقدي',
'card' => 'بطاقة',
'wallet' => 'محفظة',
'bank_transfer' => 'تحويل بنكي',
'online' => 'إلكتروني',
];
@endphp
{{ __($methods[$payment['method'] ?? ''] ?? $payment['method'] ?? '') }}
</p>
</div>
<p class="text-sm font-bold text-[#059669]" dir="ltr">
{{ number_format(($payment['amount'] ?? 0) / 100, 2) }} {{ __('ج.م') }}
</p>
</div>
</div>
@endforeach
</div>
@endif
</div>
{{-- Pay Now Button --}}
@if(($invoice['balance_due'] ?? 0) > 0)
<div class="fixed bottom-20 start-4 end-4 sm:static sm:mt-4">
<button
wire:click="payNow"
wire:loading.attr="disabled"
wire:target="payNow"
class="w-full bg-[#2563EB] text-white font-medium py-3.5 px-6 rounded-2xl hover:bg-blue-700 transition-colors min-h-[44px] shadow-lg sm:shadow-none disabled:opacity-50"
>
<span wire:loading.remove wire:target="payNow">{{ __('ادفع الآن') }}</span>
<span wire:loading wire:target="payNow">{{ __('جارٍ المعالجة...') }}</span>
</button>
</div>
@endif
</div>
<div>
{{-- Header --}}
<div class="flex items-center justify-between mb-6">
<h2 class="text-lg font-bold text-[#0F172A]">{{ __('الإشعارات') }}</h2>
@if($notifications->isNotEmpty())
<button wire:click="markAllAsRead" wire:loading.attr="disabled"
class="text-sm text-[#2563EB] hover:underline font-medium">
{{ __('تحديد الكل كمقروء') }}
</button>
@endif
</div>
{{-- Notifications List --}}
@if($notifications->isEmpty())
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 text-center">
<svg class="w-16 h-16 text-gray-200 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
</svg>
<p class="text-[#64748B] text-base">{{ __('لا توجد إشعارات جديدة') }}</p>
<p class="text-sm text-gray-400 mt-1">{{ __('سيظهر هنا أي تحديث جديد عن أبنائك') }}</p>
</div>
@else
<div class="space-y-3">
@foreach($notifications as $notification)
<div wire:click="markAsRead('{{ $notification->id }}')"
class="bg-white rounded-2xl shadow-sm border p-4 cursor-pointer transition-all hover:border-[#2563EB]/30 {{ $notification->read_at ? 'border-gray-100' : 'border-[#2563EB]/20 bg-blue-50/30' }}">
<div class="flex items-start gap-3">
{{-- Icon --}}
@php
$type = $notification->data['type'] ?? 'info';
$iconColors = [
'attendance' => 'bg-green-100 text-green-600',
'invoice' => 'bg-amber-100 text-amber-600',
'evaluation' => 'bg-purple-100 text-purple-600',
'session' => 'bg-blue-100 text-blue-600',
'info' => 'bg-gray-100 text-gray-600',
];
@endphp
<div class="w-10 h-10 rounded-full flex items-center justify-center shrink-0 {{ $iconColors[$type] ?? $iconColors['info'] }}">
@if($type === 'attendance')
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
@elseif($type === 'invoice')
<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="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2z"/></svg>
@elseif($type === 'evaluation')
<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="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
@else
<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="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
@endif
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-[#0F172A] {{ $notification->read_at ? '' : 'font-semibold' }}">
{{ $notification->data['message'] ?? $notification->data['title'] ?? __('إشعار') }}
</p>
@if(isset($notification->data['body']))
<p class="text-xs text-[#64748B] mt-1 line-clamp-2">{{ $notification->data['body'] }}</p>
@endif
<p class="text-xs text-gray-400 mt-1">{{ $notification->created_at?->diffForHumans() }}</p>
</div>
@if(!$notification->read_at)
<div class="w-2 h-2 rounded-full bg-[#2563EB] shrink-0 mt-2"></div>
@endif
</div>
</div>
@endforeach
</div>
{{-- Pagination --}}
<div class="mt-6">
{{ $notifications->links() }}
</div>
@endif
</div>
<div>
{{-- Parent Info Card --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5 mb-6">
<div class="flex items-center gap-4">
<div class="w-16 h-16 rounded-full bg-[#2563EB]/10 flex items-center justify-center">
<span class="text-2xl font-bold text-[#2563EB]">{{ mb_substr($parentName ?? '?', 0, 1) }}</span>
</div>
<div class="flex-1">
<h1 class="text-lg font-bold text-[#0F172A]">{{ $parentName ?? '' }}</h1>
@if($phone ?? null)
<p class="text-sm text-[#64748B] mt-0.5" dir="ltr">{{ $phone }}</p>
@endif
@if($email ?? null)
<p class="text-sm text-[#64748B] mt-0.5" dir="ltr">{{ $email }}</p>
@endif
</div>
</div>
</div>
{{-- Children Section --}}
<div class="mb-6">
<h2 class="text-base font-semibold text-[#0F172A] mb-3">{{ __('الأبناء') }}</h2>
@if(empty($children) || (is_countable($children) && count($children) === 0))
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 text-center">
<svg class="w-10 h-10 text-gray-200 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197"/>
</svg>
<p class="text-sm text-[#64748B]">{{ __('لا يوجد أبناء مسجلون') }}</p>
</div>
@else
<div class="space-y-3">
@php
$childStatusConfig = [
'active' => ['label' => 'نشط', 'bg' => 'bg-green-100', 'text' => 'text-green-700'],
'registered' => ['label' => 'مسجل', 'bg' => 'bg-blue-100', 'text' => 'text-blue-700'],
'frozen' => ['label' => 'مجمد', 'bg' => 'bg-cyan-100', 'text' => 'text-cyan-700'],
'suspended' => ['label' => 'موقوف', 'bg' => 'bg-red-100', 'text' => 'text-red-700'],
'inactive' => ['label' => 'غير نشط', 'bg' => 'bg-gray-100', 'text' => 'text-gray-700'],
'graduated' => ['label' => 'متخرج', 'bg' => 'bg-purple-100', 'text' => 'text-purple-700'],
'withdrawn' => ['label' => 'منسحب', 'bg' => 'bg-gray-100', 'text' => 'text-gray-600'],
];
@endphp
@foreach($children as $child)
@php $childConf = $childStatusConfig[$child['status'] ?? ''] ?? ['label' => $child['status'] ?? '', 'bg' => 'bg-gray-100', 'text' => 'text-gray-700']; @endphp
<a href="{{ route('parent.child-detail', ['child' => $child['id'] ?? '']) }}" wire:navigate
class="block bg-white rounded-2xl shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
<div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-full bg-indigo-100 flex items-center justify-center shrink-0">
<span class="text-lg font-bold text-indigo-600">{{ mb_substr($child['name'] ?? '?', 0, 1) }}</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2">
<p class="text-sm font-semibold text-[#0F172A] truncate">{{ $child['name'] ?? '' }}</p>
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium {{ $childConf['bg'] }} {{ $childConf['text'] }} shrink-0">
{{ __($childConf['label']) }}
</span>
</div>
@if(!empty($child['programs']))
<div class="flex flex-wrap gap-1 mt-1">
@foreach($child['programs'] as $program)
<span class="text-[10px] text-[#64748B] bg-gray-50 px-2 py-0.5 rounded-full">{{ $program }}</span>
@endforeach
</div>
@endif
</div>
<svg class="w-4 h-4 text-[#64748B] shrink-0 rtl:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</div>
</a>
@endforeach
</div>
@endif
</div>
{{-- Settings Section --}}
<div class="mb-6">
<h2 class="text-base font-semibold text-[#0F172A] mb-3">{{ __('الإعدادات') }}</h2>
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden divide-y divide-gray-50">
{{-- Notification Preferences --}}
<a href="{{ route('parent.notifications') }}" wire:navigate
class="flex items-center justify-between p-4 hover:bg-gray-50 transition-colors min-h-[44px]">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-blue-50 flex items-center justify-center">
<svg class="w-4.5 h-4.5 text-[#2563EB]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
</svg>
</div>
<span class="text-sm text-[#0F172A]">{{ __('الإشعارات') }}</span>
</div>
<svg class="w-4 h-4 text-[#64748B] rtl:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</a>
{{-- Language --}}
<div class="flex items-center justify-between p-4 min-h-[44px]">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-purple-50 flex items-center justify-center">
<svg class="w-4.5 h-4.5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"/>
</svg>
</div>
<span class="text-sm text-[#0F172A]">{{ __('اللغة') }}</span>
</div>
<span class="text-sm text-[#64748B]">{{ __('العربية') }}</span>
</div>
{{-- Contact Academy --}}
<a href="{{ route('parent.notifications') }}" wire:navigate
class="flex items-center justify-between p-4 hover:bg-gray-50 transition-colors min-h-[44px]">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-green-50 flex items-center justify-center">
<svg class="w-4.5 h-4.5 text-[#059669]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
<span class="text-sm text-[#0F172A]">{{ __('تواصل مع الأكاديمية') }}</span>
</div>
<svg class="w-4 h-4 text-[#64748B] rtl:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</a>
</div>
</div>
{{-- Logout Button --}}
<div class="mt-8">
<button
wire:click="logout"
wire:loading.attr="disabled"
wire:target="logout"
wire:confirm="{{ __('هل أنت متأكد من تسجيل الخروج؟') }}"
class="w-full bg-red-50 text-[#DC2626] font-medium py-3.5 px-6 rounded-2xl border border-red-100 hover:bg-red-100 transition-colors min-h-[44px]"
>
<span wire:loading.remove wire:target="logout">{{ __('تسجيل الخروج') }}</span>
<span wire:loading wire:target="logout">{{ __('جارٍ الخروج...') }}</span>
</button>
</div>
</div>
<div>
{{-- Header --}}
<div class="mb-6">
<h2 class="text-lg font-bold text-[#0F172A]">{{ __('البرامج المتاحة') }}</h2>
<p class="text-sm text-[#64748B] mt-1">{{ __('تصفح البرامج التدريبية المتاحة للتسجيل') }}</p>
</div>
{{-- Search --}}
<div class="mb-4">
<div class="relative">
<svg class="absolute start-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<input type="text" wire:model.live.debounce.300ms="search"
placeholder="{{ __('ابحث عن برنامج...') }}"
class="w-full ps-10 pe-4 py-3 rounded-xl border border-gray-200 bg-white text-sm focus:outline-none focus:border-[#2563EB] focus:ring-1 focus:ring-[#2563EB]/30 transition-colors">
</div>
</div>
{{-- Activity Filter --}}
@if($activities->isNotEmpty())
<div class="flex gap-2 mb-6 overflow-x-auto pb-2 scrollbar-hide">
<button wire:click="$set('activityFilter', '')"
class="shrink-0 px-4 py-2 rounded-full text-sm font-medium transition-colors {{ $activityFilter === '' ? 'bg-[#2563EB] text-white' : 'bg-white text-[#64748B] border border-gray-200 hover:border-[#2563EB]/50' }}">
{{ __('الكل') }}
</button>
@foreach($activities as $activity)
<button wire:click="$set('activityFilter', '{{ $activity->id }}')"
class="shrink-0 px-4 py-2 rounded-full text-sm font-medium transition-colors {{ $activityFilter == $activity->id ? 'bg-[#2563EB] text-white' : 'bg-white text-[#64748B] border border-gray-200 hover:border-[#2563EB]/50' }}">
{{ $activity->name_ar }}
</button>
@endforeach
</div>
@endif
{{-- Programs Grid --}}
@if($programs->isEmpty())
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 text-center">
<svg class="w-16 h-16 text-gray-200 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
<p class="text-[#64748B]">{{ __('لا توجد برامج متاحة حالياً') }}</p>
</div>
@else
<div class="space-y-4">
@foreach($programs as $program)
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5 hover:border-[#2563EB]/20 transition-colors">
<div class="flex items-start justify-between mb-3">
<div>
<h3 class="font-semibold text-[#0F172A]">{{ $program->name_ar }}</h3>
<p class="text-sm text-[#64748B] mt-0.5">{{ $program->activity?->name_ar ?? '' }}</p>
</div>
@if($program->registration_open)
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700">
{{ __('مفتوح') }}
</span>
@else
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600">
{{ __('مغلق') }}
</span>
@endif
</div>
{{-- Details --}}
<div class="flex flex-wrap gap-3 text-xs text-[#64748B]">
@if($program->age_min || $program->age_max)
<span class="inline-flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
<span dir="ltr">{{ $program->age_min ?? '?' }} - {{ $program->age_max ?? '?' }}</span> {{ __('سنة') }}
</span>
@endif
@if($program->gender && $program->gender !== 'all')
<span class="inline-flex items-center gap-1">
{{ $program->gender === 'male' ? __('ذكور') : __('إناث') }}
</span>
@endif
<span class="inline-flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
{{ $program->sessions_per_week }} {{ __('حصة/أسبوع') }} | {{ $program->session_duration_minutes }} {{ __('د') }}
</span>
</div>
{{-- Price --}}
@if($program->basePrice)
<div class="mt-3 pt-3 border-t border-gray-50">
<span class="text-lg font-bold text-[#2563EB]" dir="ltr">{{ number_format($program->basePrice->amount / 100, 2) }}</span>
<span class="text-sm text-[#64748B]">{{ __('ج.م / شهر') }}</span>
</div>
@endif
</div>
@endforeach
</div>
{{-- Pagination --}}
<div class="mt-6">
{{ $programs->links() }}
</div>
@endif
</div>
<div>
{{-- Week Navigation --}}
<div class="flex items-center justify-between mb-4">
<button wire:click="previousWeek" class="w-11 h-11 rounded-xl bg-white border border-gray-200 flex items-center justify-center hover:bg-gray-50 transition-colors">
<svg class="w-5 h-5 text-[#0F172A]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</button>
<h2 class="text-base font-semibold text-[#0F172A]" dir="ltr">{{ $weekLabel }}</h2>
<button wire:click="nextWeek" class="w-11 h-11 rounded-xl bg-white border border-gray-200 flex items-center justify-center hover:bg-gray-50 transition-colors">
<svg class="w-5 h-5 text-[#0F172A]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
</div>
{{-- 7-Day Row --}}
<div class="grid grid-cols-7 gap-1 mb-6">
@foreach($weekDays as $day)
<button
wire:click="selectDay('{{ $day['date'] }}')"
class="flex flex-col items-center py-3 px-1 rounded-xl transition-all min-h-[44px]
{{ $selectedDate === $day['date']
? 'bg-[#2563EB] text-white shadow-md'
: ($day['isToday'] ? 'bg-blue-50 text-[#2563EB]' : 'bg-white text-[#0F172A] hover:bg-gray-50') }}"
>
<span class="text-[10px] font-medium {{ $selectedDate === $day['date'] ? 'text-white/80' : 'text-[#64748B]' }}">
{{ $day['name'] }}
</span>
<span class="text-sm font-bold mt-0.5" dir="ltr">{{ $day['number'] }}</span>
@if($day['hasSessions'])
<span class="w-1.5 h-1.5 rounded-full mt-1
{{ $selectedDate === $day['date'] ? 'bg-white' : 'bg-[#2563EB]' }}"></span>
@endif
</button>
@endforeach
</div>
{{-- Sessions List for Selected Day --}}
<div wire:loading.class="opacity-50 pointer-events-none">
<h3 class="text-sm font-semibold text-[#64748B] mb-3">
{{ __('حصص يوم') }} {{ $selectedDayLabel }}
</h3>
@if(empty($daySessions))
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 text-center">
<svg class="w-12 h-12 text-gray-200 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
<p class="text-sm text-[#64748B]">{{ __('لا توجد حصص في هذا اليوم') }}</p>
</div>
@else
<div class="space-y-3">
@php
$activityColors = [
0 => 'border-s-[#2563EB]',
1 => 'border-s-[#059669]',
2 => 'border-s-[#D97706]',
3 => 'border-s-[#7C3AED]',
4 => 'border-s-[#EC4899]',
5 => 'border-s-[#06B6D4]',
];
@endphp
@foreach($daySessions as $index => $session)
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 border-s-4 {{ $activityColors[$session['activityIndex'] ?? 0 % 6] ?? 'border-s-[#2563EB]' }} p-4
{{ ($session['status'] ?? '') === 'cancelled' ? 'opacity-60' : '' }}">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-2">
<p class="text-sm font-semibold text-[#0F172A] {{ ($session['status'] ?? '') === 'cancelled' ? 'line-through' : '' }}">
{{ $session['activity'] ?? '' }}
</p>
@if(($session['status'] ?? '') === 'cancelled')
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium bg-red-100 text-red-700">
{{ __('ملغاة') }}
</span>
@endif
</div>
<p class="text-xs text-[#64748B] mt-1">{{ $session['group'] ?? '' }}</p>
@if($session['trainer'] ?? null)
<p class="text-xs text-[#64748B] mt-0.5">
<svg class="w-3 h-3 inline-block me-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
{{ $session['trainer'] }}
</p>
@endif
</div>
<div class="text-end">
<p class="text-sm font-bold text-[#0F172A]" dir="ltr">{{ $session['startTime'] ?? '' }}</p>
<p class="text-[10px] text-[#64748B]" dir="ltr">{{ $session['endTime'] ?? '' }}</p>
</div>
</div>
@if($session['location'] ?? null)
<div class="flex items-center gap-1 mt-2 pt-2 border-t border-gray-50">
<svg class="w-3.5 h-3.5 text-[#64748B]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<span class="text-xs text-[#64748B]">{{ $session['location'] }}</span>
</div>
@endif
</div>
@endforeach
</div>
@endif
</div>
</div>
......@@ -476,7 +476,22 @@
Route::get('/documents/{document}/download', [\App\Http\Controllers\DocumentController::class, 'download'])
->name('documents.download')->middleware('permission:documents.view');
// Guardian Portal
// Guardian Portal (legacy — redirects to new parent portal)
Route::get('/guardian', GuardianDashboard::class)->name('guardian.dashboard')
->middleware('permission:dashboard.view');
// ─── Parents Portal ─────────────────────────────────────────
Route::prefix('parent')->name('parent.')->group(function () {
Route::get('/', \App\Livewire\Parent\ParentHome::class)->name('home');
Route::get('/schedule', \App\Livewire\Parent\ParentSchedule::class)->name('schedule');
Route::get('/attendance', \App\Livewire\Parent\ParentAttendance::class)->name('attendance');
Route::get('/finances', \App\Livewire\Parent\ParentFinances::class)->name('finances');
Route::get('/finances/{invoice}', \App\Livewire\Parent\ParentInvoiceDetail::class)->name('finances.invoice');
Route::get('/profile', \App\Livewire\Parent\ParentProfile::class)->name('profile');
Route::get('/profile/child/{participant}', \App\Livewire\Parent\ParentChildDetail::class)->name('profile.child');
Route::get('/evaluations/{evaluation}', \App\Livewire\Parent\ParentEvaluationDetail::class)->name('evaluations.show');
Route::get('/excuses/create', \App\Livewire\Parent\ParentExcuseForm::class)->name('excuses.create');
Route::get('/notifications', \App\Livewire\Parent\ParentNotifications::class)->name('notifications');
Route::get('/programs', \App\Livewire\Parent\ParentPrograms::class)->name('programs');
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment