schema([ Forms\Components\Section::make('جزئیات تغییر نرخ') ->schema([ Forms\Components\TextInput::make('currency_from') ->label('ارز') ->disabled(), Forms\Components\TextInput::make('old_rate') ->label('نرخ قبلی') ->disabled() ->suffix('ریال'), Forms\Components\TextInput::make('new_rate') ->label('نرخ جدید') ->disabled() ->suffix('ریال'), Forms\Components\TextInput::make('change_percent') ->label('درصد تغییر') ->disabled() ->suffix('%'), Forms\Components\TextInput::make('change_source') ->label('منبع تغییر') ->disabled(), Forms\Components\Select::make('changed_by') ->label('توسط') ->relationship('changedBy', 'name') ->disabled(), Forms\Components\TextInput::make('status') ->label('وضعیت') ->disabled(), Forms\Components\Select::make('approved_by') ->label('تأییدکننده') ->relationship('approvedBy', 'name') ->disabled(), Forms\Components\TextInput::make('approved_at') ->label('تاریخ تأیید') ->disabled(), Forms\Components\Textarea::make('notes') ->label('یادداشت') ->disabled() ->columnSpanFull(), ])->columns(2), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('id') ->label('شناسه') ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('currency_from') ->label('ارز') ->badge() ->color('info'), Tables\Columns\TextColumn::make('old_rate') ->label('نرخ قبلی') ->numeric(0) ->suffix(' ریال') ->color('gray'), Tables\Columns\TextColumn::make('new_rate') ->label('نرخ جدید') ->numeric(0) ->suffix(' ریال') ->weight('bold') ->color('primary'), Tables\Columns\TextColumn::make('change_percent') ->label('تغییر') ->state(fn ($record) => $record->change_percent !== null ? number_format($record->change_percent, 2) . '%' : '—') ->badge() ->color(fn ($record) => match(true) { $record->change_percent > 0 => 'danger', $record->change_percent < 0 => 'success', default => 'gray', }) ->icon(fn ($record) => match(true) { $record->change_percent > 0 => 'heroicon-m-arrow-trending-up', $record->change_percent < 0 => 'heroicon-m-arrow-trending-down', default => null, }), Tables\Columns\TextColumn::make('change_source') ->label('منبع') ->badge() ->formatStateUsing(fn ($state) => $state === 'api' ? 'API خودکار' : 'دستی') ->color(fn ($state) => $state === 'api' ? 'info' : 'warning') ->icon(fn ($state) => $state === 'api' ? 'heroicon-m-cog-6-tooth' : 'heroicon-m-pencil-square'), Tables\Columns\TextColumn::make('status') ->label('وضعیت') ->badge() ->formatStateUsing(fn (string $state): string => match($state) { 'pending' => 'در انتظار', 'approved' => 'تأیید شده', 'rejected' => 'رد شده', 'superseded' => 'جای‌گذشته', default => $state, }) ->color(fn (string $state): string => match($state) { 'pending' => 'warning', 'approved' => 'success', 'rejected' => 'danger', 'superseded' => 'gray', default => 'gray', }) ->icon(fn (string $state): string => match($state) { 'pending' => 'heroicon-o-clock', 'approved' => 'heroicon-o-check-circle', 'rejected' => 'heroicon-o-x-circle', default => 'heroicon-o-minus-circle', }), Tables\Columns\TextColumn::make('changedBy.name') ->label('توسط') ->state(fn ($record) => $record->changedBy?->name ?? ($record->change_source === 'api' ? 'سیستم' : '—')) ->icon('heroicon-m-user'), Tables\Columns\TextColumn::make('created_at') ->label('تاریخ تغییر') ->dateTime('Y/m/d H:i') ->sortable(), ]) ->filters([ Tables\Filters\SelectFilter::make('status') ->label('وضعیت') ->options([ 'pending' => 'در انتظار', 'approved' => 'تأیید شده', 'rejected' => 'رد شده', 'superseded' => 'جای‌گذشته', ]), Tables\Filters\SelectFilter::make('change_source') ->label('منبع') ->options([ 'api' => 'API خودکار', 'manual' => 'دستی', ]), Tables\Filters\SelectFilter::make('currency_from') ->label('ارز') ->options([ 'AED' => 'درهم (AED)', 'USD' => 'دلار (USD)', 'EUR' => 'یورو (EUR)', 'CNY' => 'یوان (CNY)', ]), Tables\Filters\Filter::make('created_at') ->form([ Forms\Components\DatePicker::make('from')->label('از تاریخ'), Forms\Components\DatePicker::make('until')->label('تا تاریخ'), ]) ->query(function ($query, array $data) { return $query ->when($data['from'], fn ($q, $d) => $q->whereDate('created_at', '>=', $d)) ->when($data['until'], fn ($q, $d) => $q->whereDate('created_at', '<=', $d)); }), ]) ->actions([ Tables\Actions\ViewAction::make()->label('جزئیات'), Tables\Actions\Action::make('approve') ->label('تأیید') ->icon('heroicon-o-check-circle') ->color('success') ->requiresConfirmation() ->modalHeading('تأیید نرخ پیشنهادی') ->modalDescription(fn ($record) => "نرخ {$record->currency_from} از " . number_format($record->old_rate) . " به " . number_format($record->new_rate) . " ریال تغییر یابد؟ نرخ زنده پس از تأیید به‌روز می‌شود.") ->modalSubmitActionLabel('بله، تأیید و اعمال کن') ->visible(fn ($record) => $record->status === 'pending') ->action(function ($record) { try { app(ExchangeRateService::class)->approveRate($record, Auth::id()); Notification::make()->title('نرخ تأیید و روی نرخ زنده اعمال شد')->success()->send(); } catch (\Throwable $e) { Notification::make()->title('خطا')->body($e->getMessage())->danger()->send(); } }), Tables\Actions\Action::make('reject') ->label('رد') ->icon('heroicon-o-x-circle') ->color('danger') ->requiresConfirmation() ->modalHeading('رد نرخ پیشنهادی') ->modalDescription('نرخ رد می‌شود. نرخ زنده دست‌نخورده باقی می‌ماند.') ->visible(fn ($record) => $record->status === 'pending') ->action(function ($record) { try { app(ExchangeRateService::class)->rejectRate($record, Auth::id()); Notification::make()->title('نرخ رد شد')->warning()->send(); } catch (\Throwable $e) { Notification::make()->title('خطا')->body($e->getMessage())->danger()->send(); } }), ]) ->headerActions([ Action::make('refreshFromApi') ->label('آپدیت از API') ->icon('heroicon-o-arrow-path') ->color('gray') ->tooltip('این قابلیت موقتاً غیرفعال است. لطفاً نرخ‌ها را به‌صورت دستی از منوی «ارزها» در بخش تنظیمات وارد کنید.') ->action(function () { Notification::make() ->title('قابلیت غیرفعال است') ->body('دریافت خودکار نرخ از API موقتاً غیرفعال است. لطفاً نرخ‌ها را به‌صورت دستی از منوی «ارزها» در بخش تنظیمات وارد کنید.') ->warning() ->send(); }), ]) ->bulkActions([]) ->defaultSort('created_at', 'desc') ->poll('60s'); } public static function getRelations(): array { return []; } public static function getPages(): array { return [ 'index' => Pages\ListExchangeRateHistories::route('/'), 'view' => Pages\ViewExchangeRateHistory::route('/{record}'), ]; } public static function canViewAny(): bool { $user = Auth::user(); return $user && $user->hasAnyRole(['super_admin', 'admin']); } public static function canCreate(): bool { return false; } public static function canEdit($record): bool { return false; } public static function canDelete($record): bool { return false; } public static function getNavigationBadge(): ?string { $count = app(ExchangeRateService::class)->getPendingCount(); return $count > 0 ? (string) $count : null; } public static function getNavigationBadgeColor(): ?string { return 'warning'; } }