Files
Virtnet.bond/extensions/Others/NavbarManager/Admin/Resources/NavbarLinkResource.php

438 lines
18 KiB
PHP

<?php
namespace Paymenter\Extensions\Others\NavbarManager\Admin\Resources;
use Paymenter\Extensions\Others\NavbarManager\Models\NavbarLink;
use Paymenter\Extensions\Others\NavbarManager\Admin\Resources\NavbarLinkResource\Pages;
use Paymenter\Extensions\Others\NavbarManager\Support\RemixIconCatalog;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class NavbarLinkResource extends Resource
{
protected static function normalizeIcon(?string $icon): ?string
{
if (empty($icon)) {
return null;
}
$icon = strtolower(trim($icon));
if ($icon === '') {
return null;
}
if (!str_starts_with($icon, 'ri-')) {
$icon = 'ri-' . ltrim($icon, '-');
}
return $icon;
}
protected static ?string $model = NavbarLink::class;
protected static ?string $navigationIcon = 'heroicon-o-bars-3';
protected static ?string $navigationLabel = 'Navbar Manager';
protected static ?string $navigationGroup = 'Extensions';
public static function getAvailableRoutes(): array
{
$routes = [];
try {
$routeCollection = \Route::getRoutes();
$grouped = [
'Common Routes' => [],
'Client Routes' => [],
'API Routes' => [],
'Other Routes' => [],
];
foreach ($routeCollection as $route) {
$name = $route->getName();
if ($name && !str_starts_with($name, 'admin.')
&& !str_starts_with($name, 'filament.')
&& !str_starts_with($name, '_debugbar')
&& !str_starts_with($name, 'telescope')
&& !str_starts_with($name, 'clockwork')) {
$methods = implode('|', $route->methods());
$uri = $route->uri();
$label = "{$name} ({$methods} /{$uri})";
// Group routes by type
if (in_array($name, ['home', 'dashboard', 'services', 'invoices', 'profile', 'shop'])) {
$grouped['Common Routes'][$name] = $label;
} elseif (str_starts_with($name, 'client.') || str_starts_with($name, 'account.')) {
$grouped['Client Routes'][$name] = $label;
} elseif (str_starts_with($name, 'api.')) {
$grouped['API Routes'][$name] = $label;
} else {
$grouped['Other Routes'][$name] = $label;
}
}
}
// Remove empty groups and flatten for Filament
foreach ($grouped as $group => $groupRoutes) {
if (!empty($groupRoutes)) {
asort($groupRoutes);
$routes = array_merge($routes, $groupRoutes);
}
}
} catch (\Exception $e) {
// Fallback routes if there's an error
$routes = [
'home' => 'home (GET /)',
'dashboard' => 'dashboard (GET dashboard)',
'services' => 'services (GET services)',
'invoices' => 'invoices (GET invoices)',
'profile' => 'profile (GET profile)',
'shop' => 'shop (GET shop)',
];
}
return $routes;
}
public static function getAvailableProducts(): array
{
$products = [];
try {
$productModels = \App\Models\Product::where('hidden', false)->get();
foreach ($productModels as $product) {
$label = $product->name;
// Add pricing info if available
if ($product->price > 0) {
$label .= " (\${$product->price})";
}
// Add description if available
if ($product->description) {
$description = \Str::limit($product->description, 50);
$label .= " - {$description}";
}
$products[$product->id] = $label;
}
} catch (\Exception $e) {
// Fallback if there's an error
$products = [];
}
return $products;
}
public static function getAvailableCategories(): array
{
$categories = [];
try {
$categoryModels = \App\Models\Category::all();
foreach ($categoryModels as $category) {
$label = $category->name;
// Add parent category if it's a subcategory
if ($category->parent) {
$label = $category->parent->name . ' > ' . $label;
}
// Add product count if available
$productCount = $category->products()->where('hidden', false)->count();
if ($productCount > 0) {
$label .= " ({$productCount} products)";
}
$categories[$category->id] = $label;
}
} catch (\Exception $e) {
// Fallback if there's an error
$categories = [];
}
return $categories;
}
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->label('Link Name')
->required()
->maxLength(255),
Forms\Components\Select::make('type')
->label('Navigation Type')
->options([
'primary' => 'Primary Navigation',
'account' => 'Account Navigation',
'dashboard' => 'Dashboard Navigation',
])
->required()
->default('primary'),
Forms\Components\Select::make('parent_id')
->label('Parent Link (for dropdown)')
->relationship('parent', 'name')
->searchable()
->preload()
->placeholder('Select parent (optional)'),
Forms\Components\Section::make('Link Destination')
->description('Configure the link destination')
->schema([
Forms\Components\Select::make('link_type')
->label('Link Type')
->required()
->live()
->options([
'route' => 'Laravel Route',
'path' => 'Custom Path',
'url' => 'External URL',
'product' => 'Product Path',
'category' => 'Category Path',
])
->default('route')
->helperText('Select the type of link you want to create'),
Forms\Components\Select::make('route_name')
->label('Route Name')
->required(fn (Get $get): bool => $get('link_type') === 'route')
->options(self::getAvailableRoutes())
->searchable()
->placeholder('Select a Laravel route')
->helperText('Choose from available Laravel routes')
->visible(fn (Get $get): bool => $get('link_type') === 'route'),
Forms\Components\Repeater::make('route_params')
->label('Route Parameters')
->schema([
Forms\Components\TextInput::make('key')
->label('Parameter')
->required(),
Forms\Components\TextInput::make('value')
->label('Value')
->required(),
])
->columns(2)
->helperText('Parameters to pass to the route (optional)')
->visible(fn (Get $get): bool => $get('link_type') === 'route'),
Forms\Components\TextInput::make('custom_path')
->label('Custom Path')
->required(fn (Get $get): bool => $get('link_type') === 'path')
->placeholder('e.g., /admin/custom-page')
->helperText('Internal path on your website')
->visible(fn (Get $get): bool => $get('link_type') === 'path'),
Forms\Components\TextInput::make('custom_url')
->label('External URL')
->required(fn (Get $get): bool => $get('link_type') === 'url')
->url()
->placeholder('e.g., https://example.com')
->helperText('Full external URL')
->visible(fn (Get $get): bool => $get('link_type') === 'url'),
Forms\Components\Select::make('product_id')
->label('Select Product')
->required(fn (Get $get): bool => $get('link_type') === 'product')
->options(self::getAvailableProducts())
->searchable()
->placeholder('Choose a product')
->helperText('Select which product this link should point to')
->visible(fn (Get $get): bool => $get('link_type') === 'product'),
Forms\Components\Select::make('category_id')
->label('Select Category')
->required(fn (Get $get): bool => $get('link_type') === 'category')
->options(self::getAvailableCategories())
->searchable()
->placeholder('Choose a category')
->helperText('Select which category this link should point to')
->visible(fn (Get $get): bool => $get('link_type') === 'category'),
])
->columns(2),
Forms\Components\TextInput::make('icon')
->label('Icon Class')
->placeholder('ri-home-line or home-line')
->helperText('Use any Remix Icon (see https://github.com/Remix-Design/RemixIcon/blob/master/tags.json). Prefix is optional—we will auto-add `ri-`.')
->regex('/^(?:ri-)?[a-z0-9-]+$/i')
->maxLength(80)
->datalist(array_values(RemixIconCatalog::all()))
->dehydrateStateUsing(fn ($state) => self::normalizeIcon($state)),
Forms\Components\Select::make('condition')
->label('Visibility Condition')
->options([
'always_visible' => 'Always Show',
'admin_only' => 'Admin Only',
'clients_only' => 'Client Only',
'guests_only' => 'Guest Only',
'always_hidden' => 'Always Hidden',
])
->required()
->default('always_visible'),
Forms\Components\Toggle::make('is_active')
->label('Active')
->default(true),
Forms\Components\TextInput::make('sort_order')
->label('Sort Order')
->numeric()
->default(0)
->helperText('Lower numbers appear first'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->label('Name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('type')
->label('Type')
->badge()
->color(fn (string $state): string => match ($state) {
'primary' => 'primary',
'account' => 'success',
'dashboard' => 'warning',
default => 'gray',
}),
Tables\Columns\TextColumn::make('link_type')
->label('Link Type')
->badge()
->color(fn (string $state): string => match ($state) {
'route' => 'primary',
'path' => 'warning',
'url' => 'danger',
'product' => 'success',
'category' => 'info',
default => 'gray',
})
->formatStateUsing(fn (string $state): string => match ($state) {
'route' => 'Laravel Route',
'path' => 'Custom Path',
'url' => 'External URL',
'product' => 'Product Path',
'category' => 'Category Path',
default => $state,
}),
Tables\Columns\TextColumn::make('parent.name')
->label('Parent')
->placeholder('—'),
Tables\Columns\TextColumn::make('route_name')
->label('Route')
->placeholder('—')
->limit(30),
Tables\Columns\TextColumn::make('custom_path')
->label('Path')
->placeholder('—')
->limit(30),
Tables\Columns\TextColumn::make('custom_url')
->label('URL')
->placeholder('—')
->limit(30),
Tables\Columns\TextColumn::make('product.name')
->label('Product')
->placeholder('—')
->limit(25),
Tables\Columns\TextColumn::make('category.name')
->label('Category')
->placeholder('—')
->limit(25),
Tables\Columns\IconColumn::make('is_active')
->label('Active')
->boolean(),
Tables\Columns\TextColumn::make('condition')
->label('Condition')
->badge()
->color(fn (string $state): string => match ($state) {
'always_visible' => 'success',
'admin_only' => 'danger',
'clients_only' => 'primary',
'guests_only' => 'warning',
'always_hidden' => 'gray',
default => 'gray',
}),
Tables\Columns\TextColumn::make('sort_order')
->label('Order')
->sortable(),
])
->filters([
Tables\Filters\SelectFilter::make('type')
->options([
'primary' => 'Primary Navigation',
'account' => 'Account Navigation',
'dashboard' => 'Dashboard Navigation',
]),
Tables\Filters\SelectFilter::make('condition')
->options([
'always_visible' => 'Always Show',
'admin_only' => 'Admin Only',
'clients_only' => 'Client Only',
'guests_only' => 'Guest Only',
'always_hidden' => 'Always Hidden',
]),
Tables\Filters\TernaryFilter::make('is_active')
->label('Active Status'),
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
])
->defaultSort('sort_order');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListNavbarLinks::route('/'),
'create' => Pages\CreateNavbarLink::route('/create'),
'edit' => Pages\EditNavbarLink::route('/{record}/edit'),
];
}
}