diff --git a/01_Documents/DESIGN_SYSTEM.md b/01_Documents/DESIGN_SYSTEM.md new file mode 100644 index 0000000..7966084 --- /dev/null +++ b/01_Documents/DESIGN_SYSTEM.md @@ -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; +} +``` diff --git a/01_Documents/STATUS.md b/01_Documents/STATUS.md index deee34c..410f329 100644 --- a/01_Documents/STATUS.md +++ b/01_Documents/STATUS.md @@ -552,6 +552,11 @@ IFNEX-Logistics/ - ساخت دستور Artisan `ifnex:sync-wp-users` برای یک‌سازی کاربران وردپرس ↔ لاراول - تست زنده پلاگین وردپرس — همه APIها با موفقیت تست شدند - پاک‌سازی فایل‌های تست و اضافی از ریشه پروژه + - افزودن خروجی Excel برای تراکنش‌ها و مرسولات در Filament + - ساخت صفحه گزارش‌گیری مالی در Filament: خلاصه مالی، تراکنش‌ها، مرسولات + - ساخت Design System یکپارچه IFNEX برای استفاده در Filament و وردپرس + - به‌روزرسانی استایل‌های Filament مطابق با Design System + - به‌روزرسانی استایل‌های پلاگین وردپرس مطابق با Design System diff --git a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-bridge.css b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-bridge.css index 6326345..915b764 100644 --- a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-bridge.css +++ b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-bridge.css @@ -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; + } } diff --git a/04_Laravel/.phpunit.result.cache b/04_Laravel/.phpunit.result.cache index 41bbd44..8143b2b 100644 --- a/04_Laravel/.phpunit.result.cache +++ b/04_Laravel/.phpunit.result.cache @@ -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}} \ No newline at end of file +{"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}} \ No newline at end of file diff --git a/04_Laravel/app/Exports/ShipmentsExport.php b/04_Laravel/app/Exports/ShipmentsExport.php new file mode 100644 index 0000000..b664cac --- /dev/null +++ b/04_Laravel/app/Exports/ShipmentsExport.php @@ -0,0 +1,113 @@ +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]], + ]; + } +} diff --git a/04_Laravel/app/Exports/WalletTransactionsExport.php b/04_Laravel/app/Exports/WalletTransactionsExport.php new file mode 100644 index 0000000..1473de3 --- /dev/null +++ b/04_Laravel/app/Exports/WalletTransactionsExport.php @@ -0,0 +1,99 @@ +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]], + ]; + } +} diff --git a/04_Laravel/app/Filament/Pages/Reports/FinancialReport.php b/04_Laravel/app/Filament/Pages/Reports/FinancialReport.php new file mode 100644 index 0000000..6451b74 --- /dev/null +++ b/04_Laravel/app/Filament/Pages/Reports/FinancialReport.php @@ -0,0 +1,278 @@ +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); + } +} diff --git a/04_Laravel/app/Filament/Resources/ShipmentResource.php b/04_Laravel/app/Filament/Resources/ShipmentResource.php index 106e6d4..8d4f42f 100644 --- a/04_Laravel/app/Filament/Resources/ShipmentResource.php +++ b/04_Laravel/app/Filament/Resources/ShipmentResource.php @@ -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(), diff --git a/04_Laravel/app/Filament/Resources/WalletTransactionResource.php b/04_Laravel/app/Filament/Resources/WalletTransactionResource.php index 2a95b89..04f4bba 100644 --- a/04_Laravel/app/Filament/Resources/WalletTransactionResource.php +++ b/04_Laravel/app/Filament/Resources/WalletTransactionResource.php @@ -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(), diff --git a/04_Laravel/app/Providers/Filament/AdminPanelProvider.php b/04_Laravel/app/Providers/Filament/AdminPanelProvider.php index 9801737..a4b8041 100644 --- a/04_Laravel/app/Providers/Filament/AdminPanelProvider.php +++ b/04_Laravel/app/Providers/Filament/AdminPanelProvider.php @@ -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([ diff --git a/04_Laravel/resources/css/filament.css b/04_Laravel/resources/css/filament.css index fc9b53a..198e4de 100644 --- a/04_Laravel/resources/css/filament.css +++ b/04_Laravel/resources/css/filament.css @@ -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; } diff --git a/04_Laravel/resources/views/filament/pages/reports/financial-report.blade.php b/04_Laravel/resources/views/filament/pages/reports/financial-report.blade.php new file mode 100644 index 0000000..34fb998 --- /dev/null +++ b/04_Laravel/resources/views/filament/pages/reports/financial-report.blade.php @@ -0,0 +1,164 @@ + +
+ {{ $this->form }} + + @if ($reportData) +
+

نتیجه گزارش

+ + @if ($reportData['type'] === 'financial_summary') +
+
+

کل واریزی‌ها

+

+ {{ number_format($reportData['summary']['deposits']) }} ریال +

+
+
+

کل برداشت‌ها

+

+ {{ number_format($reportData['summary']['withdrawals']) }} ریال +

+
+
+

درآمد از مرسولات

+

+ {{ number_format($reportData['summary']['shipments_revenue']) }} ریال +

+
+
+

سود خالص

+

+ {{ number_format($reportData['summary']['net_profit']) }} ریال +

+
+
+ +
+
+

موجودی کل کیف پول‌ها

+

{{ number_format($reportData['summary']['total_balance']) }} ریال

+
+
+

کل واریزی‌های ثبت شده

+

{{ number_format($reportData['summary']['total_deposited']) }} ریال

+
+
+

کل برداشت‌های ثبت شده

+

{{ number_format($reportData['summary']['total_withdrawn']) }} ریال

+
+
+ + @if ($reportData['summary']['discounts_used'] > 0) +
+

تخفیف‌های استفاده شده

+

{{ number_format($reportData['summary']['discounts_used']) }} بار

+
+ @endif + @endif + + @if ($reportData['type'] === 'transactions' && $reportData['data']->count() > 0) +
+
+

تعداد تراکنش‌ها

+

{{ number_format($reportData['summary']['total_count']) }}

+
+
+

مجموع واریزی‌ها

+

+ {{ number_format($reportData['summary']['deposits']) }} ریال +

+
+
+

مجموع برداشت‌ها

+

+ {{ number_format($reportData['summary']['withdrawals']) }} ریال +

+
+
+ +
+ + + + + + + + + + + + + @foreach ($reportData['data'] as $item) + + + + + + + + + @endforeach + +
شناسهکاربرمبلغنوعوضعیتتاریخ
{{ $item->id }}{{ $item->user_name }} + {{ number_format($item->amount) }} ریال + {{ $item->type }}{{ $item->status }}{{ \Morilog\Jalali\Jalalian::fromCarbon(\Carbon\Carbon::parse($item->created_at))->format('Y/m/d H:i') }}
+
+ @endif + + @if ($reportData['type'] === 'shipments' && $reportData['data']->count() > 0) +
+
+

تعداد مرسولات

+

{{ number_format($reportData['summary']['total_count']) }}

+
+
+

هزینه کل مرسولات

+

{{ number_format($reportData['summary']['total_fee']) }} ریال

+
+
+

وزن کل (kg)

+

{{ number_format($reportData['summary']['total_weight'], 2) }}

+
+
+ +
+ + + + + + + + + + + + + + @foreach ($reportData['data'] as $item) + + + + + + + + + + @endforeach + +
AWBجهتنوعوضعیتمبدامقصدهزینه کل
{{ $item->awb_no }}{{ $item->direction }}{{ $item->type }}{{ $item->status }}{{ $item->from_country }}{{ $item->to_country }}{{ number_format($item->total_fee) }} ریال
+
+ @endif + + @if ($reportData['data']->count() === 0) +
+

هیچ داده‌ای برای این بازه زمانی یافت نشد.

+
+ @endif +
+ @endif +
+