feat(ui): implement unified design system and financial reporting

Introduce a comprehensive design system across WordPress and Laravel
to ensure visual consistency. Implement new financial reporting
capabilities and Excel export functionality within the Filament admin
panel.

- Add `DESIGN_SYSTEM.md` to define brand colors, typography, and spacing
- Implement `FinancialReport` page in Filament for transaction and
  shipment summaries
- Add Excel export support for `ShipmentResource` and
  `WalletTransactionResource` using Laravel Excel
- Refactor WordPress plugin and Filament CSS to utilize the new
  design system variables
- Update project status to reflect new reporting and design milestones
This commit is contained in:
Kazem Alghasi 2026-08-08 06:13:09 +03:30
parent cb55f74cb7
commit 8b0814230c
12 changed files with 1578 additions and 365 deletions

View File

@ -0,0 +1,179 @@
# IFNEX Design System
## 🎨 رنگ‌های برند
### رنگ‌های اصلی
| نام | HEX | استفاده |
|-----|-----|---------|
| Primary Amber | `#f59e0b` | دکمه‌های اصلی، لینک‌ها، آیکون‌های فعال |
| Primary Dark | `#d97706` | هاور دکمه‌ها |
| Primary Light | `#fbbf24` | زمینه‌های روشن، هایلایت |
| Success Green | `#059669` | عملیات موفق، موجودی مثبت |
| Danger Red | `#dc2626` | خطاها، موجودی منفی |
| Warning Yellow | `#f59e0b` | هشدارها، در حال پردازش |
| Info Blue | `#2563eb` | اطلاعات، لینک‌ها |
### رنگ‌های خنثی
| نام | HEX | استفاده |
|-----|-----|---------|
| Gray 900 | `#111827` | متن‌های اصلی |
| Gray 700 | `#374151` | متن‌های ثانویه |
| Gray 500 | `#6b7280` | توضیحات، placeholder |
| Gray 300 | `#d1d5db` | حاشیه‌ها |
| Gray 100 | `#f3f4f6` | پس‌زمینه‌های روشن |
| White | `#ffffff` | پس‌زمینه کارت‌ها |
### رنگ‌های Sidebar
| نام | HEX | استفاده |
|-----|-----|---------|
| Sidebar Dark | `#1a1a2e` | پس‌زمینه اصلی سایدبار |
| Sidebar Blue | `#16213e` | گرادینت سایدبار |
| Sidebar Active | `rgba(245, 158, 11, 0.15)` | آیتم فعال |
## 📐 فاصله‌گذاری (Spacing)
| نام | مقدار | استفاده |
|-----|-------|---------|
| xs | 0.25rem (4px) | فاصله‌های خیلی کوچک |
| sm | 0.5rem (8px) | فاصله‌های کوچک |
| md | 1rem (16px) | فاصله‌های استاندارد |
| lg | 1.5rem (24px) | فاصله‌های بزرگ |
| xl | 2rem (32px) | فاصله‌های خیلی بزرگ |
| 2xl | 3rem (48px) | فاصله‌های بخش‌ها |
## 🔤 تایپوگرافی
### فونت اصلی
- **فامیلی:** Vazirmatn, IranYekan, ui-sans-serif, system-ui, sans-serif
- **سایز پایه:** 14px
- **خط:** 1.6
### سایزهای فونت
| نام | اندازه | وزن | استفاده |
|-----|--------|-----|---------|
| xs | 0.75rem (12px) | 400 | توضیحات، watermark |
| sm | 0.875rem (14px) | 400 | متن‌های استاندارد |
| base | 1rem (16px) | 400 | متن‌های اصلی |
| lg | 1.125rem (18px) | 500 | زیرعنوان‌ها |
| xl | 1.25rem (20px) | 600 | عنوان‌های صفحه |
| 2xl | 1.5rem (24px) | 700 | عنوان‌های اصلی |
| 3xl | 1.875rem (30px) | 700 | Hero text |
## 📦 کارت‌ها و کادرها
### استایل کارت
- **Border Radius:** 0.75rem (12px)
- **Border:** 1px solid #e5e7eb
- **Shadow:** 0 1px 3px rgba(0, 0, 0, 0.05)
- **Padding:** 1.25rem
- **Background:** #ffffff
### استایل کارت statistic
- **Border Radius:** 0.75rem (12px)
- **Padding:** 1.25rem
- **Title:** 0.875rem, weight 500, color #6b7280
- **Value:** 1.5rem, weight 700, color #111827
## 🔘 دکمه‌ها
### Primary Button
- **Background:** #f59e0b
- **Border:** #f59e0b
- **Color:** #ffffff
- **Border Radius:** 0.5rem (8px)
- **Padding:** 0.625rem 1.25rem
- **Font Size:** 0.875rem
- **Hover:** #d97706, translateY(-1px), shadow
### Secondary Button
- **Background:** #ffffff
- **Border:** #d1d5db
- **Color:** #374151
- **Hover:** #f9fafb
## 📊 جداول
### Header
- **Background:** #f9fafb
- **Font Size:** 0.875rem
- **Font Weight:** 600
- **Text Transform:** uppercase
- **Letter Spacing:** 0.025em
- **Color:** #374151
- **Padding:** 0.75rem 1rem
### Cell
- **Padding:** 0.75rem 1rem
- **Font Size:** 0.875rem
- **Color:** #374151
- **Border Bottom:** 1px solid #f3f4f6
### Hover
- **Background:** #fffbeb
## 📱 رسپانسیو
### Breakpoints
- **Mobile:** < 768px
- **Tablet:** 768px - 1024px
- **Desktop:** > 1024px
### Mobile Adjustments
- **Font Size:** 13px
- **Padding:** 0.75rem
- **Grid:** 1 column
- **Cards:** Full width
### Tablet Adjustments
- **Grid:** 2 columns
- **Padding:** 1rem
## 🎯 چیدوبندی (Layout)
### Container
- **Max Width:** 1400px
- **Padding:** 1.5rem
- **Margin:** 0 auto
### Grid System
- **Gap:** 1rem
- **Columns:** auto-fit, minmax(240px, 1fr)
## 🌙 حالت تاریک (Dark Mode)
### در حال توسعه...
- پس‌زمینه: #0f172a
- متن: #f1f5f9
- کارت: #1e293b
- حاشیه: #334155
---
## 📋 پیاده‌سازی در Filament
```php
// AdminPanelProvider.php
->colors([
'primary' => Color::Amber, // #f59e0b
])
->sidebarWidth('18rem')
->maxContentWidth('xl')
```
## 📋 پیاده‌سازی در WordPress
```css
/* استفاده از متغیرهای CSS */
:root {
--ifnex-primary: #f59e0b;
--ifnex-primary-dark: #d97706;
--ifnex-success: #059669;
--ifnex-danger: #dc2626;
--ifnex-gray-900: #111827;
--ifnex-gray-700: #374151;
--ifnex-gray-500: #6b7280;
--ifnex-gray-300: #d1d5db;
--ifnex-gray-100: #f3f4f6;
--ifnex-white: #ffffff;
}
```

View File

@ -552,6 +552,11 @@ IFNEX-Logistics/
- ساخت دستور Artisan `ifnex:sync-wp-users` برای یک‌سازی کاربران وردپرس ↔ لاراول
- تست زنده پلاگین وردپرس — همه APIها با موفقیت تست شدند
- پاک‌سازی فایل‌های تست و اضافی از ریشه پروژه
- افزودن خروجی Excel برای تراکنش‌ها و مرسولات در Filament
- ساخت صفحه گزارش‌گیری مالی در Filament: خلاصه مالی، تراکنش‌ها، مرسولات
- ساخت Design System یکپارچه IFNEX برای استفاده در Filament و وردپرس
- به‌روزرسانی استایل‌های Filament مطابق با Design System
- به‌روزرسانی استایل‌های پلاگین وردپرس مطابق با Design System

View File

@ -1,384 +1,450 @@
/**
* IFNEX Bridge Styles
* استایل‌های پلاگین IFNEX Logistics Bridge
* بر اساس IFNEX Design System
*/
/* Wallet Balance */
:root {
--ifnex-primary: #f59e0b;
--ifnex-primary-dark: #d97706;
--ifnex-primary-light: #fbbf24;
--ifnex-success: #059669;
--ifnex-success-light: #d1fae5;
--ifnex-danger: #dc2626;
--ifnex-danger-light: #fee2e2;
--ifnex-warning: #f59e0b;
--ifnex-warning-light: #fef3c7;
--ifnex-info: #2563eb;
--ifnex-info-light: #dbeafe;
--ifnex-gray-900: #111827;
--ifnex-gray-700: #374151;
--ifnex-gray-600: #4b5563;
--ifnex-gray-500: #6b7280;
--ifnex-gray-400: #9ca3af;
--ifnex-gray-300: #d1d5db;
--ifnex-gray-200: #e5e7eb;
--ifnex-gray-100: #f3f4f6;
--ifnex-gray-50: #f9fafb;
--ifnex-white: #ffffff;
--ifnex-font-family: 'Vazirmatn', 'IranYekan', ui-sans-serif, system-ui, sans-serif;
--ifnex-radius-sm: 0.375rem;
--ifnex-radius-md: 0.5rem;
--ifnex-radius-lg: 0.75rem;
}
/* ============================================
GLOBAL
============================================ */
.ifnex-widget,
.ifnex-tracking-container,
.ifnex-tracking-result,
.ifnex-transactions,
.ifnex-wallet-balance {
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 20px;
margin: 15px 0;
font-family: inherit;
direction: rtl;
text-align: right;
font-family: var(--ifnex-font-family) !important;
direction: rtl !important;
text-align: right !important;
color: var(--ifnex-gray-900) !important;
}
/* ============================================
WALLET BALANCE
============================================ */
.ifnex-wallet-balance {
background: var(--ifnex-white) !important;
border: 1px solid var(--ifnex-gray-200) !important;
border-radius: var(--ifnex-radius-lg) !important;
padding: 1.25rem !important;
margin: 1rem 0 !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important;
}
.ifnex-balance-main {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #e5e7eb;
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
margin-bottom: 1rem !important;
padding-bottom: 1rem !important;
border-bottom: 1px solid var(--ifnex-gray-200) !important;
}
.ifnex-balance-label {
font-size: 14px;
color: #6b7280;
font-weight: 500;
font-size: 0.875rem !important;
color: var(--ifnex-gray-500) !important;
font-weight: 500 !important;
}
.ifnex-balance-amount {
font-size: 24px;
font-weight: 700;
color: #059669;
font-size: 1.5rem !important;
font-weight: 700 !important;
color: var(--ifnex-success) !important;
}
.ifnex-balance-details {
margin-top: 10px;
margin-top: 1rem !important;
}
.ifnex-balance-details p {
margin: 5px 0;
font-size: 14px;
color: #374151;
margin: 0.5rem 0 !important;
font-size: 0.875rem !important;
color: var(--ifnex-gray-700) !important;
}
.ifnex-balance-details strong {
color: #6b7280;
margin-left: 8px;
color: var(--ifnex-gray-500) !important;
margin-left: 0.5rem !important;
}
.ifnex-wallet-frozen {
color: #dc2626;
font-weight: 600;
margin-top: 10px;
padding: 8px 12px;
background: #fee2e2;
border-radius: 6px;
border-right: 3px solid #dc2626;
color: var(--ifnex-danger) !important;
font-weight: 600 !important;
margin-top: 1rem !important;
padding: 0.75rem 1rem !important;
background: var(--ifnex-danger-light) !important;
border-radius: var(--ifnex-radius-md) !important;
border-right: 3px solid var(--ifnex-danger) !important;
}
/* Transactions */
/* ============================================
TRANSACTIONS
============================================ */
.ifnex-transactions {
margin: 15px 0;
font-family: inherit;
direction: rtl;
text-align: right;
margin: 1rem 0 !important;
}
.ifnex-transactions h3 {
font-size: 18px;
font-weight: 600;
color: #111827;
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 2px solid #f59e0b;
font-size: 1.125rem !important;
font-weight: 600 !important;
color: var(--ifnex-gray-900) !important;
margin-bottom: 1rem !important;
padding-bottom: 0.5rem !important;
border-bottom: 2px solid var(--ifnex-primary) !important;
}
.ifnex-transactions-list {
display: flex;
flex-direction: column;
gap: 12px;
display: flex !important;
flex-direction: column !important;
gap: 0.75rem !important;
}
.ifnex-transaction-item {
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 16px;
transition: all 0.2s ease;
background: var(--ifnex-white) !important;
border: 1px solid var(--ifnex-gray-200) !important;
border-radius: var(--ifnex-radius-lg) !important;
padding: 1rem !important;
transition: all 0.2s ease !important;
}
.ifnex-transaction-item:hover {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
border-color: #f59e0b;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important;
border-color: var(--ifnex-primary-light) !important;
}
.ifnex-transaction-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
margin-bottom: 0.5rem !important;
}
.ifnex-transaction-type {
display: inline-block;
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
background: #dbeafe;
color: #1e40af;
display: inline-block !important;
padding: 0.25rem 0.75rem !important;
border-radius: var(--ifnex-radius-sm) !important;
font-size: 0.75rem !important;
font-weight: 500 !important;
text-transform: uppercase !important;
background: var(--ifnex-info-light) !important;
color: var(--ifnex-info) !important;
}
.ifnex-transaction-type.deposit {
background: #d1fae5;
color: #065f46;
}
.ifnex-transaction-type.withdrawal {
background: #fee2e2;
color: #991b1b;
background: var(--ifnex-success-light) !important;
color: var(--ifnex-success) !important;
}
.ifnex-transaction-type.withdrawal,
.ifnex-transaction-type.order_payment {
background: #fef3c7;
color: #92400e;
background: var(--ifnex-danger-light) !important;
color: var(--ifnex-danger) !important;
}
.ifnex-transaction-type.refund {
background: #e0e7ff;
color: #3730a3;
background: var(--ifnex-info-light) !important;
color: var(--ifnex-info) !important;
}
.ifnex-transaction-amount {
font-size: 16px;
font-weight: 700;
font-family: 'Vazirmatn', 'IranYekan', sans-serif;
font-size: 1rem !important;
font-weight: 700 !important;
font-family: var(--ifnex-font-family) !important;
}
.ifnex-transaction-amount.positive {
color: #059669;
color: var(--ifnex-success) !important;
}
.ifnex-transaction-amount.negative {
color: #dc2626;
color: var(--ifnex-danger) !important;
}
.ifnex-transaction-details {
margin-top: 8px;
margin-top: 0.5rem !important;
}
.ifnex-transaction-description {
color: #4b5563;
font-size: 14px;
margin: 0 0 6px 0;
color: var(--ifnex-gray-700) !important;
font-size: 0.875rem !important;
margin: 0 0 0.5rem 0 !important;
}
.ifnex-transaction-meta {
display: flex;
gap: 12px;
font-size: 12px;
color: #6b7280;
display: flex !important;
gap: 0.75rem !important;
font-size: 0.75rem !important;
color: var(--ifnex-gray-500) !important;
}
.ifnex-transaction-status {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
background: #f3f4f6;
color: #374151;
font-size: 11px;
display: inline-block !important;
padding: 0.125rem 0.5rem !important;
border-radius: var(--ifnex-radius-sm) !important;
background: var(--ifnex-gray-100) !important;
color: var(--ifnex-gray-700) !important;
font-size: 0.75rem !important;
}
.ifnex-transaction-status.completed {
background: #d1fae5;
color: #065f46;
background: var(--ifnex-success-light) !important;
color: var(--ifnex-success) !important;
}
.ifnex-transaction-status.pending {
background: #fef3c7;
color: #92400e;
background: var(--ifnex-warning-light) !important;
color: #92400e !important;
}
.ifnex-transaction-status.failed {
background: #fee2e2;
color: #991b1b;
background: var(--ifnex-danger-light) !important;
color: var(--ifnex-danger) !important;
}
.ifnex-transaction-ref {
margin-top: 6px;
font-size: 12px;
color: #6b7280;
font-family: monospace;
margin-top: 0.5rem !important;
font-size: 0.75rem !important;
color: var(--ifnex-gray-500) !important;
font-family: monospace !important;
}
/* Tracking Status */
/* ============================================
TRACKING FORM
============================================ */
.ifnex-tracking-container {
max-width: 700px;
margin: 20px auto;
padding: 24px;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 12px;
font-family: inherit;
direction: rtl;
text-align: right;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
max-width: 700px !important;
margin: 1.5rem auto !important;
padding: 1.5rem !important;
background: var(--ifnex-white) !important;
border: 1px solid var(--ifnex-gray-200) !important;
border-radius: var(--ifnex-radius-lg) !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important;
}
.ifnex-tracking-container h2 {
font-size: 20px;
font-weight: 700;
color: #111827;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f59e0b;
font-size: 1.25rem !important;
font-weight: 700 !important;
color: var(--ifnex-gray-900) !important;
margin-bottom: 1rem !important;
padding-bottom: 0.5rem !important;
border-bottom: 2px solid var(--ifnex-primary) !important;
}
.ifnex-tracking-form {
margin-bottom: 20px;
margin-bottom: 1.5rem !important;
}
.ifnex-form-group {
display: flex;
gap: 10px;
display: flex !important;
gap: 0.75rem !important;
}
.ifnex-form-group input {
flex: 1;
padding: 12px 16px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 15px;
font-family: inherit;
transition: border-color 0.2s ease;
flex: 1 !important;
padding: 0.75rem 1rem !important;
border: 1px solid var(--ifnex-gray-300) !important;
border-radius: var(--ifnex-radius-md) !important;
font-size: 0.9375rem !important;
font-family: var(--ifnex-font-family) !important;
transition: var(--ifnex-transition) !important;
}
.ifnex-form-group input:focus {
outline: none;
border-color: #f59e0b;
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.1);
outline: none !important;
border-color: var(--ifnex-primary) !important;
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.1) !important;
}
.ifnex-form-group button {
padding: 12px 24px;
background: #f59e0b;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 15px;
font-weight: 600;
font-family: inherit;
transition: all 0.2s ease;
padding: 0.75rem 1.5rem !important;
background: var(--ifnex-primary) !important;
color: var(--ifnex-white) !important;
border: none !important;
border-radius: var(--ifnex-radius-md) !important;
cursor: pointer !important;
font-size: 0.9375rem !important;
font-weight: 600 !important;
font-family: var(--ifnex-font-family) !important;
transition: var(--ifnex-transition) !important;
}
.ifnex-form-group button:hover {
background: #d97706;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
background: var(--ifnex-primary-dark) !important;
transform: translateY(-1px) !important;
box-shadow: var(--ifnex-shadow-md) !important;
}
.ifnex-form-group button:disabled {
background: #9ca3af;
cursor: not-allowed;
transform: none;
box-shadow: none;
background: var(--ifnex-gray-400) !important;
cursor: not-allowed !important;
transform: none !important;
box-shadow: none !important;
}
.ifnex-tracking-result {
margin-top: 20px;
padding: 20px;
background: #f9fafb;
border-radius: 8px;
border: 1px solid #e5e7eb;
margin-top: 1.5rem !important;
padding: 1.25rem !important;
background: var(--ifnex-gray-50) !important;
border-radius: var(--ifnex-radius-md) !important;
border: 1px solid var(--ifnex-gray-200) !important;
}
.ifnex-error {
color: #dc2626;
font-weight: 500;
padding: 10px;
background: #fee2e2;
border-radius: 6px;
border-right: 3px solid #dc2626;
color: var(--ifnex-danger) !important;
font-weight: 500 !important;
padding: 0.75rem 1rem !important;
background: var(--ifnex-danger-light) !important;
border-radius: var(--ifnex-radius-md) !important;
border-right: 3px solid var(--ifnex-danger) !important;
}
.ifnex-tracking-status h3 {
margin-top: 0;
font-size: 18px;
font-weight: 700;
color: #111827;
margin-top: 0 !important;
font-size: 1.125rem !important;
font-weight: 700 !important;
color: var(--ifnex-gray-900) !important;
}
.ifnex-status-badge {
display: inline-block;
padding: 6px 14px;
background: #f59e0b;
color: white;
border-radius: 6px;
margin-bottom: 12px;
font-weight: 600;
font-size: 14px;
display: inline-block !important;
padding: 0.375rem 0.875rem !important;
background: var(--ifnex-primary) !important;
color: var(--ifnex-white) !important;
border-radius: var(--ifnex-radius-md) !important;
margin-bottom: 0.75rem !important;
font-weight: 600 !important;
font-size: 0.875rem !important;
}
.ifnex-status-badge.delivered {
background: #059669;
background: var(--ifnex-success) !important;
}
.ifnex-status-badge.failed {
background: #dc2626;
background: var(--ifnex-danger) !important;
}
.ifnex-status-badge.in_transit {
background: #2563eb;
background: var(--ifnex-info) !important;
}
.ifnex-tracking-details {
margin-top: 12px;
margin-top: 1rem !important;
}
.ifnex-tracking-details p {
margin: 6px 0;
font-size: 14px;
color: #4b5563;
margin: 0.5rem 0 !important;
font-size: 0.875rem !important;
color: var(--ifnex-gray-700) !important;
}
.ifnex-tracking-details strong {
color: #374151;
margin-left: 8px;
color: var(--ifnex-gray-700) !important;
margin-left: 0.5rem !important;
}
.ifnex-timeline {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #e5e7eb;
margin-top: 1.5rem !important;
padding-top: 1rem !important;
border-top: 1px solid var(--ifnex-gray-200) !important;
}
.ifnex-timeline h4 {
font-size: 16px;
font-weight: 600;
color: #111827;
margin-bottom: 12px;
font-size: 1rem !important;
font-weight: 600 !important;
color: var(--ifnex-gray-900) !important;
margin-bottom: 0.75rem !important;
}
.ifnex-timeline ul {
list-style: none;
padding: 0;
margin: 0;
list-style: none !important;
padding: 0 !important;
margin: 0 !important;
}
.ifnex-timeline li {
padding: 10px 12px;
border-bottom: 1px solid #f3f4f6;
display: flex;
gap: 10px;
align-items: flex-start;
font-size: 14px;
padding: 0.75rem !important;
border-bottom: 1px solid var(--ifnex-gray-100) !important;
display: flex !important;
gap: 0.75rem !important;
align-items: flex-start !important;
font-size: 0.875rem !important;
}
.ifnex-timeline li:last-child {
border-bottom: none;
border-bottom: none !important;
}
.ifnex-timeline li strong {
color: #f59e0b;
font-weight: 600;
white-space: nowrap;
min-width: 100px;
color: var(--ifnex-primary) !important;
font-weight: 600 !important;
white-space: nowrap !important;
min-width: 100px !important;
}
.ifnex-timeline li span {
color: #4b5563;
flex: 1;
color: var(--ifnex-gray-700) !important;
flex: 1 !important;
}
.ifnex-timeline li small {
color: #9ca3af;
font-size: 12px;
white-space: nowrap;
color: var(--ifnex-gray-400) !important;
font-size: 0.75rem !important;
white-space: nowrap !important;
}
/* ============================================
RESPONSIVE
============================================ */
@media (max-width: 768px) {
.ifnex-tracking-container {
max-width: 100% !important;
padding: 1rem !important;
margin: 1rem 0 !important;
}
.ifnex-form-group {
flex-direction: column !important;
}
.ifnex-form-group button {
width: 100% !important;
}
.ifnex-balance-amount {
font-size: 1.25rem !important;
}
.ifnex-transaction-header {
flex-direction: column !important;
align-items: flex-start !important;
gap: 0.5rem !important;
}
}

View File

@ -1 +1 @@
{"version":2,"defects":{"Tests\\Feature\\Api\\PricingControllerTest::it_can_calculate_pricing_with_valid_data":5,"Tests\\Feature\\Api\\PricingControllerTest::it_returns_validation_errors_for_invalid_data":7,"Tests\\Feature\\Services\\PriceCalculatorServiceTest::it_calculates_price_correctly_for_standard_package":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_returns_active_discount_codes":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_validates_discount_code_successfully":8,"Tests\\Feature\\Api\\PaymentControllerTest::it_completes_mock_payment_successfully":7,"Tests\\Feature\\Services\\WalletServiceTest::it_creates_deposit_transaction":8,"Tests\\Feature\\Services\\WalletServiceTest::it_completes_deposit_and_updates_balance":8,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_deposit":8,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_withdrawal":8,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_withdrawal_when_balance_is_insufficient":7,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_order_payment":8,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_order_payment_when_balance_is_insufficient":7,"Tests\\Feature\\Services\\WalletServiceTest::it_fails_transaction":8},"times":{"Tests\\Feature\\Api\\PricingControllerTest::it_can_calculate_pricing_with_valid_data":0.052,"Tests\\Feature\\Api\\PricingControllerTest::it_returns_validation_errors_for_invalid_data":0.05,"Tests\\Feature\\Services\\PriceCalculatorServiceTest::it_calculates_price_correctly_for_standard_package":0.008,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.005,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.064,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_returns_active_discount_codes":0.096,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_validates_discount_code_successfully":0.059,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_invalid_discount_code":0.05,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_expired_discount_code":0.05,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_code_below_minimum_order_amount":0.05,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_calculates_fixed_discount_correctly":0.048,"Tests\\Feature\\Api\\PaymentControllerTest::it_redirects_to_gateway_for_payment":0.081,"Tests\\Feature\\Api\\PaymentControllerTest::it_validates_minimum_deposit_amount":0.059,"Tests\\Feature\\Api\\PaymentControllerTest::it_prevents_payment_for_frozen_wallet":0.049,"Tests\\Feature\\Api\\PaymentControllerTest::it_completes_mock_payment_successfully":0.073,"Tests\\Feature\\Api\\PaymentControllerTest::it_checks_transaction_status":0.062,"Tests\\Feature\\Services\\WalletServiceTest::it_creates_deposit_transaction":0.003,"Tests\\Feature\\Services\\WalletServiceTest::it_completes_deposit_and_updates_balance":0.007,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_deposit":0.007,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_withdrawal":0.004,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_withdrawal_when_balance_is_insufficient":0.002,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_order_payment":0.003,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_order_payment_when_balance_is_insufficient":0.001,"Tests\\Feature\\Services\\WalletServiceTest::it_fails_transaction":0.006}}
{"version":2,"defects":{"Tests\\Feature\\Api\\PricingControllerTest::it_can_calculate_pricing_with_valid_data":5,"Tests\\Feature\\Api\\PricingControllerTest::it_returns_validation_errors_for_invalid_data":7,"Tests\\Feature\\Services\\PriceCalculatorServiceTest::it_calculates_price_correctly_for_standard_package":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_returns_active_discount_codes":8,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_validates_discount_code_successfully":8,"Tests\\Feature\\Api\\PaymentControllerTest::it_completes_mock_payment_successfully":7,"Tests\\Feature\\Services\\WalletServiceTest::it_creates_deposit_transaction":8,"Tests\\Feature\\Services\\WalletServiceTest::it_completes_deposit_and_updates_balance":8,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_deposit":8,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_withdrawal":8,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_withdrawal_when_balance_is_insufficient":7,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_order_payment":8,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_order_payment_when_balance_is_insufficient":7,"Tests\\Feature\\Services\\WalletServiceTest::it_fails_transaction":8},"times":{"Tests\\Feature\\Api\\PricingControllerTest::it_can_calculate_pricing_with_valid_data":0.058,"Tests\\Feature\\Api\\PricingControllerTest::it_returns_validation_errors_for_invalid_data":0.06,"Tests\\Feature\\Services\\PriceCalculatorServiceTest::it_calculates_price_correctly_for_standard_package":0.012,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.008,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.081,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_returns_active_discount_codes":0.109,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_validates_discount_code_successfully":0.112,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_invalid_discount_code":0.059,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_expired_discount_code":0.071,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_rejects_code_below_minimum_order_amount":0.059,"Tests\\Feature\\Api\\DiscountCodeControllerTest::it_calculates_fixed_discount_correctly":0.053,"Tests\\Feature\\Api\\PaymentControllerTest::it_redirects_to_gateway_for_payment":0.079,"Tests\\Feature\\Api\\PaymentControllerTest::it_validates_minimum_deposit_amount":0.054,"Tests\\Feature\\Api\\PaymentControllerTest::it_prevents_payment_for_frozen_wallet":0.053,"Tests\\Feature\\Api\\PaymentControllerTest::it_completes_mock_payment_successfully":0.093,"Tests\\Feature\\Api\\PaymentControllerTest::it_checks_transaction_status":0.057,"Tests\\Feature\\Services\\WalletServiceTest::it_creates_deposit_transaction":0.005,"Tests\\Feature\\Services\\WalletServiceTest::it_completes_deposit_and_updates_balance":0.01,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_deposit":0.004,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_manual_withdrawal":0.004,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_withdrawal_when_balance_is_insufficient":0.002,"Tests\\Feature\\Services\\WalletServiceTest::it_processes_order_payment":0.003,"Tests\\Feature\\Services\\WalletServiceTest::it_prevents_order_payment_when_balance_is_insufficient":0.001,"Tests\\Feature\\Services\\WalletServiceTest::it_fails_transaction":0.003}}

View File

@ -0,0 +1,113 @@
<?php
namespace App\Exports;
use App\Models\Shipment;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class ShipmentsExport implements FromQuery, WithHeadings, WithMapping, WithStyles, ShouldAutoSize
{
protected $filters;
public function __construct(array $filters = [])
{
$this->filters = $filters;
}
public function query()
{
$query = Shipment::query()
->with(['fromCountry', 'toCountry', 'carrierMappings', 'items'])
->orderByDesc('created_at');
if (!empty($this->filters['status'])) {
$query->where('status', $this->filters['status']);
}
if (!empty($this->filters['type'])) {
$query->where('type', $this->filters['type']);
}
if (!empty($this->filters['direction'])) {
$query->where('direction', $this->filters['direction']);
}
if (!empty($this->filters['from_date'])) {
$query->where('created_at', '>=', $this->filters['from_date']);
}
if (!empty($this->filters['to_date'])) {
$query->where('created_at', '<=', $this->filters['to_date']);
}
return $query;
}
public function headings(): array
{
return [
'AWB No.',
'جهت',
'نوع',
'وضعیت',
'کشور مبدا',
'کشور مقصد',
'وزن واقعی (kg)',
'وزن حجمی (kg)',
'وزن قابل پرداخت (kg)',
'قیمت حمل (درهم)',
'خدمات اضافی (درهم)',
'هزینه بسته‌بندی (ریال)',
'دریافت داخلی (ریال)',
'تحویل داخلی (ریال)',
'هزینه انبار (ریال)',
'تخفیف (ریال)',
'هزینه کل (ریال)',
'فرستنده',
'تلفن فرستنده',
'گیرنده',
'تلفن گیرنده',
'تاریخ ایجاد',
];
}
public function map($shipment): array
{
return [
$shipment->awb_no,
$shipment->direction->label(),
$shipment->type->label(),
$shipment->status->label(),
$shipment->fromCountry->name ?? '-',
$shipment->toCountry->name ?? '-',
$shipment->weight ?? 0,
$shipment->volumetric_weight ?? 0,
$shipment->chargeable_weight ?? 0,
$shipment->shipping_price ?? 0,
$shipment->extra_service ?? 0,
$shipment->packing_cost ?? 0,
$shipment->domestic_pickup ?? 0,
$shipment->domestic_delivery ?? 0,
$shipment->warehousing_cost ?? 0,
$shipment->discount ?? 0,
$shipment->total_fee ?? 0,
$shipment->sender_name ?? '-',
$shipment->sender_phone ?? '-',
$shipment->receiver_name ?? '-',
$shipment->receiver_phone ?? '-',
$shipment->created_at?->format('Y/m/d H:i'),
];
}
public function styles(Worksheet $sheet)
{
return [
1 => ['font' => ['bold' => true, 'size' => 12]],
];
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Exports;
use App\Models\WalletTransaction;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Support\Facades\Auth;
class WalletTransactionsExport implements FromQuery, WithHeadings, WithMapping, WithStyles, ShouldAutoSize
{
protected $filters;
public function __construct(array $filters = [])
{
$this->filters = $filters;
}
public function query()
{
$query = WalletTransaction::query()
->with(['wallet.user', 'creator', 'approver'])
->orderByDesc('created_at');
if (!empty($this->filters['type'])) {
$query->where('type', $this->filters['type']);
}
if (!empty($this->filters['status'])) {
$query->where('status', $this->filters['status']);
}
if (!empty($this->filters['gateway'])) {
$query->where('gateway', $this->filters['gateway']);
}
if (!empty($this->filters['from_date'])) {
$query->where('created_at', '>=', $this->filters['from_date']);
}
if (!empty($this->filters['to_date'])) {
$query->where('created_at', '<=', $this->filters['to_date']);
}
return $query;
}
public function headings(): array
{
return [
'شناسه',
'کاربر',
'ایمیل',
'مبلغ (ریال)',
'نوع',
'وضعیت',
'درگاه',
'کد پیگیری',
'توضیحات',
'یادداشت ادمین',
'موجودی قبل',
'موجودی بعد',
'تاریخ ایجاد',
'تایید کننده',
];
}
public function map($transaction): array
{
return [
$transaction->id,
$transaction->wallet->user->name ?? 'نامشخص',
$transaction->wallet->user->email ?? 'نامشخص',
$transaction->amount,
$transaction->type->label(),
$transaction->status->label(),
$transaction->gateway?->label() ?? '-',
$transaction->gateway_reference_id ?? '-',
$transaction->description ?? '-',
$transaction->admin_notes ?? '-',
$transaction->balance_before ?? 0,
$transaction->balance_after ?? 0,
$transaction->created_at?->format('Y/m/d H:i'),
$transaction->approver?->name ?? '-',
];
}
public function styles(Worksheet $sheet)
{
return [
// First row as bold text.
1 => ['font' => ['bold' => true, 'size' => 12]],
];
}
}

View File

@ -0,0 +1,278 @@
<?php
namespace App\Filament\Pages\Reports;
use App\Exports\ShipmentsExport;
use App\Exports\WalletTransactionsExport;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Pages\Page;
use Filament\Notifications\Notification;
use Maatwebsite\Excel\Facades\Excel;
use Illuminate\Support\Facades\DB;
class FinancialReport extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-chart-bar';
protected static ?string $navigationLabel = 'گزارش مالی';
protected static ?string $navigationGroup = 'مالی';
protected static ?int $navigationSort = 5;
protected static ?string $slug = 'financial-report';
protected static string $view = 'filament.pages.reports.financial-report';
public ?array $data = [];
public ?array $reportData = null;
public function mount(): void
{
$this->form->fill();
}
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make('فیلترهای گزارش')
->schema([
Forms\Components\Select::make('report_type')
->label('نوع گزارش')
->options([
'transactions' => 'تراکنش‌های کیف پول',
'shipments' => 'مرسولات',
'financial_summary' => 'خلاصه مالی',
])
->required()
->reactive()
->default('transactions'),
Forms\Components\DatePicker::make('from_date')
->label('از تاریخ')
->required()
->default(now()->subMonth()),
Forms\Components\DatePicker::make('to_date')
->label('تا تاریخ')
->required()
->default(now()),
Forms\Components\Select::make('transaction_type')
->label('نوع تراکنش')
->options([
'deposit' => 'واریزی',
'withdrawal' => 'برداشت',
'order_payment' => 'پرداخت سفارش',
'refund' => 'بازگشت وجه',
])
->visible(fn ($get) => $get('report_type') === 'transactions')
->placeholder('همه'),
Forms\Components\Select::make('shipment_status')
->label('وضعیت مرسوله')
->options([
'processed' => 'ثبت شده',
'picked_up' => 'تحویل به پست',
'in_transit' => 'در حال حمل',
'out_for_delivery' => 'آماده تحویل',
'delivered' => 'تحویل داده شده',
'failed' => 'ناموفق',
'returned' => 'بازگشته',
])
->visible(fn ($get) => $get('report_type') === 'shipments')
->placeholder('همه'),
])
->columns(2),
Forms\Components\Section::make('عملیات')
->schema([
Forms\Components\Actions::make([
Forms\Components\Actions\Action::make('generate')
->label('تولید گزارش')
->icon('heroicon-o-eye')
->color('primary')
->action('generateReport'),
Forms\Components\Actions\Action::make('export_excel')
->label('خروجی Excel')
->icon('heroicon-o-arrow-down-tray')
->color('success')
->action('exportExcel')
->visible(fn ($get) => $get('report_type') !== 'financial_summary'),
])
->fullWidth(),
]),
])
->statePath('data');
}
public function generateReport(): void
{
$this->validate();
$type = $this->data['report_type'];
$fromDate = $this->data['from_date'];
$toDate = $this->data['to_date'];
switch ($type) {
case 'transactions':
$this->generateTransactionsReport($fromDate, $toDate);
break;
case 'shipments':
$this->generateShipmentsReport($fromDate, $toDate);
break;
case 'financial_summary':
$this->generateFinancialSummary($fromDate, $toDate);
break;
}
}
private function generateTransactionsReport($fromDate, $toDate): void
{
$query = DB::table('wallet_transactions')
->join('wallets', 'wallet_transactions.wallet_id', '=', 'wallets.id')
->join('users', 'wallets.user_id', '=', 'users.id')
->select(
'wallet_transactions.id',
'users.name as user_name',
'users.email as user_email',
'wallet_transactions.amount',
'wallet_transactions.type',
'wallet_transactions.status',
'wallet_transactions.gateway',
'wallet_transactions.description',
'wallet_transactions.created_at'
)
->whereBetween('wallet_transactions.created_at', [$fromDate, $toDate . ' 23:59:59'])
->orderByDesc('wallet_transactions.created_at');
if (!empty($this->data['transaction_type'])) {
$query->where('wallet_transactions.type', $this->data['transaction_type']);
}
$this->reportData = [
'type' => 'transactions',
'data' => $query->get(),
'summary' => [
'total_count' => $query->count(),
'total_amount' => $query->sum('amount'),
'deposits' => $query->where('type', 'deposit')->sum('amount'),
'withdrawals' => $query->whereIn('type', ['withdrawal', 'order_payment'])->sum('amount'),
],
];
}
private function generateShipmentsReport($fromDate, $toDate): void
{
$query = DB::table('shipments')
->join('countries as from_country', 'shipments.from_country_id', '=', 'from_country.id')
->join('countries as to_country', 'shipments.to_country_id', '=', 'to_country.id')
->select(
'shipments.awb_no',
'shipments.direction',
'shipments.type',
'shipments.status',
'from_country.name as from_country',
'to_country.name as to_country',
'shipments.weight',
'shipments.chargeable_weight',
'shipments.shipping_price',
'shipments.total_fee',
'shipments.sender_name',
'shipments.receiver_name',
'shipments.created_at'
)
->whereBetween('shipments.created_at', [$fromDate, $toDate . ' 23:59:59'])
->orderByDesc('shipments.created_at');
if (!empty($this->data['shipment_status'])) {
$query->where('shipments.status', $this->data['shipment_status']);
}
$this->reportData = [
'type' => 'shipments',
'data' => $query->get(),
'summary' => [
'total_count' => $query->count(),
'total_fee' => $query->sum('total_fee'),
'total_weight' => $query->sum('chargeable_weight'),
],
];
}
private function generateFinancialSummary($fromDate, $toDate): void
{
// Total deposits
$deposits = DB::table('wallet_transactions')
->where('type', 'deposit')
->where('status', 'completed')
->whereBetween('created_at', [$fromDate, $toDate . ' 23:59:59'])
->sum('amount');
// Total withdrawals/order payments
$withdrawals = DB::table('wallet_transactions')
->whereIn('type', ['withdrawal', 'order_payment'])
->where('status', 'completed')
->whereBetween('created_at', [$fromDate, $toDate . ' 23:59:59'])
->sum('amount');
// Total shipments revenue
$shipmentsRevenue = DB::table('shipments')
->whereBetween('created_at', [$fromDate, $toDate . ' 23:59:59'])
->sum('total_fee');
// Total discount codes used
$discounts = DB::table('discount_codes')
->where('is_active', true)
->whereBetween('updated_at', [$fromDate, $toDate . ' 23:59:59'])
->sum('used_count');
// Wallet balances
$totalBalance = DB::table('wallets')->sum('balance');
$totalDeposited = DB::table('wallets')->sum('total_deposited');
$totalWithdrawn = DB::table('wallets')->sum('total_withdrawn');
$this->reportData = [
'type' => 'financial_summary',
'summary' => [
'deposits' => $deposits,
'withdrawals' => abs($withdrawals),
'shipments_revenue' => $shipmentsRevenue,
'discounts_used' => $discounts,
'total_balance' => $totalBalance,
'total_deposited' => $totalDeposited,
'total_withdrawn' => $totalWithdrawn,
'net_profit' => $shipmentsRevenue - $withdrawals,
],
];
}
public function exportExcel(): \Symfony\Component\HttpFoundation\BinaryFileResponse
{
$this->validate();
$type = $this->data['report_type'];
$filters = [
'from_date' => $this->data['from_date'],
'to_date' => $this->data['to_date'],
];
if ($type === 'transactions') {
$filters['type'] = $this->data['transaction_type'] ?? null;
$export = new WalletTransactionsExport($filters);
$fileName = 'transactions_' . now()->format('Y-m-d_H-i') . '.xlsx';
} elseif ($type === 'shipments') {
$filters['status'] = $this->data['shipment_status'] ?? null;
$export = new ShipmentsExport($filters);
$fileName = 'shipments_' . now()->format('Y-m-d_H-i') . '.xlsx';
} else {
Notification::make()
->title('خطا')
->body('برای خلاصه مالی خروجی Excel موجود نیست.')
->danger()
->send();
return redirect()->back();
}
return Excel::download($export, $fileName);
}
}

View File

@ -9,6 +9,8 @@ use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use App\Exports\ShipmentsExport;
use Filament\Tables\Actions\ExportAction;
class ShipmentResource extends Resource
{
@ -196,6 +198,18 @@ class ShipmentResource extends Resource
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
])
->headerActions([
ExportAction::make()
->label('خروجی Excel')
->icon('heroicon-o-arrow-down-tray')
->color('success')
->export(fn ($query) => new ShipmentsExport([
'status' => request()->input('filters.status'),
'type' => request()->input('filters.type'),
'direction' => request()->input('filters.direction'),
]))
->fileName('shipments_' . now()->format('Y-m-d_H-i')),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),

View File

@ -12,6 +12,9 @@ use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use App\Exports\WalletTransactionsExport;
use Filament\Tables\Actions\ExportAction;
use Maatwebsite\Excel\Facades\Excel;
class WalletTransactionResource extends Resource
{
@ -133,6 +136,18 @@ class WalletTransactionResource extends Resource
->actions([
Tables\Actions\ViewAction::make(),
])
->headerActions([
ExportAction::make()
->label('خروجی Excel')
->icon('heroicon-o-arrow-down-tray')
->color('success')
->export(fn ($query) => new WalletTransactionsExport([
'type' => request()->input('filters.type'),
'status' => request()->input('filters.status'),
'gateway' => request()->input('filters.gateway'),
]))
->fileName('transactions_' . now()->format('Y-m-d_H-i')),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),

View File

@ -29,6 +29,7 @@ class AdminPanelProvider extends PanelProvider
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->pages([
Dashboard::class,
\App\Filament\Pages\Reports\FinancialReport::class,
])
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
->widgets([

View File

@ -1,8 +1,71 @@
@import 'tailwindcss';
:root {
/* IFNEX Brand Colors */
--ifnex-primary: #f59e0b;
--ifnex-primary-dark: #d97706;
--ifnex-primary-light: #fbbf24;
--ifnex-success: #059669;
--ifnex-success-light: #d1fae5;
--ifnex-danger: #dc2626;
--ifnex-danger-light: #fee2e2;
--ifnex-warning: #f59e0b;
--ifnex-warning-light: #fef3c7;
--ifnex-info: #2563eb;
--ifnex-info-light: #dbeafe;
/* Custom IFNEX Filament Theme */
/* Neutral Colors */
--ifnex-gray-900: #111827;
--ifnex-gray-700: #374151;
--ifnex-gray-600: #4b5563;
--ifnex-gray-500: #6b7280;
--ifnex-gray-400: #9ca3af;
--ifnex-gray-300: #d1d5db;
--ifnex-gray-200: #e5e7eb;
--ifnex-gray-100: #f3f4f6;
--ifnex-gray-50: #f9fafb;
--ifnex-white: #ffffff;
/* Sidebar Styling */
/* Typography */
--ifnex-font-family: 'Vazirmatn', 'IranYekan', ui-sans-serif, system-ui, sans-serif;
--ifnex-font-size-base: 14px;
--ifnex-line-height: 1.6;
/* Spacing */
--ifnex-spacing-xs: 0.25rem;
--ifnex-spacing-sm: 0.5rem;
--ifnex-spacing-md: 1rem;
--ifnex-spacing-lg: 1.5rem;
--ifnex-spacing-xl: 2rem;
--ifnex-spacing-2xl: 3rem;
/* Border Radius */
--ifnex-radius-sm: 0.375rem;
--ifnex-radius-md: 0.5rem;
--ifnex-radius-lg: 0.75rem;
/* Shadows */
--ifnex-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--ifnex-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--ifnex-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
/* Transitions */
--ifnex-transition: all 0.2s ease;
}
/* Global Reset */
* {
box-sizing: border-box !important;
}
body {
font-family: var(--ifnex-font-family) !important;
font-size: var(--ifnex-font-size-base) !important;
line-height: var(--ifnex-line-height) !important;
color: var(--ifnex-gray-900) !important;
}
/* ============================================
SIDEBAR
============================================ */
.fi-sidebar {
background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%) !important;
border-left: 1px solid rgba(245, 158, 11, 0.2) !important;
@ -18,39 +81,32 @@
padding: 0.75rem !important;
}
/* Navigation Groups */
.fi-sidebar-nav-group {
margin-bottom: 0.5rem !important;
border-radius: 0.5rem !important;
border-radius: var(--ifnex-radius-md) !important;
overflow: hidden !important;
}
.fi-sidebar-nav-group-header {
background: rgba(245, 158, 11, 0.08) !important;
border-bottom: 1px solid rgba(245, 158, 11, 0.1) !important;
transition: all 0.2s ease !important;
padding: 0.5rem 0.75rem !important;
color: rgba(255, 255, 255, 0.7) !important;
font-weight: 500 !important;
font-size: 0.75rem !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
}
.fi-sidebar-nav-group-header:hover {
background: rgba(245, 158, 11, 0.15) !important;
color: var(--ifnex-primary-light) !important;
}
.fi-sidebar-nav-group-item {
padding: 0.5rem 0.75rem !important;
font-weight: 500 !important;
color: rgba(255, 255, 255, 0.7) !important;
transition: all 0.2s ease !important;
}
.fi-sidebar-nav-group-item:hover {
color: #fbbf24 !important;
}
/* Navigation Items */
.fi-sidebar-nav-item {
margin: 0.15rem 0 !important;
border-radius: 0.5rem !important;
transition: all 0.2s ease !important;
border-radius: var(--ifnex-radius-md) !important;
transition: var(--ifnex-transition) !important;
position: relative !important;
overflow: hidden !important;
}
@ -63,7 +119,7 @@
transform: translateY(-50%) !important;
width: 3px !important;
height: 0 !important;
background: #fbbf24 !important;
background: var(--ifnex-primary) !important;
border-radius: 3px 0 0 3px !important;
transition: height 0.2s ease !important;
}
@ -83,11 +139,10 @@
}
.fi-sidebar-nav-item.active .fi-sidebar-nav-item-label {
color: #fbbf24 !important;
color: var(--ifnex-primary-light) !important;
font-weight: 600 !important;
}
/* Icons */
.fi-sidebar-nav-item-icon {
color: rgba(255, 255, 255, 0.6) !important;
transition: color 0.2s ease !important;
@ -95,193 +150,417 @@
.fi-sidebar-nav-item:hover .fi-sidebar-nav-item-icon,
.fi-sidebar-nav-item.active .fi-sidebar-nav-item-icon {
color: #fbbf24 !important;
color: var(--ifnex-primary) !important;
}
/* Labels */
.fi-sidebar-nav-item-label {
color: rgba(255, 255, 255, 0.85) !important;
font-size: 0.875rem !important;
transition: color 0.2s ease !important;
}
.fi-sidebar-nav-group-item {
color: rgba(255, 255, 255, 0.5) !important;
font-size: 0.75rem !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
/* ============================================
MAIN CONTENT
============================================ */
.fi-main {
max-width: 1400px !important;
margin: 0 auto !important;
padding: var(--ifnex-spacing-lg) !important;
}
/* Badges */
.fi-badge {
font-weight: 600 !important;
border-radius: 9999px !important;
font-size: 0.75rem !important;
padding: 0.125rem 0.5rem !important;
@media (max-width: 1024px) {
.fi-main {
padding: var(--ifnex-spacing-md) !important;
max-width: 100% !important;
}
}
/* Cards and Panels */
@media (max-width: 768px) {
.fi-main {
padding: var(--ifnex-spacing-sm) !important;
}
}
/* ============================================
PAGE HEADER
============================================ */
.fi-page-header {
margin-bottom: var(--ifnex-spacing-lg) !important;
padding-bottom: var(--ifnex-spacing-md) !important;
border-bottom: 2px solid var(--ifnex-primary) !important;
}
.fi-page-header h1 {
font-size: 1.5rem !important;
font-weight: 700 !important;
color: var(--ifnex-gray-900) !important;
margin: 0 !important;
}
@media (max-width: 768px) {
.fi-page-header h1 {
font-size: 1.25rem !important;
}
}
/* ============================================
CARDS
============================================ */
.fi-card {
border-radius: 0.75rem !important;
border: 1px solid #e5e7eb !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important;
transition: all 0.2s ease !important;
}
.fi-card:hover {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important;
}
/* Buttons */
.fi-btn {
border-radius: 0.5rem !important;
font-weight: 500 !important;
transition: all 0.2s ease !important;
}
.fi-btn-primary {
background: #f59e0b !important;
border-color: #f59e0b !important;
}
.fi-btn-primary:hover {
background: #d97706 !important;
border-color: #d97706 !important;
transform: translateY(-1px) !important;
box-shadow: 0 4px 6px -1px rgba(245, 158, 11, 0.3) !important;
}
/* Tables */
.fi-ta-table {
border-radius: 0.75rem !important;
border-radius: var(--ifnex-radius-lg) !important;
border: 1px solid var(--ifnex-gray-200) !important;
box-shadow: var(--ifnex-shadow-sm) !important;
margin-bottom: var(--ifnex-spacing-md) !important;
overflow: hidden !important;
background: var(--ifnex-white) !important;
}
.fi-card-header {
background: var(--ifnex-gray-50) !important;
padding: 1rem 1.25rem !important;
border-bottom: 1px solid var(--ifnex-gray-200) !important;
}
.fi-card-header h2 {
font-size: 1.125rem !important;
font-weight: 600 !important;
color: var(--ifnex-gray-900) !important;
margin: 0 !important;
}
.fi-card-body {
padding: 1.25rem !important;
background: var(--ifnex-white) !important;
color: var(--ifnex-gray-900) !important;
}
/* ============================================
STATS WIDGET
============================================ */
.fi-wi-stats-overview {
display: grid !important;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) !important;
gap: var(--ifnex-spacing-md) !important;
width: 100% !important;
}
.fi-wi-stats-overview-card {
border-radius: var(--ifnex-radius-lg) !important;
border: 1px solid var(--ifnex-gray-200) !important;
padding: 1.25rem !important;
transition: var(--ifnex-transition) !important;
background: var(--ifnex-white) !important;
}
.fi-wi-stats-overview-card:hover {
transform: translateY(-2px) !important;
box-shadow: var(--ifnex-shadow-md) !important;
}
.fi-wi-stats-overview-card h3 {
font-size: 0.875rem !important;
font-weight: 500 !important;
color: var(--ifnex-gray-500) !important;
margin: 0 0 0.5rem 0 !important;
}
.fi-wi-stats-overview-card p {
font-size: 1.5rem !important;
font-weight: 700 !important;
color: var(--ifnex-gray-900) !important;
margin: 0 !important;
}
@media (max-width: 768px) {
.fi-wi-stats-overview {
grid-template-columns: 1fr !important;
}
.fi-wi-stats-overview-card p {
font-size: 1.25rem !important;
}
}
/* ============================================
TABLES
============================================ */
.fi-ta-table {
border-radius: var(--ifnex-radius-lg) !important;
overflow: hidden !important;
width: 100% !important;
}
.fi-ta-header-cell {
background: #f9fafb !important;
background: var(--ifnex-gray-50) !important;
font-weight: 600 !important;
color: #374151 !important;
font-size: 0.75rem !important;
color: var(--ifnex-gray-700) !important;
font-size: 0.875rem !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
letter-spacing: 0.025em !important;
padding: 0.75rem 1rem !important;
}
.fi-ta-row {
transition: background-color 0.15s ease !important;
.fi-ta-cell {
padding: 0.75rem 1rem !important;
color: var(--ifnex-gray-700) !important;
font-size: 0.875rem !important;
border-bottom: 1px solid var(--ifnex-gray-100) !important;
}
.fi-ta-row:hover {
background-color: #fffbeb !important;
}
/* Forms */
.fi-input {
border-radius: 0.5rem !important;
border-color: #d1d5db !important;
transition: all 0.2s ease !important;
/* ============================================
FORMS
============================================ */
.fi-input,
.fi-select,
.fi-textarea {
border-radius: var(--ifnex-radius-md) !important;
border-color: var(--ifnex-gray-300) !important;
font-size: 0.875rem !important;
padding: 0.625rem 0.875rem !important;
transition: var(--ifnex-transition) !important;
width: 100% !important;
color: var(--ifnex-gray-900) !important;
}
.fi-input:focus {
border-color: #fbbf24 !important;
.fi-input:focus,
.fi-select:focus,
.fi-textarea:focus {
border-color: var(--ifnex-primary) !important;
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.1) !important;
outline: none !important;
}
.fi-select {
border-radius: 0.5rem !important;
.fi-label {
font-weight: 500 !important;
color: var(--ifnex-gray-700) !important;
margin-bottom: 0.375rem !important;
display: block !important;
font-size: 0.875rem !important;
}
/* Widgets */
.fi-wi-stats-overview {
gap: 1rem !important;
/* ============================================
BUTTONS
============================================ */
.fi-btn {
border-radius: var(--ifnex-radius-md) !important;
font-weight: 500 !important;
font-size: 0.875rem !important;
padding: 0.625rem 1.25rem !important;
transition: var(--ifnex-transition) !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
gap: 0.5rem !important;
}
.fi-wi-stats-overview-card {
border-radius: 0.75rem !important;
border: 1px solid #e5e7eb !important;
transition: all 0.2s ease !important;
.fi-btn-primary {
background: var(--ifnex-primary) !important;
border-color: var(--ifnex-primary) !important;
color: var(--ifnex-white) !important;
}
.fi-wi-stats-overview-card:hover {
transform: translateY(-2px) !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important;
.fi-btn-primary:hover {
background: var(--ifnex-primary-dark) !important;
border-color: var(--ifnex-primary-dark) !important;
transform: translateY(-1px) !important;
box-shadow: var(--ifnex-shadow-md) !important;
}
/* Dashboard */
.fi-page-header {
border-bottom: 2px solid #fbbf24 !important;
margin-bottom: 1.5rem !important;
padding-bottom: 1rem !important;
/* ============================================
REPORTS PAGE
============================================ */
.ifnex-report-page {
max-width: 1200px !important;
margin: 0 auto !important;
padding: var(--ifnex-spacing-lg) !important;
}
/* Top Navigation */
.fi-topbar {
border-bottom: 1px solid #f3f4f6 !important;
background: #ffffff !important;
.ifnex-report-container {
background: var(--ifnex-white) !important;
border-radius: var(--ifnex-radius-lg) !important;
border: 1px solid var(--ifnex-gray-200) !important;
padding: var(--ifnex-spacing-lg) !important;
box-shadow: var(--ifnex-shadow-sm) !important;
}
/* Notifications */
.fi-notification {
border-radius: 0.75rem !important;
border-right: 4px solid #fbbf24 !important;
}
/* Modals */
.fi-modal {
border-radius: 0.75rem !important;
overflow: hidden !important;
}
/* Dropdowns */
.fi-dropdown {
border-radius: 0.5rem !important;
border: 1px solid #e5e7eb !important;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important;
}
/* Tabs */
.fi-tabs {
border-bottom: 2px solid #f3f4f6 !important;
}
.fi-tab {
border-bottom: 2px solid transparent !important;
margin-bottom: -2px !important;
transition: all 0.2s ease !important;
}
.fi-tab:hover {
border-bottom-color: #fbbf24 !important;
color: #d97706 !important;
}
.fi-tab.active {
border-bottom-color: #fbbf24 !important;
color: #d97706 !important;
.section-title {
font-size: 1.125rem !important;
font-weight: 600 !important;
color: var(--ifnex-gray-900) !important;
margin: var(--ifnex-spacing-lg) 0 var(--ifnex-spacing-md) 0 !important;
padding-bottom: var(--ifnex-spacing-sm) !important;
border-bottom: 2px solid var(--ifnex-primary) !important;
}
/* Scrollbar */
.report-summary-grid {
display: grid !important;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important;
gap: var(--ifnex-spacing-md) !important;
margin: var(--ifnex-spacing-md) 0 !important;
}
.report-card {
background: var(--ifnex-white) !important;
border: 1px solid var(--ifnex-gray-200) !important;
border-radius: var(--ifnex-radius-lg) !important;
padding: 1.25rem !important;
text-align: center !important;
transition: var(--ifnex-transition) !important;
}
.report-card:hover {
box-shadow: var(--ifnex-shadow-sm) !important;
transform: translateY(-1px) !important;
}
.report-card-title {
font-size: 0.875rem !important;
font-weight: 500 !important;
color: var(--ifnex-gray-500) !important;
margin-bottom: var(--ifnex-spacing-sm) !important;
}
.report-card-value {
font-size: 1.5rem !important;
font-weight: 700 !important;
color: var(--ifnex-gray-900) !important;
}
@media (max-width: 768px) {
.report-summary-grid {
grid-template-columns: 1fr !important;
}
.report-card-value {
font-size: 1.25rem !important;
}
}
/* ============================================
REPORT TABLES
============================================ */
.report-table-container {
overflow-x: auto !important;
margin-top: var(--ifnex-spacing-lg) !important;
border-radius: var(--ifnex-radius-lg) !important;
border: 1px solid var(--ifnex-gray-200) !important;
}
.report-table {
width: 100% !important;
border-collapse: collapse !important;
font-size: 0.875rem !important;
}
.report-table thead {
background: var(--ifnex-gray-50) !important;
}
.report-table th {
padding: 0.75rem 1rem !important;
text-align: right !important;
font-weight: 600 !important;
color: var(--ifnex-gray-700) !important;
font-size: 0.875rem !important;
text-transform: uppercase !important;
letter-spacing: 0.025em !important;
border-bottom: 1px solid var(--ifnex-gray-200) !important;
}
.report-table td {
padding: 0.75rem 1rem !important;
color: var(--ifnex-gray-700) !important;
border-bottom: 1px solid var(--ifnex-gray-100) !important;
}
.report-table tbody tr:hover {
background-color: #fffbeb !important;
}
@media (max-width: 768px) {
.report-table {
font-size: 0.8125rem !important;
}
.report-table th,
.report-table td {
padding: 0.5rem !important;
}
}
/* ============================================
ALERTS
============================================ */
.fi-alert {
border-radius: var(--ifnex-radius-md) !important;
padding: var(--ifnex-spacing-md) var(--ifnex-spacing-lg) !important;
margin-bottom: var(--ifnex-spacing-md) !important;
font-size: 0.875rem !important;
}
.fi-alert-success {
background: var(--ifnex-success-light) !important;
color: var(--ifnex-success) !important;
border-right: 4px solid var(--ifnex-success) !important;
}
.fi-alert-danger {
background: var(--ifnex-danger-light) !important;
color: var(--ifnex-danger) !important;
border-right: 4px solid var(--ifnex-danger) !important;
}
/* ============================================
SCROLLBAR
============================================ */
::-webkit-scrollbar {
width: 8px !important;
height: 8px !important;
}
::-webkit-scrollbar-track {
background: #f1f5f9 !important;
background: var(--ifnex-gray-100) !important;
border-radius: var(--ifnex-radius-sm) !important;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1 !important;
border-radius: 4px !important;
background: var(--ifnex-gray-300) !important;
border-radius: var(--ifnex-radius-sm) !important;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8 !important;
background: var(--ifnex-gray-400) !important;
}
/* Dark mode adjustments */
@media (prefers-color-scheme: dark) {
.fi-sidebar {
background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%) !important;
}
}
/* ============================================
TEXT COLORS
============================================ */
.text-gray-900 { color: var(--ifnex-gray-900) !important; }
.text-gray-700 { color: var(--ifnex-gray-700) !important; }
.text-gray-600 { color: var(--ifnex-gray-600) !important; }
.text-gray-500 { color: var(--ifnex-gray-500) !important; }
.text-white { color: var(--ifnex-white) !important; }
.text-green-700 { color: var(--ifnex-success) !important; }
.text-red-700 { color: var(--ifnex-danger) !important; }
.text-blue-700 { color: var(--ifnex-info) !important; }
/* ============================================
BACKGROUNDS
============================================ */
.bg-white { background-color: var(--ifnex-white) !important; }
.bg-gray-50 { background-color: var(--ifnex-gray-50) !important; }
.bg-gray-100 { background-color: var(--ifnex-gray-100) !important; }
.bg-green-50 { background-color: var(--ifnex-success-light) !important; }
.bg-red-50 { background-color: var(--ifnex-danger-light) !important; }
.bg-yellow-50 { background-color: var(--ifnex-warning-light) !important; }
.bg-blue-50 { background-color: var(--ifnex-info-light) !important; }
/* ============================================
BORDERS
============================================ */
.border-gray-200 { border-color: var(--ifnex-gray-200) !important; }
.border-gray-300 { border-color: var(--ifnex-gray-300) !important; }

View File

@ -0,0 +1,164 @@
<x-filament-panels::page>
<div class="ifnex-report-page">
{{ $this->form }}
@if ($reportData)
<div class="ifnex-report-container">
<h2 class="section-title">نتیجه گزارش</h2>
@if ($reportData['type'] === 'financial_summary')
<div class="report-summary-grid">
<div class="report-card">
<p class="report-card-title">کل واریزی‌ها</p>
<p class="report-card-value text-green-700">
{{ number_format($reportData['summary']['deposits']) }} ریال
</p>
</div>
<div class="report-card">
<p class="report-card-title">کل برداشت‌ها</p>
<p class="report-card-value text-red-700">
{{ number_format($reportData['summary']['withdrawals']) }} ریال
</p>
</div>
<div class="report-card">
<p class="report-card-title">درآمد از مرسولات</p>
<p class="report-card-value text-blue-700">
{{ number_format($reportData['summary']['shipments_revenue']) }} ریال
</p>
</div>
<div class="report-card">
<p class="report-card-title">سود خالص</p>
<p class="report-card-value text-purple-700">
{{ number_format($reportData['summary']['net_profit']) }} ریال
</p>
</div>
</div>
<div class="report-summary-grid">
<div class="report-card">
<p class="report-card-title">موجودی کل کیف پول‌ها</p>
<p class="report-card-value">{{ number_format($reportData['summary']['total_balance']) }} ریال</p>
</div>
<div class="report-card">
<p class="report-card-title">کل واریزی‌های ثبت شده</p>
<p class="report-card-value">{{ number_format($reportData['summary']['total_deposited']) }} ریال</p>
</div>
<div class="report-card">
<p class="report-card-title">کل برداشت‌های ثبت شده</p>
<p class="report-card-value">{{ number_format($reportData['summary']['total_withdrawn']) }} ریال</p>
</div>
</div>
@if ($reportData['summary']['discounts_used'] > 0)
<div class="report-card" style="background: var(--ifnex-warning-light); border-color: var(--ifnex-primary-light);">
<p class="report-card-title">تخفیف‌های استفاده شده</p>
<p class="report-card-value">{{ number_format($reportData['summary']['discounts_used']) }} بار</p>
</div>
@endif
@endif
@if ($reportData['type'] === 'transactions' && $reportData['data']->count() > 0)
<div class="report-summary-grid">
<div class="report-card">
<p class="report-card-title">تعداد تراکنش‌ها</p>
<p class="report-card-value">{{ number_format($reportData['summary']['total_count']) }}</p>
</div>
<div class="report-card">
<p class="report-card-title">مجموع واریزی‌ها</p>
<p class="report-card-value text-green-700">
{{ number_format($reportData['summary']['deposits']) }} ریال
</p>
</div>
<div class="report-card">
<p class="report-card-title">مجموع برداشت‌ها</p>
<p class="report-card-value text-red-700">
{{ number_format($reportData['summary']['withdrawals']) }} ریال
</p>
</div>
</div>
<div class="report-table-container">
<table class="report-table">
<thead>
<tr>
<th>شناسه</th>
<th>کاربر</th>
<th>مبلغ</th>
<th>نوع</th>
<th>وضعیت</th>
<th>تاریخ</th>
</tr>
</thead>
<tbody>
@foreach ($reportData['data'] as $item)
<tr>
<td>{{ $item->id }}</td>
<td>{{ $item->user_name }}</td>
<td class="{{ $item->amount >= 0 ? 'text-green-700' : 'text-red-700' }}" style="font-weight: 600;">
{{ number_format($item->amount) }} ریال
</td>
<td>{{ $item->type }}</td>
<td>{{ $item->status }}</td>
<td>{{ \Morilog\Jalali\Jalalian::fromCarbon(\Carbon\Carbon::parse($item->created_at))->format('Y/m/d H:i') }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
@if ($reportData['type'] === 'shipments' && $reportData['data']->count() > 0)
<div class="report-summary-grid">
<div class="report-card">
<p class="report-card-title">تعداد مرسولات</p>
<p class="report-card-value">{{ number_format($reportData['summary']['total_count']) }}</p>
</div>
<div class="report-card">
<p class="report-card-title">هزینه کل مرسولات</p>
<p class="report-card-value">{{ number_format($reportData['summary']['total_fee']) }} ریال</p>
</div>
<div class="report-card">
<p class="report-card-title">وزن کل (kg)</p>
<p class="report-card-value">{{ number_format($reportData['summary']['total_weight'], 2) }}</p>
</div>
</div>
<div class="report-table-container">
<table class="report-table">
<thead>
<tr>
<th>AWB</th>
<th>جهت</th>
<th>نوع</th>
<th>وضعیت</th>
<th>مبدا</th>
<th>مقصد</th>
<th>هزینه کل</th>
</tr>
</thead>
<tbody>
@foreach ($reportData['data'] as $item)
<tr>
<td style="font-family: monospace;">{{ $item->awb_no }}</td>
<td>{{ $item->direction }}</td>
<td>{{ $item->type }}</td>
<td>{{ $item->status }}</td>
<td>{{ $item->from_country }}</td>
<td>{{ $item->to_country }}</td>
<td style="font-weight: 600;">{{ number_format($item->total_fee) }} ریال</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
@if ($reportData['data']->count() === 0)
<div class="report-card" style="text-align: center; padding: 3rem;">
<p style="color: var(--ifnex-gray-500); font-size: 1rem;">هیچ داده‌ای برای این بازه زمانی یافت نشد.</p>
</div>
@endif
</div>
@endif
</div>
</x-filament-panels::page>