Enhance DatabaseSeeder and Home view functionality

- Updated DatabaseSeeder to check for existing admin user before creation.
- Modified Home view to include max-height for collection items and added filter functionality for product categories.
- Introduced JavaScript for dynamic filtering of product cards based on selected categories.
This commit is contained in:
Mekan1206
2026-06-04 22:43:51 +05:00
parent 57002c184c
commit 06d2999c74
4 changed files with 180 additions and 9 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Filament\Resources\Categories\Schemas; namespace App\Filament\Resources\Categories\Schemas;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Schemas\Schema; use Filament\Schemas\Schema;

View File

@@ -18,10 +18,14 @@ class DatabaseSeeder extends Seeder
{ {
// User::factory(10)->create(); // User::factory(10)->create();
User::factory()->create([ if (User::where('email', 'admin@example.com')->doesntExist()) {
'name' => 'Admin', User::factory()->create([
'email' => 'admin@example.com', 'name' => 'Admin',
'password' => Hash::make('password'), 'email' => 'admin@example.com',
]); 'password' => Hash::make('password'),
]);
}
$this->call(DemoSeeder::class);
} }
} }

View File

@@ -0,0 +1,128 @@
<?php
namespace Database\Seeders;
use App\Models\CarouselSlide;
use App\Models\Category;
use App\Models\Collection;
use App\Models\CollectionItem;
use App\Models\Product;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class DemoSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Ensure storage directories exist
Storage::disk('public')->makeDirectory('slides');
Storage::disk('public')->makeDirectory('products');
Storage::disk('public')->makeDirectory('collections');
$this->command->info('Seeding Carousel Slides...');
for ($i = 1; $i <= 3; $i++) {
$imagePath = 'slides/slide-'.$i.'.jpg';
$this->generateDummyImage($imagePath, 1200, 600, 'Slide '.$i);
CarouselSlide::create([
'title' => 'Täze harytlar '.$i,
'subtitle' => 'Iň oňat bahalar bilen öýüňizi bezäň',
'order' => $i,
'is_active' => true,
'image' => $imagePath,
]);
}
$this->command->info('Seeding Categories & Products...');
$categories = ['Diwanlar', 'Krowatlar', 'Stollar', 'Oturgyçlar', 'Şkaflar'];
foreach ($categories as $index => $categoryName) {
$category = Category::create([
'name' => $categoryName,
'icon' => 'heroicon-o-archive-box', // dummy icon
'order' => $index + 1,
]);
for ($p = 1; $p <= 4; $p++) {
$imagePath = 'products/product-'.$category->id.'-'.$p.'.jpg';
$this->generateDummyImage($imagePath, 800, 800, $categoryName.' '.$p);
Product::create([
'category_id' => $category->id,
'name' => 'Owadan '.Str::lower($categoryName).' modeli '.$p,
'description' => 'Bu '.Str::lower($categoryName).' örän ýokary hilli we berk materialdan öndürilen. Öýüňize bezeg gatar.',
'price' => rand(1000, 15000),
'is_active' => true,
'image' => $imagePath,
]);
}
}
$this->command->info('Seeding Collections & Items...');
$collections = ['Tomus Kolleksiýasy', 'Täze Ýyl', 'Otag Bezegi'];
foreach ($collections as $index => $collectionName) {
$collection = Collection::create([
'title' => $collectionName,
'subtitle' => 'Aýratyn dizaýnlar',
'order' => $index + 1,
'is_active' => true,
]);
for ($i = 1; $i <= 3; $i++) {
$imagePath = 'collections/item-'.$collection->id.'-'.$i.'.jpg';
$this->generateDummyImage($imagePath, 600, 800, 'Item '.$i);
CollectionItem::create([
'collection_id' => $collection->id,
'title' => $collectionName.' elementi '.$i,
'subtitle' => 'Gözel dizaýn '.$i,
'order' => $i,
'is_active' => true,
'image' => $imagePath,
]);
}
}
}
private function generateDummyImage(string $path, int $width, int $height, string $text): void
{
// Try to download a random image from picsum, if it fails, generate a basic image using GD
try {
// Using UI Avatars for reliable and fast text-based placeholders
$url = 'https://ui-avatars.com/api/?name='.urlencode($text).'&size=512&background=random&color=fff&font-size=0.33';
$contents = file_get_contents($url);
if ($contents) {
Storage::disk('public')->put($path, $contents);
return;
}
} catch (\Exception $e) {
// fallback
}
// GD fallback if network fails
if (extension_loaded('gd')) {
$image = imagecreatetruecolor($width, $height);
$bg = imagecolorallocate($image, rand(100, 200), rand(100, 200), rand(100, 200));
$textColor = imagecolorallocate($image, 255, 255, 255);
imagefill($image, 0, 0, $bg);
imagestring($image, 5, $width / 2 - strlen($text) * 4, $height / 2 - 8, $text, $textColor);
ob_start();
imagejpeg($image);
$contents = ob_get_clean();
imagedestroy($image);
Storage::disk('public')->put($path, $contents);
} else {
// If GD is not available and download failed, just put an empty file or dummy svg
$svg = '<svg xmlns="http://www.w3.org/2000/svg" width="'.$width.'" height="'.$height.'"><rect width="100%" height="100%" fill="#ccc"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="sans-serif" font-size="20px" fill="#333">'.$text.'</text></svg>';
Storage::disk('public')->put($path, $svg);
}
}
}

View File

@@ -57,7 +57,7 @@
</div> </div>
<div class="flex overflow-x-auto hide-scrollbar gap-6 pb-8 snap-x snap-mandatory" id="carousel-{{ $collection->id }}"> <div class="flex overflow-x-auto hide-scrollbar gap-6 pb-8 snap-x snap-mandatory" id="carousel-{{ $collection->id }}">
@foreach($collection->collectionItems as $item) @foreach($collection->collectionItems as $item)
<div class="min-w-[280px] md:min-w-[320px] aspect-[3/4] relative snap-start group cursor-pointer border border-antique-gold/40 bg-surface-container-low p-4 flex flex-col justify-end overflow-hidden"> <div style="max-height: 420px;" class="min-w-[280px] md:min-w-[320px] aspect-[3/4] relative snap-start group cursor-pointer border border-antique-gold/40 bg-surface-container-low p-4 flex flex-col justify-end overflow-hidden">
<div class="absolute inset-4 border border-outline/30 z-10 pointer-events-none transition-transform duration-500 group-hover:scale-95"></div> <div class="absolute inset-4 border border-outline/30 z-10 pointer-events-none transition-transform duration-500 group-hover:scale-95"></div>
<div class="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-105 opacity-80" data-alt="{{ $item->subtitle }}" style="background-image: url('{{ $item->image ? Storage::url($item->image) : asset('assets/images/collection-chocolate-cake.jpg') }}');"></div> <div class="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-105 opacity-80" data-alt="{{ $item->subtitle }}" style="background-image: url('{{ $item->image ? Storage::url($item->image) : asset('assets/images/collection-chocolate-cake.jpg') }}');"></div>
<div class="absolute inset-0 bg-gradient-to-t from-primary-container/90 via-primary-container/30 to-transparent"></div> <div class="absolute inset-0 bg-gradient-to-t from-primary-container/90 via-primary-container/30 to-transparent"></div>
@@ -74,11 +74,11 @@
<section class="py-16 px-margin-mobile md:px-margin-desktop max-w-container-max mx-auto bg-surface-container-low border-y border-outline/10" id="menu"> <section class="py-16 px-margin-mobile md:px-margin-desktop max-w-container-max mx-auto bg-surface-container-low border-y border-outline/10" id="menu">
<div class="flex overflow-x-auto hide-scrollbar gap-4 pb-8 mb-8 justify-center animate-on-scroll"> <div class="flex overflow-x-auto hide-scrollbar gap-4 pb-8 mb-8 justify-center animate-on-scroll">
<button class="px-6 py-2 rounded-full bg-ink-brown text-surface-container-lowest font-label-sm text-label-sm uppercase tracking-wide flex items-center gap-2 whitespace-nowrap"> <button class="filter-btn px-6 py-2 rounded-full bg-ink-brown text-surface-container-lowest font-label-sm text-label-sm uppercase tracking-wide flex items-center gap-2 whitespace-nowrap" data-category="all">
<span class="material-symbols-outlined text-[16px]">cake</span> Hemmesi <span class="material-symbols-outlined text-[16px]">cake</span> Hemmesi
</button> </button>
@foreach($categories as $category) @foreach($categories as $category)
<button class="px-6 py-2 rounded-full border border-outline/30 text-on-surface-variant hover:border-antique-gold hover:text-ink-brown transition-colors font-label-sm text-label-sm uppercase tracking-wide flex items-center gap-2 whitespace-nowrap bg-surface"> <button class="filter-btn px-6 py-2 rounded-full border border-outline/30 text-on-surface-variant hover:border-antique-gold hover:text-ink-brown transition-colors font-label-sm text-label-sm uppercase tracking-wide flex items-center gap-2 whitespace-nowrap bg-surface" data-category="{{ $category->id }}">
@if($category->icon) @if($category->icon)
<span class="material-symbols-outlined text-[16px]">{{ $category->icon }}</span> <span class="material-symbols-outlined text-[16px]">{{ $category->icon }}</span>
@endif @endif
@@ -88,7 +88,7 @@
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-12"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-12">
@foreach($products as $index => $product) @foreach($products as $index => $product)
<div class="group animate-on-scroll" style="transition-delay: {{ ($index % 4) * 100 }}ms;"> <div class="product-card group animate-on-scroll" data-category="{{ $product->category_id }}" style="transition-delay: {{ ($index % 4) * 100 }}ms;">
<div class="aspect-square mb-4 overflow-hidden relative bg-surface-variant"> <div class="aspect-square mb-4 overflow-hidden relative bg-surface-variant">
<img alt="{{ $product->name }}" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" data-alt="{{ $product->name }}" src="{{ $product->image ? Storage::url($product->image) : asset('assets/images/product-macaron.jpg') }}"/> <img alt="{{ $product->name }}" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" data-alt="{{ $product->name }}" src="{{ $product->image ? Storage::url($product->image) : asset('assets/images/product-macaron.jpg') }}"/>
<div class="absolute inset-0 border border-outline/10 pointer-events-none m-2"></div> <div class="absolute inset-0 border border-outline/10 pointer-events-none m-2"></div>
@@ -128,4 +128,42 @@
</div> </div>
</div> </div>
</section> </section>
<script>
document.addEventListener("DOMContentLoaded", () => {
const filterBtns = document.querySelectorAll('.filter-btn');
const productCards = document.querySelectorAll('.product-card');
const activeClasses = ['bg-ink-brown', 'text-surface-container-lowest'];
const inactiveClasses = ['border', 'border-outline/30', 'text-on-surface-variant', 'bg-surface'];
filterBtns.forEach(btn => {
btn.addEventListener('click', () => {
const category = btn.getAttribute('data-category');
// 1. Update button styles
filterBtns.forEach(b => {
b.classList.remove(...activeClasses);
b.classList.add(...inactiveClasses);
});
btn.classList.remove(...inactiveClasses);
btn.classList.add(...activeClasses);
// 2. Filter products
productCards.forEach(card => {
if (category === 'all' || card.getAttribute('data-category') === category) {
card.style.display = 'block';
// Small timeout to allow display:block to apply before re-animating
setTimeout(() => {
card.classList.add('is-visible');
}, 50);
} else {
card.style.display = 'none';
card.classList.remove('is-visible');
}
});
});
});
});
</script>
</x-layouts.app> </x-layouts.app>