From 91984dbde31f8bf424bbd04379a9fb95fb807d97 Mon Sep 17 00:00:00 2001 From: Kazem Alghasi Date: Sat, 3 Oct 2026 11:08:50 +0330 Subject: [PATCH] feat(ui): redesign admin dashboard with layered grid and custom branding Implement a comprehensive overhaul of the Filament admin dashboard, introducing a structured 12-column responsive grid layout and enhanced visual identity. Key changes include: - Redesigned Dashboard layout with a 4-layer progressive disclosure architecture (KPIs, Exchange Rates, Data Tables, and Trends). - Introduced custom IFNEX design tokens via CSS, including branded stat cards, status badges, and improved sidebar typography. - Enhanced FinanceOverviewWidget with real-time trend analysis and sparkline-ready data structures. - Refactored RecentShipments and RecentTransactions widgets to use custom Blade templates for a more polished, consistent UI. - Integrated brand assets including the company logo and custom authentication pages. - Added global number formatting helpers and integrated them into financial widgets. - Updated AdminPanelProvider to support custom login pages and brand-specific header/footer hooks. --- 04_Laravel/app/Filament/Pages/Auth/Login.php | 65 + 04_Laravel/app/Filament/Pages/Dashboard.php | 55 +- .../Pages/DashboardWidgetRedesign.php | 28 - .../Filament/Widgets/DashboardInfoWidget.php | 2 +- .../Filament/Widgets/ExchangeRateWidget.php | 53 +- .../Widgets/FinanceOverviewWidget.php | 168 +- .../Widgets/RecentShipmentsWidget.php | 59 +- .../Widgets/RecentTransactionsWidget.php | 119 +- .../Widgets/TransactionChartWidget.php | 64 +- 04_Laravel/app/Helpers/number.php | 146 + .../app/Providers/AppServiceProvider.php | 8 + .../Providers/Filament/AdminPanelProvider.php | 20 +- 04_Laravel/composer.json | 7 +- 04_Laravel/public/images/ifnex-logo.png | Bin 0 -> 10465 bytes .../views/filament/hooks/head.blade.php | 210 +- .../views/filament/pages/auth/login.blade.php | 352 ++ .../pages/dashboard-widget-redesign.blade.php | 52 - .../widgets/recent-shipments.blade.php | 75 +- .../widgets/recent-transactions.blade.php | 71 + 04_Laravel/storage/panel-dump.html | 3619 +++++++++++++++++ 04_Laravel/storage/panel-dump2.html | 3296 +++++++++++++++ 21 files changed, 8205 insertions(+), 264 deletions(-) create mode 100644 04_Laravel/app/Filament/Pages/Auth/Login.php delete mode 100644 04_Laravel/app/Filament/Pages/DashboardWidgetRedesign.php create mode 100644 04_Laravel/app/Helpers/number.php create mode 100644 04_Laravel/public/images/ifnex-logo.png create mode 100644 04_Laravel/resources/views/filament/pages/auth/login.blade.php delete mode 100644 04_Laravel/resources/views/filament/pages/dashboard-widget-redesign.blade.php create mode 100644 04_Laravel/resources/views/filament/widgets/recent-transactions.blade.php create mode 100644 04_Laravel/storage/panel-dump.html create mode 100644 04_Laravel/storage/panel-dump2.html diff --git a/04_Laravel/app/Filament/Pages/Auth/Login.php b/04_Laravel/app/Filament/Pages/Auth/Login.php new file mode 100644 index 0000000..f8c6c1c --- /dev/null +++ b/04_Laravel/app/Filament/Pages/Auth/Login.php @@ -0,0 +1,65 @@ +schema([ + TextInput::make('email') + ->label(__('filament-panels::pages/auth/login.form.email.label')) + ->email() + ->required() + ->autocomplete() + ->prefixIcon('heroicon-o-at-symbol') + ->extraInputAttributes(['class' => 'ifnex-login-input']), + + TextInput::make('password') + ->label(__('filament-panels::pages/auth/login.form.password.label')) + ->password() + ->revealable() + ->required() + ->autocomplete('current-password') + ->prefixIcon('heroicon-o-lock-closed') + ->extraInputAttributes(['class' => 'ifnex-login-input']), + + Checkbox::make('remember') + ->label(__('filament-panels::pages/auth/login.form.remember.label')) + ->extraAttributes(['class' => 'ifnex-login-remember']), + ]) + ->statePath('data'); + } + + /** + * بازنویسی متن دکمهٔ submit به «ورود به سیستم» و افزودن کلاس + * سفارشی برای استایل گرادیان آمبر. + */ + protected function getAuthenticateFormAction(): Action + { + return parent::getAuthenticateFormAction() + ->label('ورود به سیستم') + ->extraAttributes(['class' => 'ifnex-login-submit w-full']); + } +} \ No newline at end of file diff --git a/04_Laravel/app/Filament/Pages/Dashboard.php b/04_Laravel/app/Filament/Pages/Dashboard.php index 1e2470c..a6947f3 100644 --- a/04_Laravel/app/Filament/Pages/Dashboard.php +++ b/04_Laravel/app/Filament/Pages/Dashboard.php @@ -2,31 +2,82 @@ namespace App\Filament\Pages; +use App\Filament\Widgets\DashboardInfoWidget; +use App\Filament\Widgets\ExchangeRateWidget; use App\Filament\Widgets\FinanceOverviewWidget; +use App\Filament\Widgets\RecentShipmentsWidget; use App\Filament\Widgets\RecentTransactionsWidget; use App\Filament\Widgets\TransactionChartWidget; use Filament\Pages\Dashboard as BaseDashboard; +/** + * IFNEX Admin Dashboard — layered layout (12-column grid) + * + * Layer 1 (header): FinanceOverviewWidget (10/12) + DashboardInfoWidget (2/12) + * → 4 KPI cards + small clock/welcome card in the corner + * Layer 2: ExchangeRateWidget (12/12) → 4 rate cards in a comfortable row + * Layer 3: RecentShipmentsWidget (6/12) + RecentTransactionsWidget (6/12) + * → two tables with identical style/size + * Layer 4 (bottom): TransactionChartWidget (12/12) → 7-day trend, full width + * + * Smart Grid: 12-column responsive grid. Each widget uses an array-based + * columnSpan so mobile stacks everything full-width; desktop shows the + * layered progressive-disclosure design. + * + * ⚠️ نام متد ستون‌ها در فیلامنت getColumns() است نه getWidgetsColumns() — + * با نام اشتباه، گرید روی پیش‌فرض ۲ ستون می‌ماند و spanهای ۴/۶/۸ گرید را می‌شکنند. + * این صفحه باید تنها Dashboard ثبت‌شده پنل باشد؛ ثبت صریح Filament\Pages\Dashboard + * در AdminPanelProvider باعث می‌شود داشبورد پایه (با کل استخر ویجت‌ها) بر این override غلبه کند. + */ class Dashboard extends BaseDashboard { protected static ?string $navigationLabel = 'داشبورد'; protected static ?string $slug = 'dashboard'; protected static ?string $title = 'داشبورد مالی'; - protected static ?string $navigationIcon = 'heroicon-o-home'; + /** + * Layer 1 — Top row: 4 KPI cards + small clock/welcome corner card. + */ public function getHeaderWidgets(): array { return [ FinanceOverviewWidget::class, + DashboardInfoWidget::class, ]; } + /** + * Layers 2–4 — rate row, two tables, then the full-width trend chart at the bottom. + * ترتیب نمایش با sort هر ویجت تعیین می‌شود: نرخ(3) ← جدول‌ها(6,7) ← چارت(8). + */ public function getWidgets(): array { return [ - TransactionChartWidget::class, + // Layer 2: exchange rates (12/12) + ExchangeRateWidget::class, + + // Layer 3: recent shipments (6/12) + recent transactions (6/12) + RecentShipmentsWidget::class, RecentTransactionsWidget::class, + + // Layer 4: 7-day revenue trend (12/12) — full width, bottom of the page + TransactionChartWidget::class, ]; } + + /** + * Main widgets grid: 12 columns on desktop, 1 on mobile so each + * widget stacks full-width. Each widget sets its own responsive + * columnSpan; the grid just exposes the columns. + */ + public function getColumns(): int | string | array + { + return 12; + } + + public function getHeaderWidgetsColumns(): int | string | array + { + return 12; + } } diff --git a/04_Laravel/app/Filament/Pages/DashboardWidgetRedesign.php b/04_Laravel/app/Filament/Pages/DashboardWidgetRedesign.php deleted file mode 100644 index e9e0927..0000000 --- a/04_Laravel/app/Filament/Pages/DashboardWidgetRedesign.php +++ /dev/null @@ -1,28 +0,0 @@ -user()?->hasAnyRole(['super_admin', 'admin']) ?? false; - } -} \ No newline at end of file diff --git a/04_Laravel/app/Filament/Widgets/DashboardInfoWidget.php b/04_Laravel/app/Filament/Widgets/DashboardInfoWidget.php index 471e3ee..c1c20a0 100644 --- a/04_Laravel/app/Filament/Widgets/DashboardInfoWidget.php +++ b/04_Laravel/app/Filament/Widgets/DashboardInfoWidget.php @@ -15,7 +15,7 @@ class DashboardInfoWidget extends Widget protected static bool $isLazy = false; - protected int | string | array $columnSpan = 1; + protected int | string | array $columnSpan = 2; protected static string $view = 'filament.widgets.dashboard-info'; diff --git a/04_Laravel/app/Filament/Widgets/ExchangeRateWidget.php b/04_Laravel/app/Filament/Widgets/ExchangeRateWidget.php index 4455cb2..7c7a3cb 100644 --- a/04_Laravel/app/Filament/Widgets/ExchangeRateWidget.php +++ b/04_Laravel/app/Filament/Widgets/ExchangeRateWidget.php @@ -2,21 +2,32 @@ namespace App\Filament\Widgets; +use App\Models\ExchangeRateHistory; use App\Services\ExchangeRateService; use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget\Stat; -use App\Models\ExchangeRateHistory; +/** + * Layer 2 (right) — live exchange-rate summary, spanning 4/12 of the body grid. + */ class ExchangeRateWidget extends BaseWidget { protected static ?int $sort = 3; + protected static bool $isLazy = false; - protected int | string | array $columnSpan = 'full'; + protected int|string|array $columnSpan = 12; + + /** + * Stat cards inside the rate widget: 1 column on the narrow lg+ column, + * 2 on mobile so they don't all stack vertically on tiny screens. + */ + protected int|string|array $columns = 1; protected function getStats(): array { $service = app(ExchangeRateService::class); + $aedInfo = $service->getLatestRateInfo('AED', 'IRR'); $usdInfo = $service->getLatestRateInfo('USD', 'IRR'); @@ -27,28 +38,40 @@ class ExchangeRateWidget extends BaseWidget $usdChart = $service->getRateChart('USD', 'IRR', 7); return [ - Stat::make('نرخ درهم (AED)', number_format($aedInfo['current_rate']) . ' ریال') - ->description($aedChangePercent !== null - ? 'تغییر: ' . number_format($aedChangePercent, 2) . '% — توسط ' . ($aedInfo['changed_by'] ?? 'سیستم') - : 'آخرین تغییر: ' . ($aedInfo['last_updated']?->format('Y/m/d H:i') ?? '—')) + Stat::make('نرخ درهم (AED)', ifnex_format_number($aedInfo['current_rate'] ?? 0)) + ->description( + $aedChangePercent !== null + ? sprintf('%s %s — %s', + $aedChangePercent > 0 ? '↑' : ($aedChangePercent < 0 ? '↓' : '—'), + ifnex_persian_digits(number_format(abs((float) $aedChangePercent), 2)) . '%', + ($aedInfo['changed_by'] ?? 'سیستم')) + : 'آخرین تغییر: ' . ($aedInfo['last_updated']?->format('Y/m/d H:i') ?? '—') + ) ->descriptionIcon( $aedChangePercent > 0 ? 'heroicon-m-arrow-trending-up' : ($aedChangePercent < 0 ? 'heroicon-m-arrow-trending-down' : 'heroicon-m-minus') ) + ->icon('heroicon-o-currency-dollar') ->color( $aedChangePercent > 0 ? 'danger' : ($aedChangePercent < 0 ? 'success' : 'gray') ) ->chart(!empty($aedChart) ? $aedChart : [0]), - Stat::make('نرخ دلار (USD)', number_format($usdInfo['current_rate']) . ' ریال') - ->description($usdChangePercent !== null - ? 'تغییر: ' . number_format($usdChangePercent, 2) . '% — توسط ' . ($usdInfo['changed_by'] ?? 'سیستم') - : 'آخرین تغییر: ' . ($usdInfo['last_updated']?->format('Y/m/d H:i') ?? '—')) + Stat::make('نرخ دلار (USD)', ifnex_format_number($usdInfo['current_rate'] ?? 0)) + ->description( + $usdChangePercent !== null + ? sprintf('%s %s — %s', + $usdChangePercent > 0 ? '↑' : ($usdChangePercent < 0 ? '↓' : '—'), + ifnex_persian_digits(number_format(abs((float) $usdChangePercent), 2)) . '%', + ($usdInfo['changed_by'] ?? 'سیستم')) + : 'آخرین تغییر: ' . ($usdInfo['last_updated']?->format('Y/m/d H:i') ?? '—') + ) ->descriptionIcon( $usdChangePercent > 0 ? 'heroicon-m-arrow-trending-up' : ($usdChangePercent < 0 ? 'heroicon-m-arrow-trending-down' : 'heroicon-m-minus') ) + ->icon('heroicon-o-banknotes') ->color( $usdChangePercent > 0 ? 'danger' : ($usdChangePercent < 0 ? 'success' : 'gray') @@ -56,15 +79,21 @@ class ExchangeRateWidget extends BaseWidget ->chart(!empty($usdChart) ? $usdChart : [0]), Stat::make('تغییرات امروز', - ExchangeRateHistory::whereDate('created_at', today())->count() + ifnex_persian_digits(number_format( + ExchangeRateHistory::whereDate('created_at', today())->count() + )) ) ->description('تغییرات نرخ ثبت‌شده در امروز') ->descriptionIcon('heroicon-m-arrow-path') + ->icon('heroicon-o-arrow-path') ->color('info'), - Stat::make('در انتظار تأیید', $service->getPendingCount()) + Stat::make('در انتظار تأیید', + ifnex_persian_digits(number_format($service->getPendingCount())) + ) ->description('نرخ‌های پیشنهادی API — منتظر تأیید مدیر') ->descriptionIcon('heroicon-m-clock') + ->icon('heroicon-o-clock') ->color('warning'), ]; } diff --git a/04_Laravel/app/Filament/Widgets/FinanceOverviewWidget.php b/04_Laravel/app/Filament/Widgets/FinanceOverviewWidget.php index eb2f79a..8056e91 100644 --- a/04_Laravel/app/Filament/Widgets/FinanceOverviewWidget.php +++ b/04_Laravel/app/Filament/Widgets/FinanceOverviewWidget.php @@ -2,54 +2,178 @@ namespace App\Filament\Widgets; +use App\Enums\ShipmentStatus; use App\Models\Shipment; use App\Models\Wallet; use App\Models\WalletTransaction; use Filament\Widgets\StatsOverviewWidget; use Filament\Widgets\StatsOverviewWidget\Stat; -use Illuminate\Support\Facades\DB; +use Illuminate\Support\Carbon; +/** + * Layer 1 — Top KPI row on the IFNEX dashboard. + * + * Returns exactly 4 stat cards in a 4-column grid: + * 1. سفارشات در انتظار تأیید — amber (clock) + * 2. درآمد امروز — green (trending up) + * 3. پرداخت‌های معوق — red (banknotes) + * 4. موجودی کیف پول‌ها — blue (wallet) + */ class FinanceOverviewWidget extends StatsOverviewWidget { protected static ?string $pollingInterval = '30s'; + protected static ?int $sort = 1; + + protected static bool $isLazy = false; + + // ردیف هدر گرید ۱۲ ستونه است: 10/12 برای KPIها + 2/12 کارت ساعت (DashboardInfoWidget) + protected int | string | array $columnSpan = 10; + + /** + * Stat cards render in a 4-column responsive grid: + * - default (mobile): 1 col (stacked) + * - sm: 2 cols + * - lg: 4 cols (the canonical IFNEX layout) + */ + protected int|string|array $columns = 4; + protected function getStats(): array { - $today = now()->startOfDay(); + $today = today(); + $yesterday = today()->subDay(); - $totalRevenue = WalletTransaction::where('type', 'order_payment') + // ─── KPI 1: سفارشات در انتظار تأیید ─── + $pendingApprovalCount = Shipment::where('status', ShipmentStatus::PendingApproval)->count(); + $approvedUnpaidCount = Shipment::where('status', ShipmentStatus::Approved)->count(); + $pendingOrdersTotal = $pendingApprovalCount + $approvedUnpaidCount; + + $pendingSparkline = []; + for ($i = 6; $i >= 0; $i--) { + $date = today()->subDays($i); + $pendingSparkline[] = (int) Shipment::whereDate('created_at', $date) + ->whereIn('status', [ShipmentStatus::PendingApproval, ShipmentStatus::Approved]) + ->count(); + } + + // ─── KPI 2: درآمد امروز ─── + $todayRevenue = (float) WalletTransaction::where('type', 'order_payment') ->where('status', 'completed') ->where('amount', '>', 0) + ->whereDate('created_at', $today) ->sum('amount'); - $todayRevenue = WalletTransaction::where('type', 'order_payment') + $yesterdayRevenue = (float) WalletTransaction::where('type', 'order_payment') ->where('status', 'completed') ->where('amount', '>', 0) - ->where('created_at', '>=', $today) + ->whereDate('created_at', $yesterday) ->sum('amount'); - $pendingPayments = Shipment::where('status', 'approved')->count(); - $pendingAmount = Shipment::where('status', 'approved')->sum('total_fee'); + [$revPercent, $revDirection, $revFormatted] = ifnex_trend_percent($todayRevenue, $yesterdayRevenue); - $totalWalletBalance = Wallet::sum('balance'); + $revenueSparkline = []; + for ($i = 6; $i >= 0; $i--) { + $date = today()->subDays($i); + $revenueSparkline[] = (float) WalletTransaction::where('type', 'order_payment') + ->where('status', 'completed') + ->where('amount', '>', 0) + ->whereDate('created_at', $date) + ->sum('amount'); + } + + // ─── KPI 3: پرداخت‌های معوق ─── + $pendingAmount = (float) Shipment::where('status', ShipmentStatus::Approved)->sum('total_fee'); + $pendingShipmentsCount = Shipment::where('status', ShipmentStatus::Approved)->count(); + + $yesterdayPendingAmount = (float) Shipment::where('status', ShipmentStatus::Approved) + ->whereDate('updated_at', '<', $today) + ->sum('total_fee'); + [$pendingPercent, $pendingDirection, $pendingFormatted] = ifnex_trend_percent( + $pendingAmount, + $yesterdayPendingAmount + ); + + // ─── KPI 4: موجودی کیف پول‌ها ─── + $totalWalletBalance = (float) Wallet::sum('balance'); + $activeWallets = Wallet::where('balance', '>', 0)->count(); + + $walletSparkline = []; + for ($i = 6; $i >= 0; $i--) { + $date = today()->subDays($i); + $walletSparkline[] = (float) WalletTransaction::where('status', 'completed') + ->where('amount', '>', 0) + ->whereDate('created_at', $date) + ->sum('amount'); + } return [ - Stat::make('درآمد کل (تکمیل شده)', number_format($totalRevenue) . ' ریال') - ->icon('heroicon-o-currency-dollar') - ->color('success'), - - Stat::make('درآمد امروز', number_format($todayRevenue) . ' ریال') - ->icon('heroicon-o-chart-bar') - ->color('info'), - - Stat::make('پرداخت‌های در انتظار', $pendingPayments . ' سفارش') - ->description(number_format($pendingAmount) . ' ریال') + Stat::make('سفارشات در انتظار', ifnex_persian_digits(number_format($pendingOrdersTotal))) + ->description( + $pendingOrdersTotal > 0 + ? sprintf('%s در انتظار تأیید، %s آماده پرداخت', + ifnex_persian_digits(number_format($pendingApprovalCount)), + ifnex_persian_digits(number_format($approvedUnpaidCount))) + : 'سفارش جدیدی نیست' + ) + ->descriptionIcon('heroicon-m-clock') ->icon('heroicon-o-clock') - ->color('warning'), + ->color('warning') + ->chart($pendingSparkline) + ->url( + $pendingOrdersTotal > 0 + ? route('filament.admin.resources.shipments.index') + : null + ), - Stat::make('مجموع موجودی کیف‌پول‌ها', number_format($totalWalletBalance) . ' ریال') + Stat::make('درآمد امروز', ifnex_format_number($todayRevenue)) + ->description( + $revDirection === 'up' ? sprintf('↑ %s نسبت به دیروز', $revFormatted) : + ($revDirection === 'down' ? sprintf('↓ %s نسبت به دیروز', $revFormatted) : + 'بدون تغییر نسبت به دیروز') + ) + ->descriptionIcon( + $revDirection === 'up' ? 'heroicon-m-arrow-trending-up' : + ($revDirection === 'down' ? 'heroicon-m-arrow-trending-down' : 'heroicon-m-minus') + ) + ->icon('heroicon-o-arrow-trending-up') + ->color( + $revDirection === 'up' ? 'success' : + ($revDirection === 'down' ? 'danger' : 'gray') + ) + ->chart($revenueSparkline) + ->url(route('filament.admin.resources.wallet-transactions.index')), + + Stat::make('پرداخت‌های معوق', ifnex_format_number($pendingAmount)) + ->description( + $pendingShipmentsCount > 0 + ? sprintf('%s سفارش در انتظار پرداخت — %s', + ifnex_persian_digits(number_format($pendingShipmentsCount)), + $pendingDirection === 'up' ? sprintf('↑ %s', $pendingFormatted) : + ($pendingDirection === 'down' ? sprintf('↓ %s', $pendingFormatted) : 'بدون تغییر')) + : 'پرداخت معوقی نیست' + ) + ->descriptionIcon( + $pendingDirection === 'up' ? 'heroicon-m-arrow-trending-up' : + ($pendingDirection === 'down' ? 'heroicon-m-arrow-trending-down' : 'heroicon-m-minus') + ) + ->icon('heroicon-o-banknotes') + ->color('danger') + ->chart($pendingSparkline) + ->url( + $pendingShipmentsCount > 0 + ? route('filament.admin.resources.shipments.index') + : null + ), + + Stat::make('موجودی کیف پول‌ها', ifnex_format_number($totalWalletBalance)) + ->description( + sprintf('%s کیف پول فعال', ifnex_persian_digits(number_format($activeWallets))) + ) + ->descriptionIcon('heroicon-m-wallet') ->icon('heroicon-o-wallet') - ->color('gray'), + ->color('info') + ->chart($walletSparkline) + ->url(route('filament.admin.resources.wallets.index')), ]; } -} +} \ No newline at end of file diff --git a/04_Laravel/app/Filament/Widgets/RecentShipmentsWidget.php b/04_Laravel/app/Filament/Widgets/RecentShipmentsWidget.php index 608a494..30148ad 100644 --- a/04_Laravel/app/Filament/Widgets/RecentShipmentsWidget.php +++ b/04_Laravel/app/Filament/Widgets/RecentShipmentsWidget.php @@ -7,15 +7,15 @@ use Filament\Widgets\Widget; use Morilog\Jalali\Jalalian; /** - * جدول آخرین ۵ سفارش — تمام‌عرض، زیر کارت تاریخ/ساعت. + * Layer 3 (left) — recent shipments table, spanning 6/12 of the body grid. */ class RecentShipmentsWidget extends Widget { - protected static ?int $sort = 2; + protected static ?int $sort = 6; protected static bool $isLazy = false; - protected int | string | array $columnSpan = 'full'; + protected int|string|array $columnSpan = 6; protected static string $view = 'filament.widgets.recent-shipments'; @@ -29,45 +29,58 @@ class RecentShipmentsWidget extends Widget ->map(function ($s) { return [ 'awb' => $s->awb_no, - 'route' => ($s->fromCountry?->name ?? '?') . ' → ' . ($s->toCountry?->name ?? '?'), + 'route' => ($s->fromCountry?->name ?? '?') . ' ← ' . ($s->toCountry?->name ?? '?'), 'status' => $s->status?->value ?? '—', 'status_label' => $this->statusLabel($s->status?->value), - 'status_color' => $this->statusColor($s->status?->value), + 'status_color' => $this->statusBadgeClass($s->status?->value), 'date' => Jalalian::fromCarbon($s->created_at)->format('Y/m/d'), - 'amount' => $s->total_fee ? number_format($s->total_fee) . ' ریال' : '—', + 'amount' => $s->total_fee ? ifnex_format_number($s->total_fee) : '—', + 'url' => route('filament.admin.resources.shipments.view', $s), ]; }); return [ 'recentShipments' => $recentShipments, + 'viewAllUrl' => route('filament.admin.resources.shipments.index'), ]; } private function statusLabel(?string $status): string { - return match($status) { - 'processed' => 'پردازش شده', - 'picked_up' => 'تحویل گرفته شده', + return match ($status) { + 'pending_approval' => 'در انتظار تأیید', + 'approved' => 'تأیید شده', + 'pending_payment' => 'در انتظار پرداخت', + 'cancelled' => 'لغو شده', + 'processed' => 'در حال پردازش', + 'picked_up' => 'تحویل به حمل‌کننده', 'in_transit' => 'در حال حمل', - 'out_for_delivery' => 'در مسیر تحویل', - 'delivered' => 'تحویل داده شده', - 'failed' => 'ناموفق', + 'out_for_delivery' => 'آماده تحویل', + 'failed' => 'تحویل ناموفق', + 'delivered' => 'تحویل شده', 'returned' => 'برگشتی', + 'archived' => 'آرشیو', default => '—', }; } - private function statusColor(?string $status): string + /** + * Return the badge CSS class name — uses the new ifnex-badge-* styles + * defined in head.blade.php (success / warning / danger / info / gray). + */ + private function statusBadgeClass(?string $status): string { - return match($status) { - 'processed' => 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300', - 'picked_up' => 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300', - 'in_transit' => 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300', - 'out_for_delivery' => 'bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300', - 'delivered' => 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300', - 'failed' => 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300', - 'returned' => 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300', - default => 'bg-gray-100 text-gray-700', + return match ($status) { + 'pending_approval', 'pending_payment' => 'ifnex-badge-warning', + 'approved' => 'ifnex-badge-info', + 'picked_up', 'in_transit' => 'ifnex-badge-info', + 'out_for_delivery' => 'ifnex-badge-info', + 'delivered' => 'ifnex-badge-success', + 'cancelled', 'failed' => 'ifnex-badge-danger', + 'returned' => 'ifnex-badge-warning', + 'processed' => 'ifnex-badge-info', + 'archived' => 'ifnex-badge-gray', + default => 'ifnex-badge-gray', }; } -} +} \ No newline at end of file diff --git a/04_Laravel/app/Filament/Widgets/RecentTransactionsWidget.php b/04_Laravel/app/Filament/Widgets/RecentTransactionsWidget.php index 965f270..5af4e69 100644 --- a/04_Laravel/app/Filament/Widgets/RecentTransactionsWidget.php +++ b/04_Laravel/app/Filament/Widgets/RecentTransactionsWidget.php @@ -2,68 +2,71 @@ namespace App\Filament\Widgets; +use App\Enums\TransactionStatus; +use App\Enums\TransactionType; use App\Models\WalletTransaction; -use Filament\Tables; -use Filament\Tables\Table; -use Filament\Widgets\TableWidget as BaseWidget; -use Illuminate\Database\Eloquent\Builder; +use Filament\Widgets\Widget; +use Morilog\Jalali\Jalalian; -class RecentTransactionsWidget extends BaseWidget +/** + * Layer 3 (right) — recent transactions table, spanning 6/12 of the body grid. + * استایل و ساختار همسان با RecentShipmentsWidget (کارت + هدر آیکون‌دار + بج‌های برند). + */ +class RecentTransactionsWidget extends Widget { - protected static ?string $pollingInterval = '30s'; - protected static ?string $heading = 'آخرین تراکنش‌ها'; - protected static ?int $sort = 5; + protected static ?int $sort = 7; - public function table(Table $table): Table + protected static bool $isLazy = false; + + protected int|string|array $columnSpan = 6; + + protected static string $view = 'filament.widgets.recent-transactions'; + + protected function getViewData(): array { - return $table - ->query( - WalletTransaction::query() - ->latest() - ->limit(10) - ) - ->columns([ - Tables\Columns\TextColumn::make('id') - ->label('ID') - ->sortable() - ->width(60), - Tables\Columns\TextColumn::make('wallet.user.name') - ->label('کاربر') - ->searchable() - ->limit(20), - Tables\Columns\TextColumn::make('amount') - ->label('مبلغ') - ->money('IRR', divideBy: 1) - ->color(fn ($record) => $record->amount >= 0 ? 'success' : 'danger') - ->sortable(), - Tables\Columns\BadgeColumn::make('type') - ->label('نوع') - ->getStateUsing(fn ($record) => $record->type->label()) - ->colors([ - 'success' => \App\Enums\TransactionType::DEPOSIT, - 'danger' => \App\Enums\TransactionType::WITHDRAWAL, - 'warning' => \App\Enums\TransactionType::ORDER_PAYMENT, - 'info' => \App\Enums\TransactionType::REFUND, - ]), - Tables\Columns\BadgeColumn::make('status') - ->label('وضعیت') - ->getStateUsing(fn ($record) => $record->status->label()) - ->colors([ - 'success' => \App\Enums\TransactionStatus::COMPLETED, - 'warning' => \App\Enums\TransactionStatus::PENDING, - 'danger' => \App\Enums\TransactionStatus::FAILED, - 'info' => \App\Enums\TransactionStatus::REFUNDED, - ]), - Tables\Columns\TextColumn::make('gateway_reference_id') - ->label('کد پیگیری') - ->searchable() - ->toggleable(), - Tables\Columns\TextColumn::make('created_at') - ->label('تاریخ') - ->dateTime('Y/m/d H:i', 'Asia/Tehran') - ->sortable() - ->since(), - ]) - ->defaultSort('created_at', 'desc'); + $transactions = WalletTransaction::query() + ->with('wallet.user') + ->latest() + ->limit(5) + ->get() + ->map(function ($t) { + return [ + 'user' => $t->wallet?->user?->name ?? '—', + 'amount' => ifnex_format_number(abs((float) $t->amount)), + 'amount_negative' => $t->amount < 0, + 'type_label' => $t->type->label(), + 'type_color' => $this->typeBadgeClass($t->type), + 'status_label' => $t->status->label(), + 'status_color' => $this->statusBadgeClass($t->status), + 'date' => Jalalian::fromCarbon($t->created_at)->format('Y/m/d H:i'), + ]; + }); + + return [ + 'transactions' => $transactions, + 'viewAllUrl' => route('filament.admin.resources.wallet-transactions.index'), + ]; + } + + private function typeBadgeClass(TransactionType $type): string + { + return match ($type) { + TransactionType::DEPOSIT => 'ifnex-badge-success', + TransactionType::WITHDRAWAL => 'ifnex-badge-danger', + TransactionType::ORDER_PAYMENT => 'ifnex-badge-warning', + TransactionType::REFUND => 'ifnex-badge-info', + default => 'ifnex-badge-gray', + }; + } + + private function statusBadgeClass(TransactionStatus $status): string + { + return match ($status) { + TransactionStatus::COMPLETED => 'ifnex-badge-success', + TransactionStatus::PENDING => 'ifnex-badge-warning', + TransactionStatus::FAILED => 'ifnex-badge-danger', + TransactionStatus::REFUNDED => 'ifnex-badge-info', + default => 'ifnex-badge-gray', + }; } } diff --git a/04_Laravel/app/Filament/Widgets/TransactionChartWidget.php b/04_Laravel/app/Filament/Widgets/TransactionChartWidget.php index 4334393..3fb9c91 100644 --- a/04_Laravel/app/Filament/Widgets/TransactionChartWidget.php +++ b/04_Laravel/app/Filament/Widgets/TransactionChartWidget.php @@ -5,14 +5,22 @@ namespace App\Filament\Widgets; use App\Models\WalletTransaction; use Filament\Widgets\ChartWidget; use Illuminate\Support\Facades\DB; +use Morilog\Jalali\Jalalian; +/** + * Layer 2 (left) — 7-day revenue trend chart, spanning 8/12 of the body grid. + */ class TransactionChartWidget extends ChartWidget { - -protected static ?int $sort = 4; -protected static ?string $pollingInterval = '60s'; - protected static ?string $heading = 'روند تراکنش‌های ۳۰ روز اخیر'; - protected static ?string $maxHeight = '300px'; + protected static ?int $sort = 8; + + protected static ?string $pollingInterval = '60s'; + + protected static ?string $heading = 'روند تراکنش‌های ۷ روز اخیر'; + + protected static ?string $maxHeight = '320px'; + + protected int|string|array $columnSpan = 12; protected function getData(): array { @@ -21,14 +29,16 @@ protected static ?string $pollingInterval = '60s'; DB::raw('SUM(CASE WHEN type = "deposit" AND status = "completed" THEN amount ELSE 0 END) as deposits'), DB::raw('SUM(CASE WHEN type IN ("withdrawal", "order_payment") AND status = "completed" THEN amount ELSE 0 END) as withdrawals') ) - ->where('created_at', '>=', now()->subDays(30)) + ->where('created_at', '>=', now()->subDays(7)) ->groupBy('date') ->orderBy('date') ->get(); - $labels = $data->pluck('date')->map(fn ($date) => \Morilog\Jalali\Jalalian::fromCarbon(\Carbon\Carbon::parse($date))->format('Y/m/d'))->toArray(); - $deposits = $data->pluck('deposits')->toArray(); - $withdrawals = $data->pluck('withdrawals')->toArray(); + $labels = $data->pluck('date')->map(function ($date) { + return Jalalian::fromCarbon(\Carbon\Carbon::parse($date))->format('l d'); + })->toArray(); + $deposits = $data->pluck('deposits')->map(fn ($v) => (float) $v)->toArray(); + $withdrawals = $data->pluck('withdrawals')->map(fn ($v) => (float) $v)->toArray(); return [ 'datasets' => [ @@ -36,17 +46,23 @@ protected static ?string $pollingInterval = '60s'; 'label' => 'واریزی‌ها', 'data' => $deposits, 'borderColor' => '#10b981', - 'backgroundColor' => 'rgba(16, 185, 129, 0.1)', + 'backgroundColor' => 'rgba(16, 185, 129, 0.15)', 'fill' => true, 'tension' => 0.4, + 'pointRadius' => 3, + 'pointHoverRadius' => 5, + 'pointBackgroundColor' => '#10b981', ], [ 'label' => 'برداشت‌ها', 'data' => $withdrawals, 'borderColor' => '#f59e0b', - 'backgroundColor' => 'rgba(245, 158, 11, 0.1)', + 'backgroundColor' => 'rgba(245, 158, 11, 0.15)', 'fill' => true, 'tension' => 0.4, + 'pointRadius' => 3, + 'pointHoverRadius' => 5, + 'pointBackgroundColor' => '#f59e0b', ], ], 'labels' => $labels, @@ -64,10 +80,23 @@ protected static ?string $pollingInterval = '60s'; 'plugins' => [ 'legend' => [ 'position' => 'top', + 'align' => 'end', 'labels' => [ 'font' => [ - 'family' => 'inherit', + 'family' => 'Vazirmatn', + 'size' => 12, ], + 'usePointStyle' => true, + 'boxWidth' => 8, + ], + ], + 'tooltip' => [ + 'callbacks' => [ + 'label' => 'function(context) { + var v = context.parsed.y; + if (v === null || v === undefined) return ""; + return context.dataset.label + ": " + v.toLocaleString("fa-IR") + " ریال"; + }', ], ], ], @@ -76,14 +105,23 @@ protected static ?string $pollingInterval = '60s'; 'beginAtZero' => true, 'ticks' => [ 'callback' => 'function(value) { return value.toLocaleString("fa-IR"); }', + 'font' => ['family' => 'Vazirmatn', 'size' => 11], + ], + 'grid' => [ + 'color' => 'rgba(0, 0, 0, 0.05)', ], ], 'x' => [ 'ticks' => [ 'maxRotation' => 0, + 'font' => ['family' => 'Vazirmatn', 'size' => 11], + ], + 'grid' => [ + 'display' => false, ], ], ], + 'maintainAspectRatio' => false, ]; } -} +} \ No newline at end of file diff --git a/04_Laravel/app/Helpers/number.php b/04_Laravel/app/Helpers/number.php new file mode 100644 index 0000000..c6cd763 --- /dev/null +++ b/04_Laravel/app/Helpers/number.php @@ -0,0 +1,146 @@ += 1_000_000_000) { + $formatted = number_format($number / 1_000_000_000, 2); + } elseif ($abs >= 1_000_000) { + $formatted = number_format($number / 1_000_000, 1); + } elseif ($abs >= 1_000) { + $formatted = number_format($number / 1_000, 0); + } else { + $formatted = number_format($number); + } + + // Convert Latin digits to Persian (fa-IR) + $formatted = ifnex_persian_digits($formatted); + + // Append compact unit if applicable + if ($abs >= 1_000_000_000) { + $formatted .= ' میلیارد'; + } elseif ($abs >= 1_000_000) { + $formatted .= ' میلیون'; + } elseif ($abs >= 1_000) { + $formatted .= ' هزار'; + } + + if ($currency !== '' && $currency !== null) { + $formatted .= ' ' . $currency; + } + + return trim($formatted); + } +} + +if (!function_exists('ifnex_persian_digits')) { + /** + * Replace Latin (0-9) digits in a string with Persian digits (۰-۹). + * Keeps other characters intact. + */ + function ifnex_persian_digits(string $text): string + { + return strtr($text, [ + '0' => '۰', + '1' => '۱', + '2' => '۲', + '3' => '۳', + '4' => '۴', + '5' => '۵', + '6' => '۶', + '7' => '۷', + '8' => '۸', + '9' => '۹', + ]); + } +} + +if (!function_exists('ifnex_english_digits')) { + /** + * Replace Persian digits back to Latin — handy for form input parsing. + */ + function ifnex_english_digits(string $text): string + { + return strtr($text, [ + '۰' => '0', + '۱' => '1', + '۲' => '2', + '۳' => '3', + '۴' => '4', + '۵' => '5', + '۶' => '6', + '۷' => '7', + '۸' => '8', + '۹' => '9', + '٠' => '0', + '١' => '1', + '٢' => '2', + '٣' => '3', + '٤' => '4', + '٥' => '5', + '٦' => '6', + '٧' => '7', + '٨' => '8', + '٩' => '9', + ]); + } +} + +if (!function_exists('ifnex_trend_percent')) { + /** + * Compute the percent change between two values and return a tuple of + * [percent_value, direction ('up'|'down'|'flat'), formatted Persian string]. + * + * @param float|int|null $current + * @param float|int|null $previous + * @return array{0: float, 1: string, 2: string} + */ + function ifnex_trend_percent(float|int|null $current, float|int|null $previous): array + { + $current = (float) ($current ?? 0); + $previous = (float) ($previous ?? 0); + + if ($previous == 0) { + return [0.0, $current > 0 ? 'up' : 'flat', '—']; + } + + $diff = $current - $previous; + $percent = ($diff / abs($previous)) * 100; + + if ($percent > 0.05) { + $direction = 'up'; + } elseif ($percent < -0.05) { + $direction = 'down'; + } else { + $direction = 'flat'; + } + + $formatted = ifnex_persian_digits(number_format(abs($percent), 1)) . '%'; + + return [$percent, $direction, $formatted]; + } +} \ No newline at end of file diff --git a/04_Laravel/app/Providers/AppServiceProvider.php b/04_Laravel/app/Providers/AppServiceProvider.php index 04bb716..8997fab 100644 --- a/04_Laravel/app/Providers/AppServiceProvider.php +++ b/04_Laravel/app/Providers/AppServiceProvider.php @@ -26,5 +26,13 @@ class AppServiceProvider extends ServiceProvider // ثبت Observer برای اطلاعیه‌های تغییر سفارش Shipment::observe(ShipmentObserver::class); + + // Explicitly require the IFNEX number helper file so + // ifnex_format_number() / ifnex_persian_digits() / ifnex_trend_percent() + // are available even before `composer dump-autoload` is re-run. + $helperPath = base_path('app/Helpers/number.php'); + if (is_file($helperPath)) { + require_once $helperPath; + } } } diff --git a/04_Laravel/app/Providers/Filament/AdminPanelProvider.php b/04_Laravel/app/Providers/Filament/AdminPanelProvider.php index 4f76a0a..ebdaa5f 100644 --- a/04_Laravel/app/Providers/Filament/AdminPanelProvider.php +++ b/04_Laravel/app/Providers/Filament/AdminPanelProvider.php @@ -26,8 +26,11 @@ class AdminPanelProvider extends PanelProvider ->default() ->id('admin') ->path('panel') - ->login() + ->login(\App\Filament\Pages\Auth\Login::class) ->brandName('IFNEX Logistics') + // لوگوی برند در نوار بالا — همان لوگوی صفحه لاگین + ->brandLogo(asset('images/ifnex-logo.png')) + ->brandLogoHeight('2.5rem') ->colors([ 'primary' => Color::Amber, 'gray' => [ @@ -66,16 +69,13 @@ class AdminPanelProvider extends PanelProvider ]) // ─── بارگذاری استایل سفارشی IFNEX به‌عنوان تم فیلمنت ─── // ->viteTheme('resources/css/filament.css') // موقتاً غیرفعال تا npm run build - ->pages([ - Pages\Dashboard::class, - ]) + // ⚠️ Pages\Dashboard::class اینجا ثبت نشود — App\Filament\Pages\Dashboard + // از طریق discoverPages کشف می‌شود؛ ثبت صریح داشبورد پایه باعث می‌شود + // نسخه پایه (با کل استخر ویجت‌های کشف‌شده) بر override سه‌لایه غلبه کند ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') ->widgets([ - // ویجت‌های سفارشی IFNEX (به ترتیب sort) - \App\Filament\Widgets\DashboardInfoWidget::class, \App\Filament\Widgets\RecentShipmentsWidget::class, \App\Filament\Widgets\ExchangeRateWidget::class, - \App\Filament\Widgets\WalletStats::class, ]) ->middleware([ EncryptCookies::class, @@ -94,6 +94,12 @@ class AdminPanelProvider extends PanelProvider ->renderHook( 'panels::head.start', fn () => view('filament.hooks.head') + ) + ->renderHook( + \Filament\View\PanelsRenderHook::FOOTER, + fn () => '
' + . 'قدرت گرفته از ورنا سافت
' ); } } \ No newline at end of file diff --git a/04_Laravel/composer.json b/04_Laravel/composer.json index e8e1c23..e3dfea3 100644 --- a/04_Laravel/composer.json +++ b/04_Laravel/composer.json @@ -32,7 +32,10 @@ "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" - } + }, + "files": [ + "app/Helpers/number.php" + ] }, "autoload-dev": { "psr-4": { @@ -95,4 +98,4 @@ }, "minimum-stability": "stable", "prefer-stable": true -} +} \ No newline at end of file diff --git a/04_Laravel/public/images/ifnex-logo.png b/04_Laravel/public/images/ifnex-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0227f3d914083a4aac49bae351e3b07812728c47 GIT binary patch literal 10465 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 sqiAtxg}IFV|Es a .fi-sidebar-item-label { + color: #f59e0b !important; + } + + .fi-sidebar-item-active > a .fi-icon { + color: #f59e0b !important; + } + /* ─── دکمه بستن/باز کردن همه منوها ─── */ .ifnex-sidebar-collapse-all { display: block; @@ -216,7 +249,7 @@ padding: 7px 10px; font-size: 11.5px; font-family: inherit; - color: #cbd5e1; + color: #f1f5f9; background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .14); border-radius: 8px; @@ -230,6 +263,163 @@ border-color: rgba(245, 158, 11, .35); color: #f59e0b; } + + /* ─── IFNEX Dashboard redesign — card / badge / trend design tokens ─── */ + + /* Base card: rounded-2xl, soft multi-layer shadow, 1px gray-200 border */ + .ifnex-card-shadow, + .ifnex-card { + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.08), + 0 1px 2px rgba(0, 0, 0, 0.04) !important; + } + + /* Stat card base — used by StatsOverviewWidget cards */ + .fi-wi-stats-overview-stat.ifnex-stat-card { + border-radius: 16px !important; + border: 1px solid #e5e7eb !important; + padding: 1.25rem !important; + background: #FAF9F6 !important; + } + + .dark .fi-wi-stats-overview-stat.ifnex-stat-card { + background: rgb(30 41 59) !important; + border-color: rgb(51 65 85) !important; + } + + .ifnex-stat-card { + border-radius: 16px !important; + border: 1px solid #e5e7eb !important; + padding: 1.25rem !important; + background: #FAF9F6 !important; + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.08), + 0 1px 2px rgba(0, 0, 0, 0.04) !important; + transition: transform 0.25s ease, box-shadow 0.25s ease !important; + } + + .ifnex-stat-card:hover { + transform: translateY(-2px); + box-shadow: + 0 10px 25px rgba(0, 0, 0, 0.10), + 0 4px 6px rgba(0, 0, 0, 0.05) !important; + } + + /* Stat icon — colored soft circle (40×40), centered heroicon */ + .ifnex-stat-icon { + width: 40px; + height: 40px; + border-radius: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + } + .ifnex-stat-icon-amber { background: #fef3c7; color: #d97706; } + .ifnex-stat-icon-green { background: #d1fae5; color: #059669; } + .ifnex-stat-icon-red { background: #fee2e2; color: #dc2626; } + .ifnex-stat-icon-blue { background: #dbeafe; color: #2563eb; } + .ifnex-stat-icon-gray { background: #f1f5f9; color: #475569; } + + .dark .ifnex-stat-icon-amber { background: rgba(245,158,11,.18); color: #fbbf24; } + .dark .ifnex-stat-icon-green { background: rgba(16,185,129,.18); color: #34d399; } + .dark .ifnex-stat-icon-red { background: rgba(239,68,68,.18); color: #f87171; } + .dark .ifnex-stat-icon-blue { background: rgba(59,130,246,.18); color: #60a5fa; } + .dark .ifnex-stat-icon-gray { background: rgba(148,163,184,.18); color: #cbd5e1; } + + /* Big number typography */ + .ifnex-stat-number { + font-family: 'Vazirmatn', sans-serif !important; + font-size: 28px; + line-height: 1.2; + font-weight: 800; + color: #111827; + letter-spacing: -0.5px; + } + .dark .ifnex-stat-number { color: #f9fafb; } + + /* Stat label */ + .ifnex-stat-label { + font-family: 'Vazirmatn', sans-serif !important; + font-size: 12px; + font-weight: 500; + color: #6b7280; + } + .dark .ifnex-stat-label { color: #9ca3af; } + + /* Trend indicators */ + .ifnex-stat-trend-up { + color: #10b981; + font-weight: 600; + font-size: 12px; + } + .ifnex-stat-trend-down { + color: #ef4444; + font-weight: 600; + font-size: 12px; + } + .ifnex-stat-trend-flat { + color: #9ca3af; + font-weight: 600; + font-size: 12px; + } + + /* ─── Brand status badges ─── */ + .ifnex-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 0.125rem 0.625rem; + border-radius: 9999px; + font-size: 11.5px; + font-weight: 600; + line-height: 1.4; + white-space: nowrap; + } + .ifnex-badge-success { background: #d1fae5; color: #047857; } + .ifnex-badge-warning { background: #fef3c7; color: #b45309; } + .ifnex-badge-danger { background: #fee2e2; color: #b91c1c; } + .ifnex-badge-info { background: #dbeafe; color: #1d4ed8; } + .ifnex-badge-gray { background: #f1f5f9; color: #475569; } + + .dark .ifnex-badge-success { background: rgba(16,185,129,.18); color: #6ee7b7; } + .dark .ifnex-badge-warning { background: rgba(245,158,11,.18); color: #fcd34d; } + .dark .ifnex-badge-danger { background: rgba(239,68,68,.18); color: #fca5a5; } + .dark .ifnex-badge-info { background: rgba(59,130,246,.18); color: #93c5fd; } + .dark .ifnex-badge-gray { background: rgba(148,163,184,.18); color: #cbd5e1; } + + /* ─── Table row hover (Layer 3 tables) ─── */ + .ifnex-table-row-hover:hover { + background-color: #fffbeb !important; + } + .dark .ifnex-table-row-hover:hover { + background-color: rgba(245, 158, 11, 0.08) !important; + } + + /* Compact table cells + headers used by ifnex widgets */ + .ifnex-th { + padding: 0.625rem 0.75rem; + font-size: 11.5px; + font-weight: 600; + text-transform: none; + } + .ifnex-td { + padding: 0.625rem 0.75rem; + vertical-align: middle; + } + + /* Inline link (e.g. "مشاهده همه ←") */ + .ifnex-link-inline { + display: inline-flex; + align-items: center; + gap: 2px; + } + + /* ─── Stat card icon color tinting for IFNEX KPIs ─── */ + .fi-wi-stats-overview-stat .fi-icon, + .fi-stat-icon { + border-radius: 12px !important; + } + + + + + + + + + داشبورد - IFNEX Logistics + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + + + +
+ + +
+ + +
+ +
+
+ +

+ داشبورد +

+ +
+ +
+ + + + +
+
+ +
+ +
+ + + + + + +
+ +
+
+
+
+
+
+
امروز
+
10:14
+
+
+ +
+
+ 📅 شمسی: + شنبه، 11 مهر 1405 +
+
+ 🌍 میلادی: + Saturday, 3 October 2026 +
+
+ +
+
خوش آمدید
+
مدیر کاظم
+
+
+
+ + + +
+ +
+
+
+
+ + + نرخ درهم (AED) + +
+ +
+ ۴۱۵ هزار ریال +
+ +
+ + + ↓ ۳.۷۲% — سیستم + + +
+
+ + +
+
+ + + + + +
+
+
+ +
+
+
+ + + نرخ دلار (USD) + +
+ +
+ ۳۸۷ هزار ریال +
+ +
+ + + ↑ ۱۳.۸۸% — سیستم + + +
+
+ + +
+
+ + + + + +
+
+
+ +
+
+
+ + + تغییرات امروز + +
+ +
+ ۰ +
+ +
+ + + تغییرات نرخ ثبت‌شده در امروز + + +
+
+ +
+ +
+
+
+ + + در انتظار تأیید + +
+ +
+ ۰ +
+ +
+ + + نرخ‌های پیشنهادی API — منتظر تأیید مدیر + + +
+
+ +
+ +
+
+ +
+ +
+
+
+
+ + + در انتظار تأیید + +
+ +
+ 0 +
+ +
+ + + سفارش جدیدی نیست + + +
+
+ +
+ +
+
+
+ + + در انتظار پرداخت + +
+ +
+ 8 +
+ +
+ + + سفارش‌های تأییدشده — آماده پرداخت + + +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + موجودی کل + +
+ +
+ 1,019,406,902 ریال +
+ +
+ + + مجموع موجودی همه کاربران + + +
+
+ + +
+
+ + + + + +
+
+
+ +
+
+
+ + + مجموع واریزی‌ها + +
+ +
+ 1,964,935,000 ریال +
+ +
+ + + کل مبالغ واریز شده موفق + + +
+
+ +
+ +
+
+
+ + + مجموع برداشت‌ها + +
+ +
+ 951,028,098 ریال +
+ +
+ + + کل مبالغ برداشت شده موفق + + +
+
+ +
+ +
+
+
+ + + تراکنش‌های امروز + +
+ +
+ 0 +
+ +
+ + + تعداد تراکنش‌های ثبت‌شده در امروز + + +
+
+ +
+ +
+
+
+ + + کیف پول‌های فعال + +
+ +
+ 4 +
+ +
+ + + کاربرانی با موجودی مثبت + + +
+
+ +
+ +
+
+
+ + + در انتظار بررسی + +
+ +
+ 5 +
+ +
+ + + تراکنش‌های نیاز به تأیید + + +
+
+ +
+ +
+
+ +
+
+ + +
+
+ + +

آخرین سفارشات

+
+ + مشاهده همه ← + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AWBمسیروضعیتمبلغتاریخ
+ + IFN-2026-77973 + + + Iran ← Aruba + + + تحویل به حمل‌کننده + + + ۳۲۵.۳ میلیون ریال + + 1405/07/10 +
+ + IFN-2026-29082 + + + Iran ← Armenia + + + تأیید شده + + + ۵۰.۸ میلیون ریال + + 1405/07/09 +
+ + IFN-2026-74634 + + + Iran ← Bahamas + + + در حال پردازش + + + ۲۱۱.۲ میلیون ریال + + 1405/07/09 +
+ + IFN-2026-34469 + + + Iran ← Angola + + + تأیید شده + + + ۱۷۷.۲ میلیون ریال + + 1405/07/08 +
+ + IFN-2026-39337 + + + Iran ← Anguilla + + + تأیید شده + + + ۱.۴۹ میلیارد ریال + + 1405/07/03 +
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +
+ + + + + +
+ +
+ +
+ +
+ + +
+ + + +
+ + +
+ + + +
+ +
+ + + + + +
+ + +
+ + + + +
+ +
+ + + + + +
+ +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/04_Laravel/storage/panel-dump2.html b/04_Laravel/storage/panel-dump2.html new file mode 100644 index 0000000..e060a76 --- /dev/null +++ b/04_Laravel/storage/panel-dump2.html @@ -0,0 +1,3296 @@ + + + + + + + + + + + + + + + + + + + + داشبورد مالی - IFNEX Logistics + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + + + +
+ + +
+ + +
+ +
+
+ +

+ داشبورد مالی +

+ +
+ +
+ + + + +
+
+ +
+ +
+ + + + + + + +
+ +
+
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + نرخ درهم (AED) + +
+ +
+ ۴۱۵ هزار ریال +
+ +
+ + + ↓ ۳.۷۲% — سیستم + + +
+
+ + +
+
+ + + + + +
+
+
+ +
+
+
+ + + نرخ دلار (USD) + +
+ +
+ ۳۸۷ هزار ریال +
+ +
+ + + ↑ ۱۳.۸۸% — سیستم + + +
+
+ + +
+
+ + + + + +
+
+
+ +
+
+
+ + + تغییرات امروز + +
+ +
+ ۰ +
+ +
+ + + تغییرات نرخ ثبت‌شده در امروز + + +
+
+ +
+ +
+
+
+ + + در انتظار تأیید + +
+ +
+ ۰ +
+ +
+ + + نرخ‌های پیشنهادی API — منتظر تأیید مدیر + + +
+
+ +
+ +
+
+ +
+
+ + +
+
+ + +

آخرین سفارشات

+
+ + مشاهده همه ← + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AWBمسیروضعیتمبلغتاریخ
+ + IFN-2026-77973 + + + Iran ← Aruba + + + تحویل به حمل‌کننده + + + ۳۲۵.۳ میلیون ریال + + 1405/07/10 +
+ + IFN-2026-29082 + + + Iran ← Armenia + + + تأیید شده + + + ۵۰.۸ میلیون ریال + + 1405/07/09 +
+ + IFN-2026-74634 + + + Iran ← Bahamas + + + در حال پردازش + + + ۲۱۱.۲ میلیون ریال + + 1405/07/09 +
+ + IFN-2026-34469 + + + Iran ← Angola + + + تأیید شده + + + ۱۷۷.۲ میلیون ریال + + 1405/07/08 +
+ + IFN-2026-39337 + + + Iran ← Anguilla + + + تأیید شده + + + ۱.۴۹ میلیارد ریال + + 1405/07/03 +
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +
+ + + + + +
+ +
+ +
+ +
+ + +
+ + + +
+ + +
+ + + +
+ +
+ + + + + +
+ + +
+ + + + +
+ +
+ + + + + +
+ +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +