New Initial Commit

This commit is contained in:
twotalesanimation
2025-12-09 12:04:54 +02:00
commit 6de01c13c5
350 changed files with 42032 additions and 0 deletions
@@ -0,0 +1,64 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\CategoryResource\Pages;
use App\Models\Category;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Actions;
use Filament\Tables\Table;
class CategoryResource extends Resource
{
protected static ?string $model = Category::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-tag';
public static function form(Schema $schema): Schema
{
return $schema
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\Textarea::make('description')
->maxLength(65535)
->columnSpanFull(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('products_count')
->counts('products')
->label('Products'),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
//
])
->actions([
//
])
->bulkActions([
//
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ManageCategories::route('/'),
];
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Filament\Resources\CategoryResource\Pages;
use App\Filament\Resources\CategoryResource;
use Filament\Actions;
use Filament\Resources\Pages\ManageRecords;
class ManageCategories extends ManageRecords
{
protected static string $resource = CategoryResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
protected function getTableActions(): array
{
return [
Actions\EditAction::make(),
Actions\DeleteAction::make(),
];
}
}
@@ -0,0 +1,150 @@
<?php
namespace App\Filament\Resources;
use App\Models\HeroImage;
use App\Filament\Resources\HeroImageResource\Pages;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Schemas\Components\Section;
use Filament\Tables;
use Filament\Tables\Table;
class HeroImageResource extends Resource
{
protected static ?string $model = HeroImage::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-photo';
protected static ?string $navigationLabel = 'Hero Images';
protected static ?string $modelLabel = 'Hero Image';
protected static ?string $pluralModelLabel = 'Hero Images';
public static function form(Schema $schema): Schema
{
return $schema
->schema([
Forms\Components\Select::make('page')
->label('Page')
->options(HeroImage::getPages())
->required()
->helperText('Choose which page this hero image will appear on'),
Forms\Components\FileUpload::make('image_path')
->label('Hero Image')
->image()
->required()
->disk('public')
->directory('hero-images')
->maxSize(5120)
->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp', 'image/gif'])
->helperText('Upload a high-quality image (max 5MB). Recommended size: 1920x1080px'),
Forms\Components\TextInput::make('title')
->label('Heading')
->required()
->maxLength(255)
->helperText('Main heading text displayed on the hero image'),
Forms\Components\Textarea::make('description')
->label('Description')
->maxLength(500)
->rows(3)
->helperText('Subheading text displayed below the title'),
Forms\Components\TextInput::make('button_text')
->label('Button Text')
->maxLength(100)
->helperText('Text for the call-to-action button (optional)'),
Forms\Components\TextInput::make('button_link')
->label('Button Link')
->url()
->maxLength(255)
->helperText('URL or page anchor (e.g., #products or /products)'),
Forms\Components\TextInput::make('sort_order')
->label('Sort Order')
->numeric()
->default(0)
->helperText('Lower numbers appear first in the slider'),
Forms\Components\Toggle::make('is_active')
->label('Active')
->default(true)
->helperText('Disable to hide this image from the website'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('page')
->label('Page')
->sortable()
->formatStateUsing(fn($state) => HeroImage::getPages()[$state] ?? $state)
->badge(),
Tables\Columns\ImageColumn::make('image_path')
->label('Image')
->size(80),
Tables\Columns\TextColumn::make('title')
->label('Title')
->searchable()
->limit(50),
Tables\Columns\TextColumn::make('sort_order')
->label('Order')
->sortable()
->numeric(),
Tables\Columns\IconColumn::make('is_active')
->label('Active')
->boolean()
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->label('Created')
->dateTime('M d, Y')
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\SelectFilter::make('page')
->options(HeroImage::getPages()),
Tables\Filters\TernaryFilter::make('is_active')
->label('Active')
->native(false),
])
->actions([
//
])
->bulkActions([
//
])
->defaultSort('page', 'asc')
->defaultSort('sort_order', 'asc');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListHeroImages::route('/'),
'create' => Pages\CreateHeroImage::route('/create'),
'edit' => Pages\EditHeroImage::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\HeroImageResource\Pages;
use App\Filament\Resources\HeroImageResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateHeroImage extends CreateRecord
{
protected static string $resource = HeroImageResource::class;
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\HeroImageResource\Pages;
use App\Filament\Resources\HeroImageResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditHeroImage extends EditRecord
{
protected static string $resource = HeroImageResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Filament\Resources\HeroImageResource\Pages;
use App\Filament\Resources\HeroImageResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListHeroImages extends ListRecords
{
protected static string $resource = HeroImageResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->label('Add New Hero Image'),
];
}
}
+141
View File
@@ -0,0 +1,141 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\OrderResource\Pages;
use App\Models\Order;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Actions;
use Filament\Tables\Table;
class OrderResource extends Resource
{
protected static ?string $model = Order::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-shopping-bag';
public static function form(Schema $schema): Schema
{
return $schema
->schema([
Forms\Components\TextInput::make('order_number')
->required()
->disabled()
->maxLength(255),
Forms\Components\TextInput::make('customer_name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('customer_email')
->email()
->required()
->maxLength(255),
Forms\Components\TextInput::make('customer_phone')
->tel()
->maxLength(20),
Forms\Components\Textarea::make('shipping_address')
->required()
->maxLength(500)
->columnSpanFull(),
Forms\Components\Textarea::make('notes')
->maxLength(500)
->columnSpanFull(),
Forms\Components\TextInput::make('total')
->required()
->numeric()
->prefix('R'),
Forms\Components\Select::make('status')
->options([
'pending' => 'Pending',
'processing' => 'Processing',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
])
->required(),
Forms\Components\Select::make('payment_status')
->options([
'pending' => 'Pending',
'paid' => 'Paid',
'failed' => 'Failed',
])
->required(),
Forms\Components\TextInput::make('payment_method')
->maxLength(50),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('order_number')
->searchable(),
Tables\Columns\TextColumn::make('customer_name')
->searchable(),
Tables\Columns\TextColumn::make('customer_email')
->searchable(),
Tables\Columns\TextColumn::make('total')
->money('ZAR')
->sortable(),
Tables\Columns\TextColumn::make('status')
->badge()
->color(fn (string $state): string => match ($state) {
'pending' => 'warning',
'processing' => 'info',
'completed' => 'success',
'cancelled' => 'danger',
}),
Tables\Columns\TextColumn::make('payment_status')
->badge()
->color(fn (string $state): string => match ($state) {
'pending' => 'warning',
'paid' => 'success',
'failed' => 'danger',
}),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable(),
])
->filters([
Tables\Filters\SelectFilter::make('status')
->options([
'pending' => 'Pending',
'processing' => 'Processing',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
]),
Tables\Filters\SelectFilter::make('payment_status')
->options([
'pending' => 'Pending',
'paid' => 'Paid',
'failed' => 'Failed',
]),
])
->actions([
//
])
->bulkActions([
//
])
->defaultSort('created_at', 'desc');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListOrders::route('/'),
'create' => Pages\CreateOrder::route('/create'),
'view' => Pages\ViewOrder::route('/{record}'),
'edit' => Pages\EditOrder::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\OrderResource\Pages;
use App\Filament\Resources\OrderResource;
use Filament\Resources\Pages\CreateRecord;
class CreateOrder extends CreateRecord
{
protected static string $resource = OrderResource::class;
}
@@ -0,0 +1,20 @@
<?php
namespace App\Filament\Resources\OrderResource\Pages;
use App\Filament\Resources\OrderResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditOrder extends EditRecord
{
protected static string $resource = OrderResource::class;
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
];
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\OrderResource\Pages;
use App\Filament\Resources\OrderResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListOrders extends ListRecords
{
protected static string $resource = OrderResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
@@ -0,0 +1,43 @@
<?php
namespace App\Filament\Resources\OrderResource\Pages;
use App\Filament\Resources\OrderResource;
use App\Filament\Resources\OrderResource\Widgets\OrderItemsTable;
use Filament\Actions;
use Filament\Resources\Pages\ViewRecord;
use Illuminate\Contracts\Support\Htmlable;
class ViewOrder extends ViewRecord
{
protected static string $resource = OrderResource::class;
public function getTitle(): string
{
return 'Order #' . $this->record->order_number;
}
public function getHeading(): string | Htmlable
{
return 'Order #' . $this->record->order_number;
}
protected function getHeaderActions(): array
{
return [
Actions\EditAction::make(),
];
}
protected function getHeaderWidgets(): array
{
return [
OrderItemsTable::class,
];
}
public function getHeaderWidgetsColumns(): int | array
{
return 1;
}
}
@@ -0,0 +1,47 @@
<?php
namespace App\Filament\Resources\OrderResource\Widgets;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget;
use Illuminate\Database\Eloquent\Model;
class OrderItemsTable extends BaseWidget
{
public ?Model $record = null;
protected static ?string $heading = 'Order Items';
public function table(Table $table): Table
{
return $table
->query(
fn () => $this->record->items()->getQuery()
)
->columns([
Tables\Columns\TextColumn::make('product.name')
->label('Product'),
Tables\Columns\TextColumn::make('printStock.name')
->label('Print Stock')
->default('N/A'),
Tables\Columns\TextColumn::make('type')
->badge(),
Tables\Columns\TextColumn::make('specifications')
->label('Specifications')
->getStateUsing(function ($record) {
if ($record->type === 'wallpaper') {
return "Length: {$record->length}m";
} elseif ($record->type === 'mural') {
$area = number_format($record->width * $record->height, 2);
return "{$record->width}m × {$record->height}m ({$area}m²)";
}
return 'N/A';
}),
Tables\Columns\TextColumn::make('price')
->money('ZAR')
->alignEnd(),
])
->paginated(false);
}
}
@@ -0,0 +1,107 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\PrintStockResource\Pages;
use App\Models\PrintStock;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Actions;
use Filament\Tables\Table;
class PrintStockResource extends Resource
{
protected static ?string $model = PrintStock::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-cube';
protected static ?string $navigationLabel = 'Print Stocks';
public static function form(Schema $schema): Schema
{
return $schema
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\Textarea::make('description')
->maxLength(65535)
->columnSpanFull(),
Forms\Components\TextInput::make('cost_per_meter')
->required()
->numeric()
->prefix('R')
->label('Cost per Meter'),
Forms\Components\TextInput::make('cost_per_m2')
->required()
->numeric()
->prefix('R')
->label('Cost per m²'),
Forms\Components\Select::make('type')
->options([
'wallpaper' => 'Wallpaper',
'mural' => 'Mural',
'both' => 'Both',
])
->required(),
Forms\Components\TextInput::make('width')
->label('Width (meters)')
->numeric()
->step(0.01)
->minValue(0.1)
->default(1)
->helperText('Width of wallpaper roll in meters'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('cost_per_meter')
->money('ZAR')
->label('Cost/m')
->sortable(),
Tables\Columns\TextColumn::make('cost_per_m2')
->money('ZAR')
->label('Cost/m²')
->sortable(),
Tables\Columns\TextColumn::make('type')
->badge()
->color(fn (string $state): string => match ($state) {
'wallpaper' => 'success',
'mural' => 'info',
'both' => 'warning',
}),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\SelectFilter::make('type')
->options([
'wallpaper' => 'Wallpaper',
'mural' => 'Mural',
'both' => 'Both',
]),
])
->actions([
//
])
->bulkActions([
//
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ManagePrintStocks::route('/'),
];
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Filament\Resources\PrintStockResource\Pages;
use App\Filament\Resources\PrintStockResource;
use Filament\Actions;
use Filament\Resources\Pages\ManageRecords;
class ManagePrintStocks extends ManageRecords
{
protected static string $resource = PrintStockResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
protected function getTableActions(): array
{
return [
Actions\EditAction::make(),
Actions\DeleteAction::make(),
];
}
}
+128
View File
@@ -0,0 +1,128 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\ProductResource\Pages;
use App\Models\Product;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Schemas\Components\Section;
use Filament\Tables;
use Filament\Tables\Actions;
use Filament\Tables\Table;
class ProductResource extends Resource
{
protected static ?string $model = Product::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
public static function form(Schema $schema): Schema
{
return $schema
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\Select::make('category_id')
->relationship('category', 'name')
->required(),
Forms\Components\Textarea::make('description')
->maxLength(65535)
->columnSpanFull(),
Forms\Components\TextInput::make('price')
->required()
->numeric()
->prefix('R'),
Forms\Components\TextInput::make('stock')
->required()
->numeric()
->default(100),
Forms\Components\FileUpload::make('images')
->multiple()
->disk('public')
->directory('products')
->reorderable()
->maxSize(5120)
->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp']),
Forms\Components\Select::make('type')
->options([
'wallpaper' => 'Wallpaper',
'mural' => 'Mural',
])
->required(),
Forms\Components\Select::make('printStocks')
->relationship('printStocks', 'name')
->multiple()
->preload(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\ImageColumn::make('images.image_path')
->label('Image')
->disk('public')
->getStateUsing(function ($record) {
return $record->images->first()?->image_path;
})
->defaultImageUrl('/images/placeholder.png')
->circular()
->size(60),
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('category.name')
->sortable(),
Tables\Columns\TextColumn::make('price')
->money('ZAR')
->sortable(),
Tables\Columns\TextColumn::make('stock')
->numeric()
->sortable(),
Tables\Columns\TextColumn::make('type')
->badge()
->color(fn (string $state): string => match ($state) {
'wallpaper' => 'success',
'mural' => 'info',
}),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\SelectFilter::make('type')
->options([
'wallpaper' => 'Wallpaper',
'mural' => 'Mural',
]),
Tables\Filters\SelectFilter::make('category')
->relationship('category', 'name'),
])
->actions([
//
])
->bulkActions([
//
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListProducts::route('/'),
'create' => Pages\CreateProduct::route('/create'),
'edit' => Pages\EditProduct::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Filament\Resources\ProductResource\Pages;
use App\Filament\Resources\ProductResource;
use App\Models\ProductImage;
use Filament\Resources\Pages\CreateRecord;
class CreateProduct extends CreateRecord
{
protected static string $resource = ProductResource::class;
protected function mutateFormDataBeforeSave(array $data): array
{
// Store images in a temporary property
if (isset($data['images']) && is_array($data['images'])) {
$this->pendingImages = $data['images'];
}
unset($data['images']);
return $data;
}
protected function afterSave(): void
{
if (!isset($this->pendingImages)) {
return;
}
foreach ($this->pendingImages as $index => $imagePath) {
ProductImage::create([
'product_id' => $this->record->id,
'image_path' => $imagePath,
'sort_order' => $index,
]);
}
}
}
@@ -0,0 +1,65 @@
<?php
namespace App\Filament\Resources\ProductResource\Pages;
use App\Filament\Resources\ProductResource;
use App\Models\ProductImage;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditProduct extends EditRecord
{
protected static string $resource = ProductResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
protected function mutateFormDataBeforeFill(array $data): array
{
// Load existing images for the form
$data['images'] = $this->record->images()->orderBy('sort_order')->pluck('image_path')->toArray();
return $data;
}
protected function mutateFormDataBeforeSave(array $data): array
{
// Store images for processing after save
$this->pendingImages = $data['images'] ?? [];
unset($data['images']);
return $data;
}
protected function afterSave(): void
{
if (!isset($this->pendingImages)) {
return;
}
$newImages = $this->pendingImages;
$existingImages = $this->record->images()->pluck('image_path')->toArray();
// Find images to delete (in existing but not in new)
$imagesToDelete = array_diff($existingImages, $newImages);
foreach ($imagesToDelete as $imagePath) {
$this->record->images()->where('image_path', $imagePath)->delete();
}
// Add new images and update sort order
foreach ($newImages as $index => $imagePath) {
ProductImage::firstOrCreate(
[
'product_id' => $this->record->id,
'image_path' => $imagePath,
],
[
'sort_order' => $index,
]
)->update(['sort_order' => $index]);
}
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\ProductResource\Pages;
use App\Filament\Resources\ProductResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListProducts extends ListRecords
{
protected static string $resource = ProductResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}