Commit 4de529eb authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix system coherence: RTL layout, DB column mismatches, permissions, global error handler

- Fix sidebar positioning (end-0 → start-0) so it appears on the RIGHT in RTL
- Fix main content margin (me-64 → ms-64) to offset sidebar correctly
- Fix TrainingProgram queries using non-existent is_active column → status
- Fix User queries using is_active → status
- Fix invoice type 'invoice' → 'standard' to match CHECK constraint
- Fix CollectPaymentWizard using balance_due → due_amount + billable morphs
- Fix notification template Blade parse error (unclosed parenthesis)
- Align 10+ sidebar permission checks with actual route middleware permissions
- Add missing permissions to seeder (pos.sell, inventory.list, reports.view, etc.)
- Add comprehensive global error handler with full stack trace, SQL log, request data
- Add Arabic error pages (500, 403, 404) with detailed debugging info
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 45c59e25
...@@ -137,7 +137,7 @@ public function save(BranchService $branchService): void ...@@ -137,7 +137,7 @@ public function save(BranchService $branchService): void
public function render() public function render()
{ {
return view('livewire.branches.branch-form', [ return view('livewire.branches.branch-form', [
'managers' => User::where('is_active', true)->orderBy('name_ar')->get(['id', 'name', 'name_ar']), 'managers' => User::where('status', 'active')->orderBy('name_ar')->get(['id', 'name', 'name_ar']),
]); ]);
} }
} }
...@@ -45,6 +45,8 @@ class FacilityForm extends Component ...@@ -45,6 +45,8 @@ class FacilityForm extends Component
public function mount(?Facility $facility = null): void public function mount(?Facility $facility = null): void
{ {
$this->authorize($facility?->exists ? 'facilities.update' : 'facilities.create');
if ($facility && $facility->exists) { if ($facility && $facility->exists) {
$this->facility = $facility; $this->facility = $facility;
$this->editing = true; $this->editing = true;
......
...@@ -95,7 +95,7 @@ public function save(InvoiceService $service): void ...@@ -95,7 +95,7 @@ public function save(InvoiceService $service): void
$invoiceData = [ $invoiceData = [
'academy_id' => auth()->user()->academy_id, 'academy_id' => auth()->user()->academy_id,
'number' => $service->generateNumber(auth()->user()->academy_id), 'number' => $service->generateNumber(auth()->user()->academy_id),
'type' => 'invoice', 'type' => 'standard',
'billable_type' => $this->participant_id ? Participant::class : null, 'billable_type' => $this->participant_id ? Participant::class : null,
'billable_id' => $this->participant_id, 'billable_id' => $this->participant_id,
'contact_name' => $this->contact_name, 'contact_name' => $this->contact_name,
......
...@@ -286,7 +286,7 @@ public function newTransaction(): void ...@@ -286,7 +286,7 @@ public function newTransaction(): void
public function render() public function render()
{ {
$programs = TrainingProgram::where('is_active', true)->orderBy('name_ar')->get(); $programs = TrainingProgram::where('status', 'active')->orderBy('name_ar')->get();
return view('livewire.pos.pos-terminal', [ return view('livewire.pos.pos-terminal', [
'programs' => $programs, 'programs' => $programs,
......
...@@ -82,7 +82,7 @@ public function selectInvoice(int $id): void ...@@ -82,7 +82,7 @@ public function selectInvoice(int $id): void
// Pre-fill the full balance due // Pre-fill the full balance due
$invoice = Invoice::find($id); $invoice = Invoice::find($id);
if ($invoice) { if ($invoice) {
$this->payment_amount_display = number_format($invoice->balance_due / 100, 2, '.', ''); $this->payment_amount_display = number_format($invoice->due_amount / 100, 2, '.', '');
} }
} }
...@@ -90,7 +90,7 @@ public function payFullAmount(): void ...@@ -90,7 +90,7 @@ public function payFullAmount(): void
{ {
$invoice = Invoice::find($this->selected_invoice_id); $invoice = Invoice::find($this->selected_invoice_id);
if ($invoice) { if ($invoice) {
$this->payment_amount_display = number_format($invoice->balance_due / 100, 2, '.', ''); $this->payment_amount_display = number_format($invoice->due_amount / 100, 2, '.', '');
} }
} }
...@@ -103,7 +103,7 @@ public function nextStep(): void ...@@ -103,7 +103,7 @@ public function nextStep(): void
$invoice = Invoice::find($this->selected_invoice_id); $invoice = Invoice::find($this->selected_invoice_id);
$amountPiasters = (int) round((float) $this->payment_amount_display * 100); $amountPiasters = (int) round((float) $this->payment_amount_display * 100);
if ($invoice && $amountPiasters > $invoice->balance_due) { if ($invoice && $amountPiasters > $invoice->due_amount) {
$this->addError('payment_amount_display', 'المبلغ أكبر من الرصيد المستحق'); $this->addError('payment_amount_display', 'المبلغ أكبر من الرصيد المستحق');
return; return;
} }
...@@ -132,7 +132,7 @@ public function confirm(PaymentService $service): void ...@@ -132,7 +132,7 @@ public function confirm(PaymentService $service): void
// PaymentService::recordPayment() handles: // PaymentService::recordPayment() handles:
// - Creating payment record // - Creating payment record
// - Double-entry transactions (debit cash/card, credit AR) // - Double-entry transactions (debit cash/card, credit AR)
// - Updating invoice.paid_amount and balance_due // - Updating invoice.paid_amount and due_amount
// - Transitioning invoice status if fully paid // - Transitioning invoice status if fully paid
// - Updating cash session if cash payment // - Updating cash session if cash payment
...@@ -177,13 +177,14 @@ public function render() ...@@ -177,13 +177,14 @@ public function render()
// Outstanding invoices for selected participant // Outstanding invoices for selected participant
$invoices = collect(); $invoices = collect();
if ($this->selected_participant_id) { if ($this->selected_participant_id) {
$invoices = Invoice::where('participant_id', $this->selected_participant_id) $invoices = Invoice::where('billable_type', \App\Domain\Participant\Models\Participant::class)
->where('billable_id', $this->selected_participant_id)
->whereIn('status', [ ->whereIn('status', [
InvoiceStatus::Sent, InvoiceStatus::Sent,
InvoiceStatus::PartiallyPaid, InvoiceStatus::PartiallyPaid,
InvoiceStatus::Overdue, InvoiceStatus::Overdue,
]) ])
->where('balance_due', '>', 0) ->where('due_amount', '>', 0)
->orderByDesc('due_date') ->orderByDesc('due_date')
->get(); ->get();
} }
......
...@@ -155,7 +155,7 @@ public function render() ...@@ -155,7 +155,7 @@ public function render()
->pluck('training_program_id'); ->pluck('training_program_id');
$programs = TrainingProgram::where('activity_id', $this->selected_activity_id) $programs = TrainingProgram::where('activity_id', $this->selected_activity_id)
->where('is_active', true) ->where('status', 'active')
->whereNotIn('id', $enrolledProgramIds) ->whereNotIn('id', $enrolledProgramIds)
->orderBy('name_ar') ->orderBy('name_ar')
->get(); ->get();
......
...@@ -175,7 +175,7 @@ public function render() ...@@ -175,7 +175,7 @@ public function render()
if ($this->selected_activity_id) { if ($this->selected_activity_id) {
$programs = TrainingProgram::where('activity_id', $this->selected_activity_id) $programs = TrainingProgram::where('activity_id', $this->selected_activity_id)
->where('is_active', true) ->where('status', 'active')
->orderBy('name_ar') ->orderBy('name_ar')
->get(); ->get();
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
use App\Domain\Identity\Services\PermissionService; use App\Domain\Identity\Services\PermissionService;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
...@@ -22,6 +23,8 @@ public function register(): void ...@@ -22,6 +23,8 @@ public function register(): void
*/ */
public function boot(): void public function boot(): void
{ {
DB::enableQueryLog();
Gate::before(function ($user, $ability) { Gate::before(function ($user, $ability) {
return app(PermissionService::class)->can($user, $ability) ?: null; return app(PermissionService::class)->can($user, $ability) ?: null;
}); });
......
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
return Application::configure(basePath: dirname(__DIR__)) return Application::configure(basePath: dirname(__DIR__))
->withRouting( ->withRouting(
...@@ -26,6 +30,130 @@ ...@@ -26,6 +30,130 @@
}) })
->withExceptions(function (Exceptions $exceptions): void { ->withExceptions(function (Exceptions $exceptions): void {
$exceptions->shouldRenderJsonWhen( $exceptions->shouldRenderJsonWhen(
fn (Request $request) => $request->is('api/*'), fn (Request $request) => $request->is('api/*') || $request->expectsJson(),
); );
$exceptions->respond(function (\Symfony\Component\HttpFoundation\Response $response, \Throwable $e, Request $request) {
$statusCode = $response->getStatusCode();
if ($request->is('api/*') || $request->expectsJson()) {
$errorId = Str::uuid()->toString();
Log::error('[' . $errorId . '] API Error', [
'error_id' => $errorId,
'exception' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'url' => $request->fullUrl(),
'method' => $request->method(),
'input' => $request->except(['password', 'password_confirmation', '_token']),
'user_id' => $request->user()?->id,
]);
return response()->json([
'error' => true,
'error_id' => $errorId,
'message' => $e->getMessage(),
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->take(20)->map(fn ($frame) => [
'file' => $frame['file'] ?? null,
'line' => $frame['line'] ?? null,
'function' => ($frame['class'] ?? '') . ($frame['type'] ?? '') . ($frame['function'] ?? ''),
])->toArray(),
'url' => $request->fullUrl(),
'method' => $request->method(),
'input' => $request->except(['password', 'password_confirmation', '_token']),
'user_id' => $request->user()?->id,
'timestamp' => now()->toIso8601String(),
], $statusCode >= 400 ? $statusCode : 500);
}
if ($statusCode === 500 || (!$e instanceof HttpExceptionInterface && $statusCode >= 500)) {
$errorId = Str::uuid()->toString();
$queries = [];
try {
$queryLog = DB::getQueryLog();
$queries = collect($queryLog)->takeRight(10)->map(fn ($q) => [
'query' => $q['query'] ?? '',
'time' => $q['time'] ?? null,
])->toArray();
} catch (\Throwable $ignored) {
}
$trace = collect($e->getTrace())->take(30)->map(fn ($frame) => [
'file' => $frame['file'] ?? '(internal)',
'line' => $frame['line'] ?? null,
'class' => $frame['class'] ?? '',
'type' => $frame['type'] ?? '',
'function' => $frame['function'] ?? '',
])->toArray();
$inputData = '';
try {
$filtered = $request->except(['password', 'password_confirmation', '_token']);
$inputData = !empty($filtered) ? json_encode($filtered, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : '';
} catch (\Throwable $ignored) {
}
$headers = '';
try {
$headerBag = $request->headers->all();
unset($headerBag['cookie'], $headerBag['authorization']);
$headers = json_encode($headerBag, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (\Throwable $ignored) {
}
$sessionData = '';
try {
if ($request->hasSession()) {
$sess = $request->session()->all();
unset($sess['_token'], $sess['_previous'], $sess['_flash']);
$sessionData = !empty($sess) ? json_encode($sess, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : '';
}
} catch (\Throwable $ignored) {
}
Log::error('[' . $errorId . '] Unhandled Exception', [
'error_id' => $errorId,
'exception' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'url' => $request->fullUrl(),
'method' => $request->method(),
'route' => $request->route()?->getName(),
'input' => $request->except(['password', 'password_confirmation', '_token']),
'user_id' => $request->user()?->id,
'user_email' => $request->user()?->email,
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'trace' => $e->getTraceAsString(),
]);
return response()->view('errors.500', [
'errorId' => $errorId,
'exceptionClass' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'url' => $request->fullUrl(),
'method' => $request->method(),
'routeName' => $request->route()?->getName() ?? $request->route()?->uri(),
'userName' => $request->user()?->name_ar ?? $request->user()?->name,
'userId' => $request->user()?->id,
'ip' => $request->ip(),
'trace' => $trace,
'inputData' => $inputData,
'headers' => $headers,
'sessionData' => $sessionData,
'queries' => $queries,
], 500);
}
return $response;
});
})->create(); })->create();
...@@ -100,10 +100,34 @@ public function run(): void ...@@ -100,10 +100,34 @@ public function run(): void
'approvals.list', 'approvals.approve', 'approvals.reject', 'approvals.configure', 'approvals.list', 'approvals.approve', 'approvals.reject', 'approvals.configure',
// Settings // Settings
'settings.manage', 'settings.view_audit_log', 'settings.manage', 'settings.view', 'settings.view_audit_log',
// Evaluations // Evaluations
'evaluations.list', 'evaluations.create', 'evaluations.update', 'evaluations.list', 'evaluations.create', 'evaluations.update', 'evaluations.manage',
// Audit
'audit.list', 'audit.export',
// POS (route-aligned aliases)
'pos.sell', 'pos.list',
// Notifications (route-aligned)
'notifications.manage',
// Cash Sessions (route-aligned)
'cash_sessions.manage',
// Wallets (route-aligned)
'wallets.view',
// Inventory (route-aligned)
'inventory.list', 'inventory.create', 'inventory.update',
// Reports (route-aligned)
'reports.view',
// Super Admin
'super_admin.access',
]; ];
// Insert permissions (idempotent) // Insert permissions (idempotent)
......
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
['label' => 'الأنشطة', 'route' => 'activities.list', 'icon' => 'bolt', 'permission' => 'activities.list'], ['label' => 'الأنشطة', 'route' => 'activities.list', 'icon' => 'bolt', 'permission' => 'activities.list'],
['label' => 'البرامج', 'route' => 'programs.list', 'icon' => 'academic-cap', 'permission' => 'programs.list'], ['label' => 'البرامج', 'route' => 'programs.list', 'icon' => 'academic-cap', 'permission' => 'programs.list'],
['label' => 'المجموعات', 'route' => 'groups.list', 'icon' => 'user-group', 'permission' => 'groups.list'], ['label' => 'المجموعات', 'route' => 'groups.list', 'icon' => 'user-group', 'permission' => 'groups.list'],
['label' => 'التسجيلات', 'route' => 'enrollments.list', 'icon' => 'clipboard-check', 'permission' => 'participants.list'], ['label' => 'التسجيلات', 'route' => 'enrollments.list', 'icon' => 'clipboard-check', 'permission' => 'enrollments.list'],
['label' => 'التقييمات', 'route' => 'evaluations.list', 'icon' => 'chart-bar', 'permission' => 'participants.list'], ['label' => 'التقييمات', 'route' => 'evaluations.list', 'icon' => 'chart-bar', 'permission' => 'evaluations.list'],
['label' => 'التعيينات', 'route' => 'assignments.list', 'icon' => 'calendar', 'permission' => 'participants.list'], ['label' => 'التعيينات', 'route' => 'assignments.list', 'icon' => 'calendar', 'permission' => 'assignments.list'],
]], ]],
['section' => 'الحضور', 'items' => [ ['section' => 'الحضور', 'items' => [
...@@ -24,25 +24,25 @@ ...@@ -24,25 +24,25 @@
['section' => 'المالية', 'items' => [ ['section' => 'المالية', 'items' => [
['label' => 'الفواتير', 'route' => 'invoices.list', 'icon' => 'document', 'permission' => 'invoices.list'], ['label' => 'الفواتير', 'route' => 'invoices.list', 'icon' => 'document', 'permission' => 'invoices.list'],
['label' => 'المحافظ', 'route' => 'wallets.list', 'icon' => 'wallet', 'permission' => 'wallets.list'], ['label' => 'المحافظ', 'route' => 'wallets.list', 'icon' => 'wallet', 'permission' => 'wallets.list'],
['label' => 'جلسات الكاشير', 'route' => 'cash-sessions.list', 'icon' => 'calculator', 'permission' => 'invoices.list'], ['label' => 'جلسات الكاشير', 'route' => 'cash-sessions.list', 'icon' => 'calculator', 'permission' => 'cash_sessions.list'],
]], ]],
['section' => 'نقطة البيع', 'items' => [ ['section' => 'نقطة البيع', 'items' => [
['label' => 'نقطة البيع', 'route' => 'pos.terminal', 'icon' => 'shopping-cart', 'permission' => 'pos.access'], ['label' => 'نقطة البيع', 'route' => 'pos.terminal', 'icon' => 'shopping-cart', 'permission' => 'pos.sell'],
['label' => 'سجل المبيعات', 'route' => 'pos.history', 'icon' => 'clock', 'permission' => 'pos.access'], ['label' => 'سجل المبيعات', 'route' => 'pos.history', 'icon' => 'clock', 'permission' => 'pos.list'],
]], ]],
['section' => 'التسعير', 'items' => [ ['section' => 'التسعير', 'items' => [
['label' => 'الأسعار الأساسية', 'route' => 'pricing.base-prices', 'icon' => 'tag', 'permission' => 'pricing_rules.list'], ['label' => 'الأسعار الأساسية', 'route' => 'pricing.base-prices', 'icon' => 'tag', 'permission' => 'pricing.list'],
['label' => 'قواعد التسعير', 'route' => 'pricing.rules', 'icon' => 'adjustments-horizontal', 'permission' => 'pricing_rules.list'], ['label' => 'قواعد التسعير', 'route' => 'pricing.rules', 'icon' => 'adjustments-horizontal', 'permission' => 'pricing.list'],
['label' => 'العروض والكوبونات', 'route' => 'pricing.promotions', 'icon' => 'gift', 'permission' => 'pricing_rules.list'], ['label' => 'العروض والكوبونات', 'route' => 'pricing.promotions', 'icon' => 'gift', 'permission' => 'pricing.list'],
]], ]],
['section' => 'المخزون', 'items' => [ ['section' => 'المخزون', 'items' => [
['label' => 'المنتجات', 'route' => 'inventory.products', 'icon' => 'cube', 'permission' => 'products.list'], ['label' => 'المنتجات', 'route' => 'inventory.products', 'icon' => 'cube', 'permission' => 'inventory.list'],
['label' => 'المستودعات', 'route' => 'inventory.warehouses', 'icon' => 'building-storefront', 'permission' => 'products.list'], ['label' => 'المستودعات', 'route' => 'inventory.warehouses', 'icon' => 'building-storefront', 'permission' => 'inventory.list'],
['label' => 'الحركات', 'route' => 'inventory.movements', 'icon' => 'truck', 'permission' => 'products.list'], ['label' => 'الحركات', 'route' => 'inventory.movements', 'icon' => 'truck', 'permission' => 'inventory.list'],
['label' => 'التسويات', 'route' => 'inventory.adjustments', 'icon' => 'clipboard-document-list', 'permission' => 'products.list'], ['label' => 'التسويات', 'route' => 'inventory.adjustments', 'icon' => 'clipboard-document-list', 'permission' => 'inventory.adjust'],
]], ]],
['section' => 'المنشآت', 'items' => [ ['section' => 'المنشآت', 'items' => [
...@@ -50,17 +50,17 @@ ...@@ -50,17 +50,17 @@
]], ]],
['section' => 'الإشعارات', 'items' => [ ['section' => 'الإشعارات', 'items' => [
['label' => 'مركز الإشعارات', 'route' => 'notifications.center', 'icon' => 'bolt', 'permission' => 'settings.manage'], ['label' => 'مركز الإشعارات', 'route' => 'notifications.center', 'icon' => 'bolt', 'permission' => 'dashboard.view'],
['label' => 'القوالب', 'route' => 'notifications.templates', 'icon' => 'document-text', 'permission' => 'settings.manage'], ['label' => 'القوالب', 'route' => 'notifications.templates', 'icon' => 'document-text', 'permission' => 'notifications.manage'],
['label' => 'سجل الإرسال', 'route' => 'notifications.logs', 'icon' => 'eye', 'permission' => 'settings.manage'], ['label' => 'سجل الإرسال', 'route' => 'notifications.logs', 'icon' => 'eye', 'permission' => 'notifications.manage'],
]], ]],
['section' => 'الإدارة', 'items' => [ ['section' => 'الإدارة', 'items' => [
['label' => 'التقارير', 'route' => 'reports.view', 'icon' => 'chart-bar', 'permission' => 'reports.financial'], ['label' => 'التقارير', 'route' => 'reports.view', 'icon' => 'chart-bar', 'permission' => 'reports.view'],
['label' => 'المستخدمين', 'route' => 'users.list', 'icon' => 'users', 'permission' => 'users.list'], ['label' => 'المستخدمين', 'route' => 'users.list', 'icon' => 'users', 'permission' => 'users.list'],
['label' => 'الأدوار', 'route' => 'roles.list', 'icon' => 'shield-check', 'permission' => 'roles.list'], ['label' => 'الأدوار', 'route' => 'roles.list', 'icon' => 'shield-check', 'permission' => 'roles.list'],
['label' => 'الفروع', 'route' => 'branches.list', 'icon' => 'building-office', 'permission' => 'settings.manage'], ['label' => 'الفروع', 'route' => 'branches.list', 'icon' => 'building-office', 'permission' => 'settings.manage'],
['label' => 'سجل المراجعة', 'route' => 'audit.list', 'icon' => 'eye', 'permission' => 'settings.view_audit_log'], ['label' => 'سجل المراجعة', 'route' => 'audit.list', 'icon' => 'eye', 'permission' => 'audit.list'],
['label' => 'إعدادات الأكاديمية', 'route' => 'settings.academy', 'icon' => 'cog-6-tooth', 'permission' => 'settings.manage'], ['label' => 'إعدادات الأكاديمية', 'route' => 'settings.academy', 'icon' => 'cog-6-tooth', 'permission' => 'settings.manage'],
['label' => 'إعدادات النظام', 'route' => 'settings.system', 'icon' => 'cog-6-tooth', 'permission' => 'settings.manage'], ['label' => 'إعدادات النظام', 'route' => 'settings.system', 'icon' => 'cog-6-tooth', 'permission' => 'settings.manage'],
['label' => 'معالج الإعداد', 'route' => 'setup.wizard', 'icon' => 'bolt', 'permission' => 'settings.manage'], ['label' => 'معالج الإعداد', 'route' => 'setup.wizard', 'icon' => 'bolt', 'permission' => 'settings.manage'],
...@@ -110,7 +110,7 @@ ...@@ -110,7 +110,7 @@
}; };
@endphp @endphp
<aside dir="rtl" class="fixed top-0 end-0 h-screen w-64 bg-slate-900 text-white flex flex-col z-40 overflow-hidden"> <aside dir="rtl" class="fixed top-0 start-0 h-screen w-64 bg-slate-900 text-white flex flex-col z-40 overflow-hidden">
{{-- Logo --}} {{-- Logo --}}
<div class="flex items-center justify-center h-16 border-b border-slate-700 px-4 shrink-0"> <div class="flex items-center justify-center h-16 border-b border-slate-700 px-4 shrink-0">
<h1 class="text-xl font-bold text-white">الكابتن</h1> <h1 class="text-xl font-bold text-white">الكابتن</h1>
......
<!DOCTYPE html>
<html dir="rtl" lang="ar" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>غير مصرح</title>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<style>body { font-family: 'Cairo', sans-serif; }</style>
</head>
<body class="h-full bg-gray-50 flex items-center justify-center">
<div class="text-center p-8">
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-orange-100 mb-6">
<svg class="w-10 h-10 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
</div>
<h1 class="text-4xl font-bold text-gray-800 mb-2">403</h1>
<p class="text-xl text-gray-600 mb-6">غير مصرح لك بالوصول لهذه الصفحة</p>
<div class="flex items-center justify-center gap-4">
<a href="{{ url()->previous() }}" class="inline-flex items-center gap-2 px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 text-sm font-medium">
العودة
</a>
<a href="{{ url('/') }}" class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium">
الصفحة الرئيسية
</a>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html dir="rtl" lang="ar" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>الصفحة غير موجودة</title>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<style>body { font-family: 'Cairo', sans-serif; }</style>
</head>
<body class="h-full bg-gray-50 flex items-center justify-center">
<div class="text-center p-8">
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-blue-100 mb-6">
<svg class="w-10 h-10 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</div>
<h1 class="text-4xl font-bold text-gray-800 mb-2">404</h1>
<p class="text-xl text-gray-600 mb-6">الصفحة المطلوبة غير موجودة</p>
<div class="flex items-center justify-center gap-4">
<a href="{{ url()->previous() }}" class="inline-flex items-center gap-2 px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 text-sm font-medium">
العودة
</a>
<a href="{{ url('/') }}" class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium">
الصفحة الرئيسية
</a>
</div>
</div>
</body>
</html>
This diff is collapsed.
...@@ -27,7 +27,7 @@ class="fixed inset-0 z-30 bg-gray-600/75 lg:hidden" @click="sidebarOpen = false" ...@@ -27,7 +27,7 @@ class="fixed inset-0 z-30 bg-gray-600/75 lg:hidden" @click="sidebarOpen = false"
@include('components.layouts.sidebar') @include('components.layouts.sidebar')
<!-- Main content area --> <!-- Main content area -->
<div class="lg:me-64"> <div class="lg:ms-64">
<!-- Topbar --> <!-- Topbar -->
@include('components.layouts.topbar') @include('components.layouts.topbar')
......
...@@ -63,7 +63,7 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin ...@@ -63,7 +63,7 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
<p class="text-xs font-medium text-blue-700 mb-1">{{ __('المتغيرات المتاحة لهذا الحدث:') }}</p> <p class="text-xs font-medium text-blue-700 mb-1">{{ __('المتغيرات المتاحة لهذا الحدث:') }}</p>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@foreach($this->availableVariables as $var) @foreach($this->availableVariables as $var)
<code class="px-2 py-0.5 bg-blue-100 text-blue-800 rounded text-xs" dir="ltr">@{{ '{{' . $var . '}}' }}</code> <code class="px-2 py-0.5 bg-blue-100 text-blue-800 rounded text-xs" dir="ltr">{{ '{{' . $var . '}}' }}</code>
@endforeach @endforeach
</div> </div>
</div> </div>
......
...@@ -186,7 +186,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-amber-600 text-white ...@@ -186,7 +186,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-amber-600 text-white
</div> </div>
<div class="text-end"> <div class="text-end">
<p class="text-lg font-bold text-gray-800" dir="ltr"> <p class="text-lg font-bold text-gray-800" dir="ltr">
{{ number_format($invoice->balance_due / 100, 2) }} {{ __('ج.م') }} {{ number_format($invoice->due_amount / 100, 2) }} {{ __('ج.م') }}
</p> </p>
@if($invoice->paid_amount > 0) @if($invoice->paid_amount > 0)
<p class="text-xs text-green-600"> <p class="text-xs text-green-600">
...@@ -254,7 +254,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-amber-600 text-white ...@@ -254,7 +254,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-amber-600 text-white
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-gray-600">{{ __('الرصيد المستحق') }}</span> <span class="text-gray-600">{{ __('الرصيد المستحق') }}</span>
<span class="text-xl font-bold text-gray-800" dir="ltr"> <span class="text-xl font-bold text-gray-800" dir="ltr">
{{ number_format($selectedInvoice->balance_due / 100, 2) }} {{ __('ج.م') }} {{ number_format($selectedInvoice->due_amount / 100, 2) }} {{ __('ج.م') }}
</span> </span>
</div> </div>
</div> </div>
......
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