feat(admin): implement currency management and rate import system
Introduce a new currency management module including a dedicated resource, database migration, and a custom Filament page for importing exchange rates. Additionally, enhance the ShipmentResource table with improved column visibility, sorting, and route display, and integrate shipment update notifications.
This commit is contained in:
parent
a973c7ed5c
commit
e9a0c731c2
115
04_Laravel/app/Filament/Pages/ImportRatesPage.php
Normal file
115
04_Laravel/app/Filament/Pages/ImportRatesPage.php
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Pages;
|
||||||
|
|
||||||
|
use App\Imports\ShippingRatesImport;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Forms\Components\FileUpload;
|
||||||
|
use Filament\Forms\Components\Section;
|
||||||
|
use Filament\Forms\Concerns\InteractsWithForms;
|
||||||
|
use Filament\Forms\Contracts\HasForms;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Pages\Page;
|
||||||
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
|
||||||
|
class ImportRatesPage extends Page implements HasForms
|
||||||
|
{
|
||||||
|
use InteractsWithForms;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-arrow-up-tray';
|
||||||
|
protected static ?string $navigationLabel = 'آپلود نرخها';
|
||||||
|
protected static ?string $navigationGroup = 'تنظیمات';
|
||||||
|
protected static ?int $navigationSort = 10;
|
||||||
|
protected static string $view = 'filament.pages.import-rates';
|
||||||
|
|
||||||
|
public ?array $data = [];
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->form->fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormSchema(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Section::make('فایل اکسل نرخها')
|
||||||
|
->description('فایل اکسل حاوی شیتهای Export Rate, Import Rate و DocEco را آپلود کنید.')
|
||||||
|
->schema([
|
||||||
|
FileUpload::make('file')
|
||||||
|
->label('فایل اکسل')
|
||||||
|
->acceptedFileTypes([
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'application/vnd.ms-excel',
|
||||||
|
])
|
||||||
|
->maxSize(5120)
|
||||||
|
->required()
|
||||||
|
->disk('local')
|
||||||
|
->directory('imports')
|
||||||
|
->preserveFilenames(),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function import(): void
|
||||||
|
{
|
||||||
|
$data = $this->form->getState();
|
||||||
|
|
||||||
|
if (empty($data['file'])) {
|
||||||
|
Notification::make()
|
||||||
|
->title('خطا')
|
||||||
|
->body('لطفاً فایل اکسل را انتخاب کنید.')
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = storage_path('app/' . $data['file']);
|
||||||
|
|
||||||
|
if (!file_exists($filePath)) {
|
||||||
|
Notification::make()
|
||||||
|
->title('خطا')
|
||||||
|
->body('فایل پیدا نشد.')
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$import = new ShippingRatesImport();
|
||||||
|
Excel::import($import, $filePath);
|
||||||
|
|
||||||
|
$stats = [];
|
||||||
|
foreach ($import->sheets() as $name => $sheet) {
|
||||||
|
if (method_exists($sheet, 'getStats')) {
|
||||||
|
$s = $sheet->getStats();
|
||||||
|
$stats[] = "{$name}: {$s['imported']} imported, {$s['skipped']} skipped";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title('ایمپورت موفق')
|
||||||
|
->body(implode(' | ', $stats))
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
|
||||||
|
$this->form->fill();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Notification::make()
|
||||||
|
->title('خطا در ایمپورت')
|
||||||
|
->body($e->getMessage())
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getFormActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Action::make('import')
|
||||||
|
->label('شروع ایمپورت')
|
||||||
|
->icon('heroicon-o-arrow-up-tray')
|
||||||
|
->color('success')
|
||||||
|
->action('import'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
112
04_Laravel/app/Filament/Resources/CurrencyResource.php
Normal file
112
04_Laravel/app/Filament/Resources/CurrencyResource.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Resources\CurrencyResource\Pages;
|
||||||
|
use App\Models\Currency;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class CurrencyResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Currency::class;
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-banknotes';
|
||||||
|
protected static ?string $navigationLabel = 'ارزها';
|
||||||
|
protected static ?string $navigationGroup = 'تنظیمات';
|
||||||
|
protected static ?int $navigationSort = 11;
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Section::make('اطلاعات ارز')
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('code')
|
||||||
|
->required()
|
||||||
|
->maxLength(10)
|
||||||
|
->unique(ignoreRecord: true)
|
||||||
|
->label('کد ارز (مثل AED, USD, EUR)'),
|
||||||
|
Forms\Components\TextInput::make('name')
|
||||||
|
->required()
|
||||||
|
->maxLength(100)
|
||||||
|
->label('نام ارز'),
|
||||||
|
Forms\Components\TextInput::make('symbol')
|
||||||
|
->maxLength(10)
|
||||||
|
->nullable()
|
||||||
|
->label('نماد (مثل $, €, د.إ)'),
|
||||||
|
])->columns(3),
|
||||||
|
|
||||||
|
Forms\Components\Section::make('نرخ تبدیل')
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('rate_to_rial')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->default(0)
|
||||||
|
->suffix('ریال')
|
||||||
|
->label('نرخ نسبت به ریال'),
|
||||||
|
Forms\Components\TextInput::make('rate_to_aed')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->default(0)
|
||||||
|
->suffix('درهم')
|
||||||
|
->label('نرخ نسبت به درهم'),
|
||||||
|
Forms\Components\Toggle::make('is_active')
|
||||||
|
->default(true)
|
||||||
|
->label('فعال'),
|
||||||
|
])->columns(3),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('code')
|
||||||
|
->searchable()
|
||||||
|
->sortable()
|
||||||
|
->badge()
|
||||||
|
->label('کد'),
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->searchable()
|
||||||
|
->sortable()
|
||||||
|
->label('نام'),
|
||||||
|
Tables\Columns\TextColumn::make('symbol')
|
||||||
|
->label('نماد'),
|
||||||
|
Tables\Columns\TextColumn::make('rate_to_rial')
|
||||||
|
->sortable()
|
||||||
|
->formatStateUsing(fn ($state) => number_format((float) $state, 0))
|
||||||
|
->label('نرخ به ریال'),
|
||||||
|
Tables\Columns\TextColumn::make('rate_to_aed')
|
||||||
|
->sortable()
|
||||||
|
->formatStateUsing(fn ($state) => number_format((float) $state, 4))
|
||||||
|
->label('نرخ به درهم'),
|
||||||
|
Tables\Columns\IconColumn::make('is_active')
|
||||||
|
->boolean()
|
||||||
|
->label('فعال'),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
Tables\Filters\TernaryFilter::make('is_active')
|
||||||
|
->label('وضعیت'),
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListCurrencies::route('/'),
|
||||||
|
'create' => Pages\CreateCurrency::route('/create'),
|
||||||
|
'edit' => Pages\EditCurrency::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\CurrencyResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\CurrencyResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateCurrency extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = CurrencyResource::class;
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\CurrencyResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\CurrencyResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditCurrency extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = CurrencyResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\CurrencyResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\CurrencyResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListCurrencies extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = CurrencyResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ use Filament\Resources\Resource;
|
|||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Filament\Tables\Actions\Action;
|
use Filament\Tables\Actions\Action;
|
||||||
|
use App\Notifications\ShipmentUpdatedNotification;
|
||||||
|
|
||||||
class ShipmentResource extends Resource
|
class ShipmentResource extends Resource
|
||||||
{
|
{
|
||||||
@ -99,9 +100,6 @@ class ShipmentResource extends Resource
|
|||||||
Forms\Components\TextInput::make('content_description')
|
Forms\Components\TextInput::make('content_description')
|
||||||
->nullable()
|
->nullable()
|
||||||
->label('Content Description'),
|
->label('Content Description'),
|
||||||
Forms\Components\TextInput::make('reason_for_export')
|
|
||||||
->nullable()
|
|
||||||
->label('Reason for Export'),
|
|
||||||
])->columns(4),
|
])->columns(4),
|
||||||
|
|
||||||
Forms\Components\Section::make('Financial Info')
|
Forms\Components\Section::make('Financial Info')
|
||||||
@ -284,43 +282,63 @@ class ShipmentResource extends Resource
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('awb_no')
|
Tables\Columns\TextColumn::make('awb_no')
|
||||||
->searchable()
|
->searchable()->label('AWB No.')->sortable(),
|
||||||
->label('AWB No.')
|
Tables\Columns\TextColumn::make('direction')
|
||||||
->sortable(),
|
->badge()
|
||||||
Tables\Columns\TextColumn::make('direction')
|
->color(fn (\App\Enums\ShipmentDirection $state): string => match ($state->value) {
|
||||||
->badge()
|
'import' => 'success',
|
||||||
->color(fn (\App\Enums\ShipmentDirection $state): string => match ($state->value) {
|
'export' => 'info',
|
||||||
'import' => 'success',
|
default => 'gray',
|
||||||
'export' => 'info',
|
})
|
||||||
default => 'gray',
|
->label('Direction'),
|
||||||
})
|
|
||||||
->label('Direction'),
|
|
||||||
Tables\Columns\TextColumn::make('type')
|
Tables\Columns\TextColumn::make('type')
|
||||||
->badge()
|
->badge()->label('Type'),
|
||||||
->label('Type'),
|
|
||||||
Tables\Columns\TextColumn::make('status')
|
Tables\Columns\TextColumn::make('status')
|
||||||
->badge()
|
->badge()
|
||||||
->color(fn (ShipmentStatus $state): string => $state->color())
|
->color(fn (ShipmentStatus $state): string => $state->color())
|
||||||
->formatStateUsing(fn (ShipmentStatus $state): string => $state->label())
|
->formatStateUsing(fn (ShipmentStatus $state): string => $state->label())
|
||||||
->searchable()
|
->searchable()->label('Status'),
|
||||||
->label('Status'),
|
|
||||||
Tables\Columns\TextColumn::make('sender_name')
|
Tables\Columns\TextColumn::make('sender_name')
|
||||||
->searchable()
|
->searchable()->toggleable()->label('Sender'),
|
||||||
->toggleable()
|
|
||||||
->label('Sender'),
|
|
||||||
Tables\Columns\TextColumn::make('receiver_name')
|
Tables\Columns\TextColumn::make('receiver_name')
|
||||||
|
->searchable()->toggleable()->label('Receiver'),
|
||||||
|
Tables\Columns\TextColumn::make('fromCountry.name')
|
||||||
|
->label('From')
|
||||||
->searchable()
|
->searchable()
|
||||||
->toggleable()
|
->toggleable()
|
||||||
->label('Receiver'),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('total_fee')
|
Tables\Columns\TextColumn::make('toCountry.name')
|
||||||
->money('IRR')
|
->label('To')
|
||||||
->sortable()
|
->searchable()
|
||||||
->label('Total Fee'),
|
->toggleable()
|
||||||
Tables\Columns\TextColumn::make('created_at')
|
->sortable(),
|
||||||
->dateTime()
|
Tables\Columns\TextColumn::make('route')
|
||||||
->sortable()
|
->label('Route')
|
||||||
|
->state(fn ($record) => (
|
||||||
|
($record->fromCountry?->name ?? '?') .
|
||||||
|
' → ' .
|
||||||
|
($record->toCountry?->name ?? '?')
|
||||||
|
))
|
||||||
|
->searchable()
|
||||||
|
->toggleable(),
|
||||||
|
Tables\Columns\TextColumn::make('sender_phone')
|
||||||
|
->label('Sender Phone')
|
||||||
|
->searchable()
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
Tables\Columns\TextColumn::make('receiver_phone')
|
||||||
|
->label('Receiver Phone')
|
||||||
|
->searchable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
Tables\Columns\TextColumn::make('cod_amount')
|
||||||
|
->label('COD (AED)')
|
||||||
|
->money('AED')
|
||||||
|
->sortable()
|
||||||
|
->toggleable(),
|
||||||
|
Tables\Columns\TextColumn::make('total_fee')
|
||||||
|
->money('IRR')->sortable()->label('Total Fee'),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->dateTime()->sortable()->toggleable(isToggledHiddenByDefault: true),
|
||||||
])
|
])
|
||||||
->filters([
|
->filters([
|
||||||
Tables\Filters\SelectFilter::make('status')
|
Tables\Filters\SelectFilter::make('status')
|
||||||
@ -387,7 +405,7 @@ class ShipmentResource extends Resource
|
|||||||
$s->awb_no,
|
$s->awb_no,
|
||||||
$s->direction?->value ?? '',
|
$s->direction?->value ?? '',
|
||||||
$s->type?->value ?? '',
|
$s->type?->value ?? '',
|
||||||
$s->status?->value ?? '',
|
$s->status?->label() ?? '',
|
||||||
$s->fromCountry?->name ?? '',
|
$s->fromCountry?->name ?? '',
|
||||||
$s->toCountry?->name ?? '',
|
$s->toCountry?->name ?? '',
|
||||||
$s->weight,
|
$s->weight,
|
||||||
@ -418,7 +436,13 @@ class ShipmentResource extends Resource
|
|||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
public static function afterSave(\Filament\Resources\Pages\Page $page, \Illuminate\Database\Eloquent\Model $record): void
|
||||||
|
{
|
||||||
|
if ($page instanceof \Filament\Resources\Pages\EditRecord) {
|
||||||
|
ShipmentUpdatedNotification::notify($record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function getRelations(): array
|
public static function getRelations(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|||||||
21
04_Laravel/app/Models/Currency.php
Normal file
21
04_Laravel/app/Models/Currency.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Currency extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'code', 'name', 'symbol', 'rate_to_rial', 'rate_to_aed', 'is_active',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'rate_to_rial' => 'decimal:4',
|
||||||
|
'rate_to_aed' => 'decimal:6',
|
||||||
|
'is_active' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
45
04_Laravel/app/Notifications/ShipmentUpdatedNotification.php
Normal file
45
04_Laravel/app/Notifications/ShipmentUpdatedNotification.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Shipment;
|
||||||
|
use Filament\Notifications\Actions\Action;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
|
class ShipmentUpdatedNotification
|
||||||
|
{
|
||||||
|
public static function notify(Shipment $shipment): void
|
||||||
|
{
|
||||||
|
$changes = $shipment->getChanges();
|
||||||
|
$ignored = ['updated_at'];
|
||||||
|
|
||||||
|
$changedFields = array_filter(
|
||||||
|
array_keys($changes),
|
||||||
|
fn ($key) => !in_array($key, $ignored)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($changedFields)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldList = implode('، ', $changedFields);
|
||||||
|
$label = count($changedFields) === 1 ? $changedFields[0] : count($changedFields) . ' فیلد';
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title('محموله ویرایش شد')
|
||||||
|
->body("AWB {$shipment->awb_no} — {$label} تغییر کرد: {$fieldList}")
|
||||||
|
->info()
|
||||||
|
->icon('heroicon-o-pencil-square')
|
||||||
|
->actions([
|
||||||
|
Action::make('view')
|
||||||
|
->label('مشاهده')
|
||||||
|
->url(
|
||||||
|
\Filament\Facades\Filament::getPanel()->getResourceUrl(Shipment::class) . '/' . $shipment->id,
|
||||||
|
shouldOpenInNewTab: false
|
||||||
|
),
|
||||||
|
])
|
||||||
|
->sendToDatabase(\App\Models\User::all());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('currencies', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('code', 10)->unique();
|
||||||
|
$table->string('name', 100);
|
||||||
|
$table->string('symbol', 10)->nullable();
|
||||||
|
$table->decimal('rate_to_rial', 20, 4)->default(0);
|
||||||
|
$table->decimal('rate_to_aed', 20, 6)->default(0);
|
||||||
|
$table->boolean('is_active')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('currencies');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
<x-filament-panels::page>
|
||||||
|
{{ $this->form }}
|
||||||
|
</x-filament-panels::page>
|
||||||
Loading…
Reference in New Issue
Block a user