feat(admin): add customer financial overview page
Implement a new Filament page to provide a comprehensive overview of customer financial data, including new page class and blade template.
This commit is contained in:
parent
e6c67e217a
commit
ab972fe3e8
230
04_Laravel/app/Filament/Pages/CustomerFinancialOverview.php
Normal file
230
04_Laravel/app/Filament/Pages/CustomerFinancialOverview.php
Normal file
@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Enums\ShipmentStatus;
|
||||
use App\Models\CustomerCredit;
|
||||
use App\Models\Shipment;
|
||||
use App\Models\User;
|
||||
use App\Models\WalletTransaction;
|
||||
use App\Services\CustomerCreditService;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* صفحهٔ «وضعیت مالی مشتری» — نمای یکپارچه مالی per-customer در پنل ادمین.
|
||||
*
|
||||
* مدیر/کارمند مشتری را انتخاب میکند و در یک نگاه میبیند:
|
||||
* - موجودی کیف پول
|
||||
* - کل بدهی (سفارشات تأییدشده پرداختنشده)
|
||||
* - کل پرداختی (تاریخچه)
|
||||
* - تعداد کل سفارشات
|
||||
* - اعتبارات چندارزی فعال
|
||||
* - سفارشات اخیر
|
||||
* - تراکنشهای اخیر
|
||||
*/
|
||||
class CustomerFinancialOverview extends Page
|
||||
{
|
||||
protected static ?string $navigationIcon = 'heroicon-o-banknotes';
|
||||
protected static ?string $navigationLabel = 'وضعیت مالی مشتری';
|
||||
protected static ?string $navigationGroup = 'مالی';
|
||||
protected static ?int $navigationSort = 5;
|
||||
protected static ?string $slug = 'customer-financial';
|
||||
protected static string $view = 'filament.pages.customer-financial-overview';
|
||||
|
||||
/** @var array فرم انتخاب مشتری */
|
||||
public ?array $data = [];
|
||||
|
||||
/** @var array|null دادههای مالی پس از انتخاب مشتری */
|
||||
public ?array $financialData = null;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->form->fill();
|
||||
}
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Select::make('customer_id')
|
||||
->label('انتخاب مشتری')
|
||||
->placeholder('نام، ایمیل یا شماره موبایل را جستجو کنید…')
|
||||
->options(fn () => $this->getCustomerOptions())
|
||||
->searchable()
|
||||
->reactive()
|
||||
->preload()
|
||||
->required(),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
/**
|
||||
* لیست مشتریان برای Select — کاربرانی که مشتری هستند (role = customer یا بدون نقش).
|
||||
*/
|
||||
protected function getCustomerOptions(): array
|
||||
{
|
||||
return User::query()
|
||||
->where('role', 'customer')
|
||||
->orWhereNull('role')
|
||||
->where('is_active', true)
|
||||
->orderBy('name')
|
||||
->limit(500)
|
||||
->get()
|
||||
->mapWithKeys(fn ($user) => [
|
||||
$user->id => "{$user->name}" . ($user->phone ? " — {$user->phone}" : ''),
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Livewire lifecycle hook — هنگام تغییر فرم صدا زده میشود.
|
||||
*/
|
||||
public function updated($property, $value): void
|
||||
{
|
||||
if ($property === 'data.customer_id') {
|
||||
if (!empty($value)) {
|
||||
$this->loadFinancialData((int) $value);
|
||||
} else {
|
||||
$this->financialData = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* بارگذاری دادههای مالی مشتری انتخابشده.
|
||||
*/
|
||||
public function loadFinancialData(int $customerId): void
|
||||
{
|
||||
$customer = User::with(['wallet'])->find($customerId);
|
||||
|
||||
if (!$customer) {
|
||||
$this->financialData = null;
|
||||
return;
|
||||
}
|
||||
|
||||
$creditService = app(CustomerCreditService::class);
|
||||
|
||||
// ─── KPI محاسبات ───
|
||||
$walletBalance = (float) ($customer->wallet?->balance ?? 0);
|
||||
|
||||
$totalPendingIrr = (float) Shipment::where('user_id', $customer->id)
|
||||
->where('status', ShipmentStatus::Approved)
|
||||
->sum('total_fee');
|
||||
|
||||
$pendingCount = Shipment::where('user_id', $customer->id)
|
||||
->where('status', ShipmentStatus::Approved)
|
||||
->count();
|
||||
|
||||
$totalOrders = Shipment::where('user_id', $customer->id)->count();
|
||||
|
||||
$totalPaid = (float) WalletTransaction::whereHas('wallet', fn ($q) => $q->where('user_id', $customer->id))
|
||||
->where('type', 'order_payment')
|
||||
->where('status', 'completed')
|
||||
->where('amount', '>', 0)
|
||||
->sum('amount');
|
||||
|
||||
$totalDeposits = (float) WalletTransaction::whereHas('wallet', fn ($q) => $q->where('user_id', $customer->id))
|
||||
->where('type', 'deposit')
|
||||
->where('status', 'completed')
|
||||
->where('amount', '>', 0)
|
||||
->sum('amount');
|
||||
|
||||
// بدهیهای چندارزی (EU/AED/USD)
|
||||
$foreignCredits = $creditService->getCustomerDebtsByCurrency($customer);
|
||||
|
||||
// اعتبارات فعال (ریکوردهای قابل ویرایش)
|
||||
$activeCredits = CustomerCredit::where('user_id', $customer->id)
|
||||
->whereIn('status', ['open', 'partially_settled'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
// سفارشات اخیر (۵ ردیف)
|
||||
$recentOrders = Shipment::where('user_id', $customer->id)
|
||||
->with(['fromCountry', 'toCountry'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
// تراکنشهای اخیر (۵ ردیف)
|
||||
$recentTransactions = WalletTransaction::whereHas('wallet', fn ($q) => $q->where('user_id', $customer->id))
|
||||
->with('wallet')
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
// مانده حساب = کیف پول منهای تعهدات ریالی
|
||||
$accountBalance = $walletBalance - $totalPendingIrr;
|
||||
|
||||
$this->financialData = [
|
||||
'customer' => [
|
||||
'id' => $customer->id,
|
||||
'name' => $customer->name,
|
||||
'email' => $customer->email,
|
||||
'phone' => $customer->phone,
|
||||
'created_at' => $customer->created_at?->format('Y/m/d'),
|
||||
],
|
||||
'kpis' => [
|
||||
[
|
||||
'label' => 'موجودی کیف پول',
|
||||
'value' => ifnex_format_number($walletBalance),
|
||||
'desc' => $walletBalance > 0 ? 'موجودی مثبت' : 'صفر یا منفی',
|
||||
'icon' => 'heroicon-o-wallet',
|
||||
'color' => 'blue',
|
||||
],
|
||||
[
|
||||
'label' => 'بدهی فعلی (سفارشات پرداختنشده)',
|
||||
'value' => ifnex_format_number($totalPendingIrr),
|
||||
'desc' => ifnex_persian_digits(number_format($pendingCount)) . ' سفارش تأییدشده',
|
||||
'icon' => 'heroicon-o-clock',
|
||||
'color' => $totalPendingIrr > 0 ? 'red' : 'gray',
|
||||
],
|
||||
[
|
||||
'label' => 'کل پرداختی (تاریخچه)',
|
||||
'value' => ifnex_format_number($totalPaid),
|
||||
'desc' => 'مجموع پرداختهای موفق',
|
||||
'icon' => 'heroicon-o-arrow-trending-up',
|
||||
'color' => 'green',
|
||||
],
|
||||
[
|
||||
'label' => 'مانده حساب',
|
||||
'value' => ifnex_format_number($accountBalance),
|
||||
'desc' => $accountBalance >= 0 ? 'موجودیساز' : 'بدهکار — منفی',
|
||||
'icon' => 'heroicon-o-banknotes',
|
||||
'color' => $accountBalance >= 0 ? 'green' : 'red',
|
||||
],
|
||||
],
|
||||
'walletBalance' => $walletBalance,
|
||||
'totalPendingIrr' => $totalPendingIrr,
|
||||
'totalOrders' => $totalOrders,
|
||||
'totalDeposits' => $totalDeposits,
|
||||
'accountBalance' => $accountBalance,
|
||||
'foreignCredits' => $foreignCredits,
|
||||
'activeCredits' => $activeCredits,
|
||||
'recentOrders' => $recentOrders,
|
||||
'recentTransactions' => $recentTransactions,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* فقط مدیر و کارمند میتوانند این صفحه را ببینند.
|
||||
*/
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
$user = auth()->user();
|
||||
if (!$user) return false;
|
||||
|
||||
// مدیر یا کارمند
|
||||
return $user->hasRole('super_admin')
|
||||
|| $user->hasRole('admin')
|
||||
|| $user->hasRole('tracking_operator')
|
||||
|| $user->hasRole('data_entry')
|
||||
|| $user->role === 'super_admin'
|
||||
|| $user->role === 'admin'
|
||||
|| $user->role === 'tracking_operator'
|
||||
|| $user->role === 'data_entry';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,392 @@
|
||||
{{--
|
||||
IFNEX — Customer Financial Overview page.
|
||||
Director/staff selects a customer, sees complete financial status:
|
||||
KPIs + multi-currency credits + recent orders + recent transactions.
|
||||
--}}
|
||||
<x-filament-panels::page>
|
||||
<div class="space-y-6">
|
||||
|
||||
<style>
|
||||
/* ════════ KPI Grid & Cards (matching dashboard design) ════════ */
|
||||
.ifnex-kpi-grid {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(4, 1fr) !important;
|
||||
gap: 1rem !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.ifnex-kpi-grid {
|
||||
grid-template-columns: repeat(2, 1fr) !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.ifnex-kpi-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
}
|
||||
.ifnex-kpi-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.875rem;
|
||||
padding: 1.25rem;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
.dark .ifnex-kpi-card {
|
||||
background: rgb(30 41 59);
|
||||
border-color: rgb(51 65 85);
|
||||
}
|
||||
.ifnex-kpi-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);
|
||||
}
|
||||
.ifnex-kpi-icon-wrap {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ifnex-kpi-icon-amber { background: #fef3c7; color: #d97706; }
|
||||
.ifnex-kpi-icon-green { background: #d1fae5; color: #059669; }
|
||||
.ifnex-kpi-icon-red { background: #fee2e2; color: #dc2626; }
|
||||
.ifnex-kpi-icon-blue { background: #dbeafe; color: #2563eb; }
|
||||
.ifnex-kpi-icon-gray { background: #f1f5f9; color: #475569; }
|
||||
.dark .ifnex-kpi-icon-amber { background: rgba(245,158,11,.18); color: #fbbf24; }
|
||||
.dark .ifnex-kpi-icon-green { background: rgba(16,185,129,.18); color: #34d399; }
|
||||
.dark .ifnex-kpi-icon-red { background: rgba(239,68,68,.18); color: #f87171; }
|
||||
.dark .ifnex-kpi-icon-blue { background: rgba(59,130,246,.18); color: #60a5fa; }
|
||||
.dark .ifnex-kpi-icon-gray { background: rgba(148,163,184,.18); color: #cbd5e1; }
|
||||
.ifnex-kpi-body { flex: 1; min-width: 0; }
|
||||
.ifnex-kpi-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.dark .ifnex-kpi-label { color: #9ca3af; }
|
||||
.ifnex-kpi-value {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: #111827;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.5px;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.dark .ifnex-kpi-value { color: #f9fafb; }
|
||||
.ifnex-kpi-desc {
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.dark .ifnex-kpi-desc { color: #6b7280; }
|
||||
|
||||
/* ════════ 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 styling ════════ */
|
||||
.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; }
|
||||
.ifnex-table-row-hover:hover { background-color: #fffbeb !important; }
|
||||
.dark .ifnex-table-row-hover:hover { background-color: rgba(245, 158, 11, 0.08) !important; }
|
||||
.ifnex-link-inline { display: inline-flex; align-items: center; gap: 2px; }
|
||||
|
||||
/* ════════ Buttons ════════ */
|
||||
.ifnex-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
.ifnex-btn-sm { padding: 0.375rem 0.75rem; font-size: 12px; }
|
||||
.ifnex-btn-primary { background: linear-gradient(135deg, #f59e0b 0%, #FDB913 100%); color: #ffffff !important; }
|
||||
.ifnex-btn-primary:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(245,158,11,0.35); color: #fff !important; }
|
||||
.ifnex-btn-info { background: #dbeafe; color: #1d4ed8 !important; }
|
||||
.ifnex-btn-info:hover { background: #bfdbfe; color: #1e40af !important; }
|
||||
.ifnex-btn-success { background: #d1fae5; color: #047857 !important; }
|
||||
.ifnex-btn-success:hover { background: #a7f3d0; color: #065f46 !important; }
|
||||
.dark .ifnex-btn-info { background: rgba(59,130,246,.18); color: #93c5fd !important; }
|
||||
.dark .ifnex-btn-primary { color: #fff !important; }
|
||||
</style>
|
||||
|
||||
{{-- ════════ Customer Selector ════════ --}}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 p-5">
|
||||
<h3 class="text-base font-bold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
||||
<x-heroicon-o-magnifying-glass class="w-5 h-5 text-amber-500" />
|
||||
جستجوی مشتری
|
||||
</h3>
|
||||
{{ $this->form }}
|
||||
</div>
|
||||
|
||||
{{-- ════════ Financial Overview (only when customer selected) ════════ --}}
|
||||
@if(!empty($financialData))
|
||||
|
||||
{{-- Customer Info + KPIs --}}
|
||||
<div class="space-y-4">
|
||||
|
||||
{{-- Customer header --}}
|
||||
<div class="bg-gradient-to-l from-amber-50 to-white dark:from-gray-800 dark:to-gray-900 rounded-2xl border border-amber-200 dark:border-gray-700 p-5">
|
||||
<div class="flex items-center justify-between flex-wrap gap-3">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
{{ $financialData['customer']['name'] }}
|
||||
</h2>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
@if($financialData['customer']['phone'])
|
||||
<span class="ltr:inline-block">{{ $financialData['customer']['phone'] }}</span>
|
||||
@endif
|
||||
@if($financialData['customer']['email'])
|
||||
— {{ $financialData['customer']['email'] }}
|
||||
@endif
|
||||
— عضو از {{ $financialData['customer']['created_at'] }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="{{ route('filament.admin.resources.customer-credits.create') }}?user_id={{ $financialData['customer']['id'] }}"
|
||||
class="ifnex-btn ifnex-btn-sm ifnex-btn-primary">
|
||||
<x-heroicon-o-plus class="w-4 h-4 inline" /> اعتبار جدید
|
||||
</a>
|
||||
<a href="{{ route('filament.admin.resources.shipments.index') }}?tableFilters[user_id][value]={{ $financialData['customer']['id'] }}"
|
||||
class="ifnex-btn ifnex-btn-sm ifnex-btn-info">
|
||||
<x-heroicon-o-truck class="w-4 h-4 inline" /> سفارشات
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- KPI Grid --}}
|
||||
<div class="ifnex-kpi-grid">
|
||||
@foreach($financialData['kpis'] as $kpi)
|
||||
<div class="ifnex-kpi-card ifnex-kpi-{{ $kpi['color'] }}">
|
||||
<div class="ifnex-kpi-icon-wrap ifnex-kpi-icon-{{ $kpi['color'] }}">
|
||||
<x-dynamic-component :component="$kpi['icon']" class="w-6 h-6" />
|
||||
</div>
|
||||
<div class="ifnex-kpi-body">
|
||||
<div class="ifnex-kpi-label">{{ $kpi['label'] }}</div>
|
||||
<div class="ifnex-kpi-value">{{ $kpi['value'] }}</div>
|
||||
<div class="ifnex-kpi-desc">{{ $kpi['desc'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Multi-Currency Credits --}}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<div class="px-5 py-4 border-b border-gray-100 dark:border-gray-700 flex items-center gap-2.5">
|
||||
<span class="inline-flex items-center justify-center w-9 h-9 rounded-xl bg-amber-100 dark:bg-amber-900/40 text-amber-600 dark:text-amber-400">
|
||||
<x-heroicon-o-globe-alt class="w-5 h-5" />
|
||||
</span>
|
||||
<h3 class="text-base font-bold text-gray-900 dark:text-white">بدهیهای چندارزی</h3>
|
||||
</div>
|
||||
@if(empty($financialData['foreignCredits']))
|
||||
<div class="p-8 text-center">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">بدهی چندارزی فعال ندارد ✅</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 dark:bg-gray-900/40 text-gray-500 dark:text-gray-400 border-b border-gray-100 dark:border-gray-700">
|
||||
<th class="ifnex-th text-right">ارز</th>
|
||||
<th class="ifnex-th text-right">مبلغ کل</th>
|
||||
<th class="ifnex-th text-right">تسویهشده</th>
|
||||
<th class="ifnex-th text-right">مانده</th>
|
||||
<th class="ifnex-th text-right">تعداد رکورد</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50 dark:divide-gray-700/60">
|
||||
@foreach($financialData['foreignCredits'] as $credit)
|
||||
<tr class="ifnex-table-row-hover transition-colors duration-150">
|
||||
<td class="ifnex-td">
|
||||
<span class="ifnex-badge ifnex-badge-info">{{ $credit['currency_code'] }}</span>
|
||||
</td>
|
||||
<td class="ifnex-td font-semibold">{{ number_format($credit['total_amount'], 2) }}</td>
|
||||
<td class="ifnex-td text-green-600">{{ number_format($credit['total_settled'], 2) }}</td>
|
||||
<td class="ifnex-td font-bold text-red-600">{{ number_format($credit['balance'], 2) }}</td>
|
||||
<td class="ifnex-td text-gray-500">{{ ifnex_persian_digits(number_format($credit['count'])) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Recent Orders + Transactions (side by side) --}}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
|
||||
{{-- Recent Orders --}}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<div class="px-5 py-4 border-b border-gray-100 dark:border-gray-700 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span class="inline-flex items-center justify-center w-9 h-9 rounded-xl bg-amber-100 dark:bg-amber-900/40 text-amber-600 dark:text-amber-400">
|
||||
<x-heroicon-o-truck class="w-5 h-5" />
|
||||
</span>
|
||||
<h3 class="text-base font-bold text-gray-900 dark:text-white">سفارشات اخیر</h3>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500">{{ ifnex_persian_digits(number_format($financialData['totalOrders'])) }} سفارش کل</span>
|
||||
</div>
|
||||
@if($financialData['recentOrders']->isEmpty())
|
||||
<div class="p-6 text-center text-sm text-gray-500">سفارشی ثبت نشده است</div>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 dark:bg-gray-900/40 text-gray-500 border-b border-gray-100 dark:border-gray-700">
|
||||
<th class="ifnex-th text-right">AWB</th>
|
||||
<th class="ifnex-th text-right">مسیر</th>
|
||||
<th class="ifnex-th text-right">وضعیت</th>
|
||||
<th class="ifnex-th text-right">مبلغ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50 dark:divide-gray-700/60">
|
||||
@foreach($financialData['recentOrders'] as $order)
|
||||
<tr class="ifnex-table-row-hover transition-colors duration-150">
|
||||
<td class="ifnex-td">
|
||||
<a href="{{ route('filament.admin.resources.shipments.view', $order) }}"
|
||||
class="font-mono text-xs font-bold text-gray-900 dark:text-white hover:text-amber-600 transition-colors">
|
||||
{{ $order->awb_no }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="ifnex-td text-xs text-gray-700 dark:text-gray-300">
|
||||
{{ $order->fromCountry?->name }} ← {{ $order->toCountry?->name }}
|
||||
</td>
|
||||
<td class="ifnex-td">
|
||||
@php
|
||||
$stColor = match($order->status?->value) {
|
||||
'approved' => 'ifnex-badge-info',
|
||||
'delivered' => 'ifnex-badge-success',
|
||||
'cancelled', 'failed' => 'ifnex-badge-danger',
|
||||
'pending_approval', 'pending_payment' => 'ifnex-badge-warning',
|
||||
default => 'ifnex-badge-gray',
|
||||
};
|
||||
@endphp
|
||||
<span class="ifnex-badge {{ $stColor }}">
|
||||
{{ $order->status?->label() ?? '—' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="ifnex-td text-xs font-semibold">
|
||||
{{ $order->total_fee ? ifnex_format_number($order->total_fee) : '—' }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Recent Transactions --}}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<div class="px-5 py-4 border-b border-gray-100 dark:border-gray-700 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span class="inline-flex items-center justify-center w-9 h-9 rounded-xl bg-blue-100 dark:bg-blue-900/40 text-blue-600 dark:text-blue-400">
|
||||
<x-heroicon-o-arrow-path class="w-5 h-5" />
|
||||
</span>
|
||||
<h3 class="text-base font-bold text-gray-900 dark:text-white">تراکنشهای اخیر</h3>
|
||||
</div>
|
||||
<a href="{{ route('filament.admin.resources.wallet-transactions.index') }}"
|
||||
class="ifnex-link-inline text-xs font-semibold text-amber-600 hover:text-amber-700">
|
||||
مشاهده همه ←
|
||||
</a>
|
||||
</div>
|
||||
@if($financialData['recentTransactions']->isEmpty())
|
||||
<div class="p-6 text-center text-sm text-gray-500">تراکنشی ثبت نشده است</div>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 dark:bg-gray-900/40 text-gray-500 border-b border-gray-100 dark:border-gray-700">
|
||||
<th class="ifnex-th text-right">نوع</th>
|
||||
<th class="ifnex-th text-right">مبلغ</th>
|
||||
<th class="ifnex-th text-right">توضیحات</th>
|
||||
<th class="ifnex-th text-right">تاریخ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50 dark:divide-gray-700/60">
|
||||
@foreach($financialData['recentTransactions'] as $tx)
|
||||
<tr class="ifnex-table-row-hover transition-colors duration-150">
|
||||
<td class="ifnex-td">
|
||||
@php
|
||||
$txColor = match($tx->type?->value) {
|
||||
'deposit' => 'ifnex-badge-success',
|
||||
'withdrawal' => 'ifnex-badge-danger',
|
||||
'order_payment' => 'ifnex-badge-warning',
|
||||
'refund' => 'ifnex-badge-info',
|
||||
default => 'ifnex-badge-gray',
|
||||
};
|
||||
@endphp
|
||||
<span class="ifnex-badge {{ $txColor }}">
|
||||
{{ $tx->type?->label() ?? '—' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="ifnex-td text-xs font-semibold {{ $tx->amount >= 0 ? 'text-green-600' : 'text-red-600' }}">
|
||||
{{ $tx->amount >= 0 ? '+' : '' }}{{ ifnex_format_number(abs($tx->amount)) }}
|
||||
</td>
|
||||
<td class="ifnex-td text-xs text-gray-700 dark:text-gray-300">
|
||||
{{ \Illuminate\Support\Str::limit($tx->description ?? '—', 30) }}
|
||||
</td>
|
||||
<td class="ifnex-td text-xs text-gray-500">
|
||||
{{ \Morilog\Jalali\Jalalian::fromCarbon($tx->created_at)->format('Y/m/d H:i') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@else
|
||||
{{-- Empty state --}}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl border border-dashed border-gray-300 dark:border-gray-600 p-12 text-center">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-amber-100 dark:bg-amber-900/30 text-amber-500 mb-4">
|
||||
<x-heroicon-o-banknotes class="w-8 h-8" />
|
||||
</div>
|
||||
<h3 class="text-base font-bold text-gray-900 dark:text-white mb-1">
|
||||
مشتری را انتخاب کنید
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
برای مشاهده وضعیت مالی کامل، از فرم بالا یک مشتری جستجو و انتخاب کنید.
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</x-filament-panels::page>
|
||||
Loading…
Reference in New Issue
Block a user