From 8972b0d099045f5023288ce26aa5f48de2c45da4 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Tue, 2 Jul 2024 00:40:56 +0200 Subject: [PATCH 01/16] created the dinamical form for size, stock and color, still facing some issues --- app/Http/Controllers/ProductController.php | 37 +++-- app/Http/Requests/ProductRequest.php | 53 ++++--- app/Models/Product.php | 5 +- app/Models/ProductColor.php | 10 +- app/Models/Size.php | 21 +++ ...024_03_10_185543_create_products_table.php | 2 +- .../2024_05_22_202606_create_sizes_table.php | 22 +++ ...5_22_202748_alter_product_colors_table.php | 25 ++++ .../2024_05_22_202852_alter_product_table.php | 22 +++ ...7_01_215351_alter_product_colors_table.php | 29 ++++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/ProductSeeder.php | 26 ++-- database/seeders/SizeSeeder.php | 23 +++ resources/views/layouts/app.blade.php | 1 + resources/views/products/create.blade.php | 139 ++++++++---------- resources/views/products/index.blade.php | 15 +- routes/web.php | 4 +- 17 files changed, 300 insertions(+), 135 deletions(-) create mode 100644 app/Models/Size.php create mode 100644 database/migrations/2024_05_22_202606_create_sizes_table.php create mode 100644 database/migrations/2024_05_22_202748_alter_product_colors_table.php create mode 100644 database/migrations/2024_05_22_202852_alter_product_table.php create mode 100644 database/migrations/2024_07_01_215351_alter_product_colors_table.php create mode 100644 database/seeders/SizeSeeder.php diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 27b0ee0..39a02db 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Models\Size; use App\Models\Brand; use App\Models\Product; use App\Models\Category; @@ -33,8 +34,9 @@ public function create() $categories = Category::all(); $brands = Brand::all(); $discounts = Discount::where('status', 'active')->get(); + $sizes = Size::all(); // Add this line to fetch sizes - return view('products.create', compact('categories', 'brands', 'discounts')); + return view('products.create', compact('categories', 'brands', 'discounts', 'sizes')); } /** @@ -44,20 +46,24 @@ public function store(ProductRequest $request) { $validated = $request->validated(); - $sizes = $request->input('sizes', []); - $validated['size'] = json_encode($sizes); - + // Create the product $product = Product::create($validated); - $colors = $request->input('colors', []); + // Handle product colors and sizes + $colorsAndSizes = $request->input('colors_and_sizes', []); + dd($request->input('colors_and_sizes')); + foreach ($request->input('colors_and_sizes') as $colorAndSize) { - foreach ($colors as $color) { + // doesnt work, gonna fix it tomorrow ProductColor::create([ 'product_id' => $product->id, - 'color_name' => $color, + 'size_id' => $colorAndSize['size_id'], // Ensure 'size_id' is correctly set + 'color_name' => $colorAndSize['color'], // Ensure 'color' key exists in each element + 'stock_quantity' => $colorAndSize['stock'], // Ensure 'stock' key exists in each element ]); } + // Handle images $images = $request->file('images'); if ($images) { foreach ($images as $image) { @@ -71,6 +77,7 @@ public function store(ProductRequest $request) } } + // Handle selected discount if ($request->filled('selectedDiscountId')) { $product->discount_id = $request->input('selectedDiscountId'); $product->save(); @@ -79,6 +86,8 @@ public function store(ProductRequest $request) return redirect()->route('products.index')->with('success', 'Продуктот е уÑпешно додаден!'); } + + /** * Show the form for editing the specified resource. */ @@ -103,22 +112,24 @@ public function update(ProductRequest $request, Product $product) { $validated = $request->validated(); - $sizes = $request->input('sizes', []); - $validated['size'] = json_encode($sizes); - + // Update the product $product->update($validated); - $colors = $request->input('colors', []); + // Update colors and sizes + $colorsAndSizes = $request->input('colors_and_sizes', []); ProductColor::where('product_id', $product->id)->delete(); - foreach ($colors as $color) { + foreach ($colorsAndSizes as $colorAndSize) { ProductColor::create([ 'product_id' => $product->id, - 'color_name' => $color, + 'color_name' => $colorAndSize['color'], + 'size_id' => $colorAndSize['size_id'], + 'quantity' => $colorAndSize['stock'], ]); } + // Handle images $images = $request->file('images'); if ($images) { diff --git a/app/Http/Requests/ProductRequest.php b/app/Http/Requests/ProductRequest.php index d129378..d82d822 100644 --- a/app/Http/Requests/ProductRequest.php +++ b/app/Http/Requests/ProductRequest.php @@ -25,19 +25,26 @@ public function rules(): array 'name' => 'required|string|max:255', 'description' => 'required|string', 'price' => 'required|numeric|min:0', - 'stock_quantity' => 'required|numeric|min:1', - 'sizes' => 'required|array|min:1', + // 'sizes' => 'required|array|min:1', + // 'sizes.*' => 'required|string', + // 'colors' => 'required|array|min:1', + // 'colors.*' => 'required|string', + // 'stock_quantities' => 'required|array|min:1', + // 'stock_quantities.*' => 'required|numeric|min:1', 'size_advice' => 'required|string', - 'colors' => 'required|array|min:1', - 'colors.*' => 'required|string', 'maintenance' => 'required|string', - 'tags' => 'string|starts_with:#', - 'images.*' => 'image|mimes:jpeg,png,jpg,gif,svg|max:6048', - 'category_id' => 'required', - 'brand_id' => 'required', - 'discount_id' => 'nullable', + 'tags' => 'nullable|string|starts_with:#', + 'images.*' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:6048', + 'category_id' => 'required|exists:categories,id', + 'brand_id' => 'required|exists:brands,id', + 'discount_id' => 'nullable|exists:discounts,id', + // 'colors_and_sizes' => 'required|array', + // 'colors_and_sizes.*.color' => 'required|string', + // 'colors_and_sizes.*.size_id' => 'required|exists:sizes,id', + // 'colors_and_sizes.*.stock' => 'required|integer|min:0', ]; - // името да е уникатно Ñамо кога креираме ноњ продукт + + // Ensure unique name when creating a new product if ($this->isMethod('post')) { $rules['name'] .= '|unique:products'; } @@ -60,17 +67,21 @@ public function messages() 'price.numeric' => 'Цената мора да биде нумеричка вредноÑÑ‚.', 'price.min' => 'Цената мора да биде најмалку :min.', - 'stock_quantity.required' => 'Количината на залиха е задолжителна.', - 'stock_quantity.numeric' => 'Количината на залиха мора да биде нумеричка вредноÑÑ‚.', - 'stock_quantity.min' => 'Количината на залиха мора да биде најмалку :min.', + // 'sizes.required' => 'Барем една големина мора да биде избрана.', + // 'sizes.*.required' => 'Изборот на големина е задолжителен.', - 'sizes.required' => 'Барем една големина мора да биде избрана.', - 'size_advice.required' => 'Советот за големината е задолжителен.', + // 'colors.required' => 'Барем една боја мора да биде избрана.', + // 'colors.array' => 'Изборот на бои мора да биде низа.', + // 'colors.min' => 'Изборот на бои мора да вклучува најмалку :min боја.', + // 'colors.*.required' => 'Изборот на боја е задолжителен.', - 'colors.required' => 'Барем една боја мора да биде избрана.', - 'colors.array' => 'Изборот на бои мора да биде низа.', - 'colors.min' => 'Изборот на бои мора да вклучува најмалку :min боја.', - 'colors.*.required' => 'Изборот на боја е задолжителен.', + // 'stock_quantities.required' => 'Количината на залиха е задолжителна.', + // 'stock_quantities.array' => 'Количината на залиха мора да биде низа.', + // 'stock_quantities.*.required' => 'Количината на залиха е задолжителна за Ñекој избран артикл.', + // 'stock_quantities.*.numeric' => 'Количината на залиха мора да биде нумеричка вредноÑÑ‚.', + // 'stock_quantities.*.min' => 'Количината на залиха мора да биде најмалку :min.', + + 'size_advice.required' => 'Советот за големината е задолжителен.', 'maintenance.required' => 'УпатÑтвото за одржување е задолжително.', @@ -79,8 +90,12 @@ public function messages() 'images.*.max' => 'Датотеката не Ñмее да биде поголема од :max килобајти.', 'category_id.required' => 'Категоријата е задолжителна.', + 'category_id.exists' => 'Избраната категорија не поÑтои.', 'brand_id.required' => 'Брендот е задолжителен.', + 'brand_id.exists' => 'Избраниот бренд не поÑтои.', + + 'discount_id.exists' => 'Избраниот попуÑÑ‚ не поÑтои.', ]; } } diff --git a/app/Models/Product.php b/app/Models/Product.php index 2678628..a9f0dc6 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Models\ProductColor; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -12,8 +11,8 @@ class Product extends Model use HasFactory, SoftDeletes; protected $fillable = [ - 'name', 'description', 'price', 'stock_quantity', - 'size', 'size_advice', 'maintenance', 'tags', + 'name', 'description', 'price', + 'size_advice', 'maintenance', 'tags', 'discount_id', 'category_id', 'brand_id' ]; diff --git a/app/Models/ProductColor.php b/app/Models/ProductColor.php index 40d097c..41453e7 100644 --- a/app/Models/ProductColor.php +++ b/app/Models/ProductColor.php @@ -9,7 +9,7 @@ class ProductColor extends Model { use HasFactory; - protected $fillable = ['product_id', 'color_name']; + protected $fillable = ['product_id', 'color_name', 'size_id', 'quantity']; /** * Get the product that owns the color. @@ -18,4 +18,12 @@ public function product() { return $this->belongsTo(Product::class); } + + /** + * Get the size associated with the product color. + */ + public function size() + { + return $this->belongsTo(Size::class); + } } diff --git a/app/Models/Size.php b/app/Models/Size.php new file mode 100644 index 0000000..298633c --- /dev/null +++ b/app/Models/Size.php @@ -0,0 +1,21 @@ +<?php + +namespace App\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; + +class Size extends Model +{ + use HasFactory; + + protected $fillable = ['name']; + + /** + * Get the product colors associated with the size. + */ + public function productColors() + { + return $this->hasMany(ProductColor::class); + } +} diff --git a/database/migrations/2024_03_10_185543_create_products_table.php b/database/migrations/2024_03_10_185543_create_products_table.php index a119a69..2d8f616 100644 --- a/database/migrations/2024_03_10_185543_create_products_table.php +++ b/database/migrations/2024_03_10_185543_create_products_table.php @@ -16,7 +16,7 @@ public function up(): void $table->string('name'); $table->text('description'); $table->integer('price'); - $table->integer('stock_quantity'); + $table->integer('stock_quantity')->nullable(); $table->enum('size', ['xs', 's', 'm', 'l', 'xl']); $table->string('size_advice'); $table->text('maintenance'); diff --git a/database/migrations/2024_05_22_202606_create_sizes_table.php b/database/migrations/2024_05_22_202606_create_sizes_table.php new file mode 100644 index 0000000..0a77834 --- /dev/null +++ b/database/migrations/2024_05_22_202606_create_sizes_table.php @@ -0,0 +1,22 @@ +<?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('sizes', function (Blueprint $table) { + $table->id(); + $table->string('name'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('sizes'); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_05_22_202748_alter_product_colors_table.php b/database/migrations/2024_05_22_202748_alter_product_colors_table.php new file mode 100644 index 0000000..77654db --- /dev/null +++ b/database/migrations/2024_05_22_202748_alter_product_colors_table.php @@ -0,0 +1,25 @@ +<?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::table('product_colors', function (Blueprint $table) { + $table->foreignId('size_id')->constrained('sizes')->after('product_id'); + $table->dropColumn('color_name'); + }); + } + + public function down(): void + { + Schema::table('product_colors', function (Blueprint $table) { + $table->dropForeign(['size_id']); + $table->dropColumn('size_id'); + $table->string('color_name'); + }); + } +}; diff --git a/database/migrations/2024_05_22_202852_alter_product_table.php b/database/migrations/2024_05_22_202852_alter_product_table.php new file mode 100644 index 0000000..8f61c84 --- /dev/null +++ b/database/migrations/2024_05_22_202852_alter_product_table.php @@ -0,0 +1,22 @@ +<?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::table('products', function (Blueprint $table) { + $table->dropColumn('size'); + }); + } + + public function down(): void + { + Schema::table('products', function (Blueprint $table) { + $table->enum('size', ['xs', 's', 'm', 'l', 'xl']); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_07_01_215351_alter_product_colors_table.php b/database/migrations/2024_07_01_215351_alter_product_colors_table.php new file mode 100644 index 0000000..fdde80d --- /dev/null +++ b/database/migrations/2024_07_01_215351_alter_product_colors_table.php @@ -0,0 +1,29 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + Schema::table('product_colors', function (Blueprint $table) { + $table->string('color_name'); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('product_colors', function (Blueprint $table) { + $table->dropColumn('color_name'); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 997f623..82140ea 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -22,6 +22,7 @@ public function run(): void $this->call(UsersSeeder::class); $this->call(CategorySeeder::class); $this->call(BrandsTableSeeder::class); + $this->call(SizeSeeder::class); $this->call(ProductSeeder::class); $this->call(DiscountSeeder::class); } diff --git a/database/seeders/ProductSeeder.php b/database/seeders/ProductSeeder.php index 4dfba85..a21aa67 100644 --- a/database/seeders/ProductSeeder.php +++ b/database/seeders/ProductSeeder.php @@ -3,14 +3,13 @@ namespace Database\Seeders; use App\Models\Brand; - use App\Models\Product; use App\Models\Category; -use Faker\Factory as Faker; use App\Models\ProductColor; use App\Models\ProductImage; +use App\Models\Size; use Illuminate\Database\Seeder; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; +use Faker\Factory as Faker; class ProductSeeder extends Seeder { @@ -25,19 +24,19 @@ public function run() $brands = Brand::all()->pluck('id')->toArray(); $categories = Category::all()->pluck('id')->toArray(); + $sizes = Size::all()->pluck('id')->toArray(); // Fetch sizes $colors = ['black', 'white', 'yellow', 'blue', 'green', 'red', 'pink']; foreach (range(1, 20) as $index) { $product = new Product(); $product->name = $faker->sentence(2); $product->description = $faker->text; - //цената Ñекогаш да завршува на xx90 (1190, 1390, 2990, 3290 итн...) + // Ensure price ends with xx90 (1190, 1390, 2990, 3290, etc.) $randomPrice = $faker->numberBetween(99, 399); $roundedPrice = ceil($randomPrice / 10) * 10; $product->price = ($roundedPrice * 10) - 10; $product->stock_quantity = $faker->numberBetween(0, 20); - $product->size = json_encode($faker->randomElements(['xs', 's', 'm', 'l', 'xl'], $count = $faker->numberBetween(1, 3))); $product->size_advice = $faker->sentence(5); $product->maintenance = $faker->sentence(10); $product->tags = implode(',', $faker->randomElements(['#summer', '#winter', '#casual', '#formal', '#shirts', '#shoes'], $count = rand(1, 3))); @@ -45,15 +44,20 @@ public function run() $product->category_id = $faker->randomElement($categories); $product->save(); - // бои, ама бројо на различни бои да не е поголем од stock_quantity + // Colors, but the number of different colors shouldn't exceed stock_quantity $maxColors = min($product->stock_quantity, count($colors)); $randomColors = $faker->randomElements($colors, $faker->numberBetween(1, $maxColors)); foreach ($randomColors as $color) { - ProductColor::create([ - 'product_id' => $product->id, - 'color_name' => $color, - 'quantity' => $faker->numberBetween(1, 10), - ]); + // Randomly select sizes for each product color + $randomSizes = $faker->randomElements($sizes, $faker->numberBetween(1, 3)); + foreach ($randomSizes as $sizeId) { + ProductColor::create([ + 'product_id' => $product->id, + 'size_id' => $sizeId, + 'color_name' => $color, // Add color_name here + 'quantity' => $faker->numberBetween(1, 10), + ]); + } } $this->generateProductImages($product); diff --git a/database/seeders/SizeSeeder.php b/database/seeders/SizeSeeder.php new file mode 100644 index 0000000..3d35cf1 --- /dev/null +++ b/database/seeders/SizeSeeder.php @@ -0,0 +1,23 @@ +<?php + +namespace Database\Seeders; + +use Illuminate\Database\Seeder; +use App\Models\Size; + +class SizeSeeder extends Seeder +{ + /** + * Run the database seeds. + * + * @return void + */ + public function run() + { + $sizes = ['xs', 's', 'm', 'l', 'xl']; + + foreach ($sizes as $size) { + Size::create(['name' => $size]); + } + } +} \ No newline at end of file diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index ed3d739..a2d710c 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -40,5 +40,6 @@ <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> + </body> </html> diff --git a/resources/views/products/create.blade.php b/resources/views/products/create.blade.php index f0f4443..8853604 100644 --- a/resources/views/products/create.blade.php +++ b/resources/views/products/create.blade.php @@ -4,6 +4,18 @@ <div class="container px-2 py-5"> <div class="row"> <div class="col-md-8 offset-md-2"> + + {{-- validation errors: --}} + @if ($errors->any()) + <div class="alert alert-danger"> + <ul> + @foreach ($errors->all() as $error) + <li>{{ $error }}</li> + @endforeach + </ul> + </div> + @endif + <form id="create_product" method="POST" action="{{ route('products.store') }}" enctype="multipart/form-data"> @csrf <div class="row mb-3"> @@ -13,7 +25,7 @@ </div> <div class="col"> <div class="mb-3 d-flex justify-content-end"> - <select class="form-select w-50 " id="status" name="status"> + <select class="form-select w-50" id="status" name="status"> <option disabled selected>СтатуÑ</option> <option value="active">Ðктивен</option> <option value="archived">Ðрхивиран</option> @@ -48,48 +60,12 @@ </div> </div> - <div class="mb-3 row align-items-center"> - <div class="col-auto"> - <label for="stock_quantity" class="form-label">Количина</label> - </div> - <div class="col-auto"> - <div class="input-group"> - <button class="btn border border-secondary rounded-circle" type="button" id="minus-btn">-</button> - <input type="number" class="form-control text-center stock-quantity" id="stock_quantity" name="stock_quantity" value="1" min="0" oninput="this.value = Math.abs(this.value)"> - <button class="btn border border-secondary rounded-circle" type="button" id="plus-btn">+</button> - @if ($errors->has('stock_quantiy')) - <span class="text-danger">{{ $errors->first('stock_quantiy') }}</span> - @endif - </div> - </div> - </div> - <div class="mb-3"> - <label for="size" class="form-label">Величина</label><br> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_xs" name="sizes[]" value="xs" {{ in_array('xs', old('sizes', [])) ? 'checked' : '' }}> - <label class="form-check-label" for="size_xs">XS</label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_s" name="sizes[]" value="s" {{ in_array('s', old('sizes', [])) ? 'checked' : '' }}> - <label class="form-check-label" for="size_s">S</label> + <label for="dynamicInputs" class="form-label">Величина, Боја, Количина</label> + <div id="dynamicInputs"> + <!-- for dynamic inputs --> </div> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_m" name="sizes[]" value="m" {{ in_array('m', old('sizes', [])) ? 'checked' : '' }}> - <label class="form-check-label" for="size_m">M</label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_l" name="sizes[]" value="l" {{ in_array('l', old('sizes', [])) ? 'checked' : '' }}> - <label class="form-check-label" for="size_l">L</label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_xl" name="sizes[]" value="xl" {{ in_array('xl', old('sizes', [])) ? 'checked' : '' }}> - <label class="form-check-label" for="size_xl">XL</label> - </div> <br> - <span class="text-danger stock_quantity_exceeded_for_sizes p2"></span> - @if ($errors->has('size')) - <span class="text-danger stock_quantity_exceeded">{{ $errors->first('size') }}</span> - @endif + <button type="button" id="addInputBtn" class="btn btn-secondary mt-2">Додај</button> </div> <div class="mb-3"> @@ -100,42 +76,6 @@ @endif </div> - <div class="mb-3"> - <label class="form-label">Боја</label><br> - <div class="form-check form-check-inline"> - <input class="form-check-input color-checkbox-input" type="checkbox" id="color_black" name="colors[]" value="black" {{ in_array('black', old('colors', [])) ? 'checked' : '' }}> - <label class="black color-checkbox" for="color_black"></label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input color-checkbox-input" type="checkbox" id="color_white" name="colors[]" value="white" {{ in_array('white', old('colors', [])) ? 'checked' : '' }}> - <label class="white color-checkbox" for="color_white"></label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input color-checkbox-input" type="checkbox" id="color_yellow" name="colors[]" value="yellow" {{ in_array('yellow', old('colors', [])) ? 'checked' : '' }}> - <label class="yellow color-checkbox" for="color_yellow"></label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input color-checkbox-input" type="checkbox" id="color_blue" name="colors[]" value="blue" {{ in_array('blue', old('colors', [])) ? 'checked' : '' }}> - <label class="blue color-checkbox" for="color_blue"></label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input color-checkbox-input" type="checkbox" id="color_green" name="colors[]" value="green" {{ in_array('green', old('colors', [])) ? 'checked' : '' }}> - <label class="green color-checkbox" for="color_green"></label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input color-checkbox-input" type="checkbox" id="color_red" name="colors[]" value="red" {{ in_array('red', old('colors', [])) ? 'checked' : '' }}> - <label class="red color-checkbox" for="color_red"></label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input color-checkbox-input" type="checkbox" id="color_pink" name="colors[]" value="pink" {{ in_array('pink', old('colors', [])) ? 'checked' : '' }}> - <label class="pink color-checkbox" for="color_pink"></label> - </div> - <span class="text-danger stock_quantity_exceeded_for_colors"></span> - @if ($errors->has('colors')) - <span class="text-danger">{{ $errors->first('colors') }}</span> - @endif - </div> - <div class="mb-3"> <label for="maintenance" class="form-label">ÐаÑоки за одржување</label> <textarea class="form-control" id="maintenance" name="maintenance" rows="3">{{ old('maintenance') }}</textarea> @@ -160,7 +100,7 @@ <input type="file" class="form-control" accept="image/*" name="images[]"> <div class="image-container"> @if(Session::has('uploaded_image')) - <img src="{{ asset(Session::get('uploaded_image')) }}" class="uploaded-image"> + <img src="{{ asset(Session::get('uploaded_image')) }}" class="uploaded-image"> @else <img src="{{ $oldImages[$i] ?? '' }}" class="uploaded-image"> @endif @@ -252,3 +192,46 @@ </div> </div> @endsection + +<script> + document.addEventListener('DOMContentLoaded', function () { + const addInputBtn = document.getElementById('addInputBtn'); + const dynamicInputsContainer = document.getElementById('dynamicInputs'); + + addInputBtn.addEventListener('click', function () { + const newInputGroup = document.createElement('div'); + newInputGroup.classList.add('mb-3', 'input-group'); + + newInputGroup.innerHTML = ` + <select class="form-select me-2" name="colors_and_sizes[][size_id]" required> + <option value="" disabled selected>Величина</option> + <option value="xs">XS</option> + <option value="s">S</option> + <option value="m">M</option> + <option value="l">L</option> + <option value="xl">XL</option> + </select> + <select class="form-select me-2" name="colors_and_sizes[][color]" required> + <option value="" disabled selected>Боја</option> + <option value="black">Black</option> + <option value="white">White</option> + <option value="yellow">Yellow</option> + <option value="blue">Blue</option> + <option value="green">Green</option> + <option value="red">Red</option> + <option value="pink">Pink</option> + </select> + <input type="number" class="form-control me-2" name="colors_and_sizes[][stock]" min="0" placeholder="Количина" required> + <button type="button" class="btn btn-danger remove-input-btn"><i class="fa fa-minus"></i></button> + `; + + dynamicInputsContainer.appendChild(newInputGroup); + + // Add event listener to the remove button + newInputGroup.querySelector('.remove-input-btn').addEventListener('click', function () { + newInputGroup.remove(); + }); + }); + }); +</script> + diff --git a/resources/views/products/index.blade.php b/resources/views/products/index.blade.php index a6e8295..2fce629 100644 --- a/resources/views/products/index.blade.php +++ b/resources/views/products/index.blade.php @@ -38,9 +38,9 @@ </div> @else @foreach($product->images as $index => $image) - <div class="carousel-item {{ $index === 0 ? 'active' : '' }}" style="position: relative; height: 300px;"> - <img src="{{ Storage::url($image->image) }}" class="d-block img-fluid rounded mx-auto" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-height: 100%; max-width: 100%; object-fit: contain;" alt="Product Image"> - </div> + <div class="carousel-item {{ $index === 0 ? 'active' : '' }}" style="position: relative; height: 300px;"> + <img src="{{ Storage::url($image->image) }}" class="d-block img-fluid rounded mx-auto" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-height: 100%; max-width: 100%; object-fit: contain;" alt="Product Image"> + </div> @endforeach @endif </div> @@ -64,8 +64,11 @@ <div class="col"> <p class="card-text h5 mt-2"> Величина: - @foreach(json_decode($product->size) as $size) - <span class="badge badge-secondary">{{ strtoupper($size) }}</span> + @foreach($product->productColors as $productColor) + @if(!$loop->first) + <span class="ml-1">,</span> + @endif + <span class="badge badge-secondary">{{ strtoupper($productColor->size->name) }}</span> @endforeach </p> </div> @@ -105,6 +108,4 @@ <div class="d-flex justify-content-center mt-4 pagination"> {{ $products->links('pagination::bootstrap-4') }} </div> - </div> @endsection - diff --git a/routes/web.php b/routes/web.php index fa613f7..5716c24 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,6 +6,8 @@ use App\Http\Controllers\ProductController; use App\Http\Controllers\DiscountController; use App\Http\Controllers\Auth\LoginController; +use Illuminate\Support\Facades\Auth; + /* |-------------------------------------------------------------------------- @@ -57,8 +59,6 @@ Route::group(['middleware' => ['admin', 'super_admin']], function () { - - }); -- GitLab From 2988d66266edc4d19f30995522aecabb01c530f1 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Wed, 3 Jul 2024 00:09:59 +0200 Subject: [PATCH 02/16] finished the issue with the size/stock/color... in all three blades> create, edit, and index (products) --- app/Http/Controllers/ProductController.php | 61 +++--- app/Http/Requests/ProductRequest.php | 38 +--- database/seeders/ProductSeeder.php | 10 +- resources/views/products/create.blade.php | 25 ++- resources/views/products/edit.blade.php | 221 +++++++++++---------- resources/views/products/index.blade.php | 8 +- 6 files changed, 185 insertions(+), 178 deletions(-) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 39a02db..678dd8e 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -34,8 +34,7 @@ public function create() $categories = Category::all(); $brands = Brand::all(); $discounts = Discount::where('status', 'active')->get(); - $sizes = Size::all(); // Add this line to fetch sizes - + $sizes = Size::all(); return view('products.create', compact('categories', 'brands', 'discounts', 'sizes')); } @@ -46,24 +45,38 @@ public function store(ProductRequest $request) { $validated = $request->validated(); - // Create the product + Log::info('Request Data:', $request->all()); + $product = Product::create($validated); - // Handle product colors and sizes $colorsAndSizes = $request->input('colors_and_sizes', []); - dd($request->input('colors_and_sizes')); - foreach ($request->input('colors_and_sizes') as $colorAndSize) { - // doesnt work, gonna fix it tomorrow - ProductColor::create([ - 'product_id' => $product->id, - 'size_id' => $colorAndSize['size_id'], // Ensure 'size_id' is correctly set - 'color_name' => $colorAndSize['color'], // Ensure 'color' key exists in each element - 'stock_quantity' => $colorAndSize['stock'], // Ensure 'stock' key exists in each element - ]); + // за дебагирање + Log::info('Colors and Sizes Data:', $colorsAndSizes); + + foreach ($colorsAndSizes as $colorAndSize) { + if (isset($colorAndSize['size_id']) && isset($colorAndSize['color']) && isset($colorAndSize['stock'])) { + try { + ProductColor::create([ + 'product_id' => $product->id, + 'size_id' => $colorAndSize['size_id'], + 'color_name' => $colorAndSize['color'], + 'quantity' => $colorAndSize['stock'], + ]); + } catch (\Exception $e) { + Log::error('Failed to create ProductColor:', [ + 'product_id' => $product->id, + 'size_id' => $colorAndSize['size_id'], + 'color_name' => $colorAndSize['color'], + 'quantity' => $colorAndSize['stock'], + 'error' => $e->getMessage(), + ]); + } + } else { + Log::warning('Incomplete colors_and_sizes entry:', $colorAndSize); + } } - // Handle images $images = $request->file('images'); if ($images) { foreach ($images as $image) { @@ -77,7 +90,6 @@ public function store(ProductRequest $request) } } - // Handle selected discount if ($request->filled('selectedDiscountId')) { $product->discount_id = $request->input('selectedDiscountId'); $product->save(); @@ -112,26 +124,23 @@ public function update(ProductRequest $request, Product $product) { $validated = $request->validated(); - // Update the product $product->update($validated); - // Update colors and sizes $colorsAndSizes = $request->input('colors_and_sizes', []); + // прво избриши ги Ñите рекорди, за да ги креираме наново ProductColor::where('product_id', $product->id)->delete(); foreach ($colorsAndSizes as $colorAndSize) { ProductColor::create([ 'product_id' => $product->id, - 'color_name' => $colorAndSize['color'], 'size_id' => $colorAndSize['size_id'], + 'color_name' => $colorAndSize['color'], 'quantity' => $colorAndSize['stock'], ]); } - // Handle images $images = $request->file('images'); - if ($images) { $product->images()->delete(); @@ -142,14 +151,18 @@ public function update(ProductRequest $request, Product $product) 'product_id' => $product->id, 'image' => $path, ]); - } else { - $error = $image->getError(); - Log::error("File upload error: $error"); - return redirect()->back()->with('error', 'Error uploading file: ' . $error); } } } + if ($request->filled('selectedDiscountId')) { + $product->discount_id = $request->input('selectedDiscountId'); + $product->save(); + } else { + $product->discount_id = null; + $product->save(); + } + return redirect()->route('products.index')->with('success', 'Продуктот е уÑпешно ажуриран!'); } } diff --git a/app/Http/Requests/ProductRequest.php b/app/Http/Requests/ProductRequest.php index d82d822..ee4b8e6 100644 --- a/app/Http/Requests/ProductRequest.php +++ b/app/Http/Requests/ProductRequest.php @@ -24,27 +24,21 @@ public function rules(): array $rules = [ 'name' => 'required|string|max:255', 'description' => 'required|string', - 'price' => 'required|numeric|min:0', - // 'sizes' => 'required|array|min:1', - // 'sizes.*' => 'required|string', - // 'colors' => 'required|array|min:1', - // 'colors.*' => 'required|string', - // 'stock_quantities' => 'required|array|min:1', - // 'stock_quantities.*' => 'required|numeric|min:1', + 'price' => 'required|numeric|min:1', 'size_advice' => 'required|string', 'maintenance' => 'required|string', - 'tags' => 'nullable|string|starts_with:#', - 'images.*' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:6048', + 'tags' => 'nullable|string', 'category_id' => 'required|exists:categories,id', 'brand_id' => 'required|exists:brands,id', 'discount_id' => 'nullable|exists:discounts,id', - // 'colors_and_sizes' => 'required|array', - // 'colors_and_sizes.*.color' => 'required|string', - // 'colors_and_sizes.*.size_id' => 'required|exists:sizes,id', - // 'colors_and_sizes.*.stock' => 'required|integer|min:0', + 'colors_and_sizes' => 'required|array', + 'colors_and_sizes.*.size_id' => 'required|exists:sizes,id', + 'colors_and_sizes.*.color' => 'required|string', + 'colors_and_sizes.*.stock' => 'required|integer|min:0', + 'images' => 'nullable|array', + 'images.*' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:6048', ]; - // Ensure unique name when creating a new product if ($this->isMethod('post')) { $rules['name'] .= '|unique:products'; } @@ -67,20 +61,6 @@ public function messages() 'price.numeric' => 'Цената мора да биде нумеричка вредноÑÑ‚.', 'price.min' => 'Цената мора да биде најмалку :min.', - // 'sizes.required' => 'Барем една големина мора да биде избрана.', - // 'sizes.*.required' => 'Изборот на големина е задолжителен.', - - // 'colors.required' => 'Барем една боја мора да биде избрана.', - // 'colors.array' => 'Изборот на бои мора да биде низа.', - // 'colors.min' => 'Изборот на бои мора да вклучува најмалку :min боја.', - // 'colors.*.required' => 'Изборот на боја е задолжителен.', - - // 'stock_quantities.required' => 'Количината на залиха е задолжителна.', - // 'stock_quantities.array' => 'Количината на залиха мора да биде низа.', - // 'stock_quantities.*.required' => 'Количината на залиха е задолжителна за Ñекој избран артикл.', - // 'stock_quantities.*.numeric' => 'Количината на залиха мора да биде нумеричка вредноÑÑ‚.', - // 'stock_quantities.*.min' => 'Количината на залиха мора да биде најмалку :min.', - 'size_advice.required' => 'Советот за големината е задолжителен.', 'maintenance.required' => 'УпатÑтвото за одржување е задолжително.', @@ -91,7 +71,7 @@ public function messages() 'category_id.required' => 'Категоријата е задолжителна.', 'category_id.exists' => 'Избраната категорија не поÑтои.', - + 'brand_id.required' => 'Брендот е задолжителен.', 'brand_id.exists' => 'Избраниот бренд не поÑтои.', diff --git a/database/seeders/ProductSeeder.php b/database/seeders/ProductSeeder.php index a21aa67..dfbcf9a 100644 --- a/database/seeders/ProductSeeder.php +++ b/database/seeders/ProductSeeder.php @@ -24,14 +24,14 @@ public function run() $brands = Brand::all()->pluck('id')->toArray(); $categories = Category::all()->pluck('id')->toArray(); - $sizes = Size::all()->pluck('id')->toArray(); // Fetch sizes + $sizes = Size::all()->pluck('id')->toArray(); $colors = ['black', 'white', 'yellow', 'blue', 'green', 'red', 'pink']; foreach (range(1, 20) as $index) { $product = new Product(); $product->name = $faker->sentence(2); $product->description = $faker->text; - // Ensure price ends with xx90 (1190, 1390, 2990, 3290, etc.) + // да завршува цената на xx90 (1190, 1390, 2990, 3290, etc.) $randomPrice = $faker->numberBetween(99, 399); $roundedPrice = ceil($randomPrice / 10) * 10; $product->price = ($roundedPrice * 10) - 10; @@ -44,17 +44,17 @@ public function run() $product->category_id = $faker->randomElement($categories); $product->save(); - // Colors, but the number of different colors shouldn't exceed stock_quantity + // боите $maxColors = min($product->stock_quantity, count($colors)); $randomColors = $faker->randomElements($colors, $faker->numberBetween(1, $maxColors)); foreach ($randomColors as $color) { - // Randomly select sizes for each product color + // рандом величини за Ñекоја од креираните бои $randomSizes = $faker->randomElements($sizes, $faker->numberBetween(1, 3)); foreach ($randomSizes as $sizeId) { ProductColor::create([ 'product_id' => $product->id, 'size_id' => $sizeId, - 'color_name' => $color, // Add color_name here + 'color_name' => $color, 'quantity' => $faker->numberBetween(1, 10), ]); } diff --git a/resources/views/products/create.blade.php b/resources/views/products/create.blade.php index 8853604..cdc321d 100644 --- a/resources/views/products/create.blade.php +++ b/resources/views/products/create.blade.php @@ -5,7 +5,7 @@ <div class="row"> <div class="col-md-8 offset-md-2"> - {{-- validation errors: --}} + {{-- валидациÑки ерори: --}} @if ($errors->any()) <div class="alert alert-danger"> <ul> @@ -63,7 +63,7 @@ <div class="mb-3"> <label for="dynamicInputs" class="form-label">Величина, Боја, Количина</label> <div id="dynamicInputs"> - <!-- for dynamic inputs --> + <!-- динамични инпути за величина/боја/парчиња на залиха --> </div> <button type="button" id="addInputBtn" class="btn btn-secondary mt-2">Додај</button> </div> @@ -197,21 +197,22 @@ document.addEventListener('DOMContentLoaded', function () { const addInputBtn = document.getElementById('addInputBtn'); const dynamicInputsContainer = document.getElementById('dynamicInputs'); + let inputIndex = 0; addInputBtn.addEventListener('click', function () { const newInputGroup = document.createElement('div'); newInputGroup.classList.add('mb-3', 'input-group'); newInputGroup.innerHTML = ` - <select class="form-select me-2" name="colors_and_sizes[][size_id]" required> + <select class="form-select me-2" name="colors_and_sizes[${inputIndex}][size_id]" required> <option value="" disabled selected>Величина</option> - <option value="xs">XS</option> - <option value="s">S</option> - <option value="m">M</option> - <option value="l">L</option> - <option value="xl">XL</option> + <option value="1">XS</option> + <option value="2">S</option> + <option value="3">M</option> + <option value="4">L</option> + <option value="5">XL</option> </select> - <select class="form-select me-2" name="colors_and_sizes[][color]" required> + <select class="form-select me-2" name="colors_and_sizes[${inputIndex}][color]" required> <option value="" disabled selected>Боја</option> <option value="black">Black</option> <option value="white">White</option> @@ -221,17 +222,19 @@ <option value="red">Red</option> <option value="pink">Pink</option> </select> - <input type="number" class="form-control me-2" name="colors_and_sizes[][stock]" min="0" placeholder="Количина" required> + <input type="number" class="form-control me-2" name="colors_and_sizes[${inputIndex}][stock]" min="0" placeholder="Количина" required> <button type="button" class="btn btn-danger remove-input-btn"><i class="fa fa-minus"></i></button> `; dynamicInputsContainer.appendChild(newInputGroup); - // Add event listener to the remove button newInputGroup.querySelector('.remove-input-btn').addEventListener('click', function () { newInputGroup.remove(); }); + + inputIndex++; }); }); </script> + diff --git a/resources/views/products/edit.blade.php b/resources/views/products/edit.blade.php index c4ab36c..91436b6 100644 --- a/resources/views/products/edit.blade.php +++ b/resources/views/products/edit.blade.php @@ -4,11 +4,22 @@ <div class="container px-2 py-5"> <div class="row"> <div class="col-md-8 offset-md-2"> + + {{-- валидациÑки ерори: --}} + @if ($errors->any()) + <div class="alert alert-danger"> + <ul> + @foreach ($errors->all() as $error) + <li>{{ $error }}</li> + @endforeach + </ul> + </div> + @endif + <form id="edit_product" method="POST" action="{{ route('products.update', $product->id) }}" enctype="multipart/form-data"> @csrf - @method('PUT') - + <div class="row mb-3"> <div class="col d-flex align-center"> <a href="{{ route('products.index') }}" class="text-secondary"><i class="fa-solid fa-left-long fa-2x"></i></a> @@ -16,7 +27,7 @@ </div> <div class="col"> <div class="mb-3 d-flex justify-content-end"> - <select class="form-select w-50 " id="status" name="status"> + <select class="form-select w-50" id="status" name="status"> <option disabled selected>СтатуÑ</option> <option value="active" {{ $product->status === 'active' ? 'selected' : '' }}>Ðктивен</option> <option value="archived" {{ $product->status === 'archived' ? 'selected' : '' }}>Ðрхивиран</option> @@ -27,7 +38,7 @@ <div class="mb-3"> <label for="name" class="form-label">Име на продукт</label> - <input type="text" class="form-control" id="name" name="name" value="{{ $product->name }}"> + <input type="text" class="form-control" id="name" name="name" value="{{ old('name', $product->name) }}"> @error('name') <span class="text-danger">{{ $message }}</span> @enderror @@ -35,7 +46,7 @@ <div class="mb-3"> <label for="description" class="form-label">ОпиÑ</label> - <textarea class="form-control" id="description" name="description" rows="3">{{ $product->description }}</textarea> + <textarea class="form-control" id="description" name="description" rows="3">{{ old('description', $product->description) }}</textarea> @error('description') <span class="text-danger">{{ $message }}</span> @enderror @@ -44,91 +55,55 @@ <div class="mb-3 row"> <label for="price" class="form-label col-md-3">Цена</label> <div class="col-md-9"> - <input type="number" class="form-control w-25" id="price" name="price" step="0.01" value="{{ $product->price }}"> + <input type="number" class="form-control w-25" id="price" name="price" step="0.01" value="{{ old('price', $product->price) }}"> @error('price') <span class="text-danger">{{ $message }}</span> @enderror </div> </div> - <div class="mb-3 row align-items-center"> - <div class="col-auto"> - <label for="stock_quantity" class="form-label">Количина</label> - </div> - <div class="col-auto"> - <div class="input-group"> - <button class="btn border border-secondary rounded-circle" type="button" id="minus-btn">-</button> - <input type="number" class="form-control text-center stock-quantity" id="stock_quantity" name="stock_quantity" value="{{ $product->stock_quantity }}" min="0" oninput="this.value = Math.abs(this.value)"> - <button class="btn border border-secondary rounded-circle" type="button" id="plus-btn">+</button> - @error('stock_quantity') - <span class="text-danger">{{ $message }}</span> - @enderror - </div> - </div> - </div> - <div class="mb-3"> - <label for="size" class="form-label">Величина</label><br> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_xs" name="sizes[]" value="xs" {{ in_array('xs', json_decode($product->size)) ? 'checked' : '' }}> - <label class="form-check-label" for="size_xs">XS</label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_s" name="sizes[]" value="s" {{ in_array('s', json_decode($product->size)) ? 'checked' : '' }}> - <label class="form-check-label" for="size_s">S</label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_m" name="sizes[]" value="m" {{ in_array('m', json_decode($product->size)) ? 'checked' : '' }}> - <label class="form-check-label" for="size_m">M</label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_l" name="sizes[]" value="l" {{ in_array('l', json_decode($product->size)) ? 'checked' : '' }}> - <label class="form-check-label" for="size_l">L</label> - </div> - <div class="form-check form-check-inline"> - <input class="form-check-input size-checkbox-input" type="checkbox" id="size_xl" name="sizes[]" value="xl" {{ in_array('xl', json_decode($product->size)) ? 'checked' : '' }}> - <label class="form-check-label" for="size_xl">XL</label> + <label for="dynamicInputs" class="form-label">Величина, Боја, Количина</label> + <div id="dynamicInputs"> + @foreach ($product->productColors as $index => $productColor) + <div class="mb-3 input-group"> + <select class="form-select me-2" name="colors_and_sizes[{{ $index }}][size_id]" required> + <option value="" disabled selected>Величина</option> + <option value="1" {{ $productColor->size_id == 1 ? 'selected' : '' }}>XS</option> + <option value="2" {{ $productColor->size_id == 2 ? 'selected' : '' }}>S</option> + <option value="3" {{ $productColor->size_id == 3 ? 'selected' : '' }}>M</option> + <option value="4" {{ $productColor->size_id == 4 ? 'selected' : '' }}>L</option> + <option value="5" {{ $productColor->size_id == 5 ? 'selected' : '' }}>XL</option> + </select> + <select class="form-select me-2" name="colors_and_sizes[{{ $index }}][color]" required> + <option value="" disabled selected>Боја</option> + <option value="black" {{ $productColor->color_name == 'black' ? 'selected' : '' }}>Black</option> + <option value="white" {{ $productColor->color_name == 'white' ? 'selected' : '' }}>White</option> + <option value="yellow" {{ $productColor->color_name == 'yellow' ? 'selected' : '' }}>Yellow</option> + <option value="blue" {{ $productColor->color_name == 'blue' ? 'selected' : '' }}>Blue</option> + <option value="green" {{ $productColor->color_name == 'green' ? 'selected' : '' }}>Green</option> + <option value="red" {{ $productColor->color_name == 'red' ? 'selected' : '' }}>Red</option> + <option value="pink" {{ $productColor->color_name == 'pink' ? 'selected' : '' }}>Pink</option> + </select> + <input type="number" class="form-control me-2" name="colors_and_sizes[{{ $index }}][stock]" min="0" value="{{ $productColor->quantity }}" placeholder="Количина" required> + <button type="button" class="btn btn-danger remove-input-btn"><i class="fa fa-minus"></i></button> + </div> + @endforeach </div> - @error('sizes') - <span class="text-danger">{{ $message }}</span> - @enderror + <button type="button" id="addInputBtn" class="btn btn-secondary mt-2">Додај</button> </div> - <span class="text-danger stock_quantity_exceeded_for_sizes"></span> <div class="mb-3"> <label for="size_advice" class="form-label">Совет за величина</label> - <textarea class="form-control" id="size_advice" name="size_advice" rows="3">{{ $product->size_advice }}</textarea> + <textarea class="form-control" id="size_advice" name="size_advice" rows="3">{{ old('size_advice', $product->size_advice) }}</textarea> @error('size_advice') <span class="text-danger">{{ $message }}</span> @enderror </div> - <div class="mb-3"> - @php - $colors = ['black', 'white', 'yellow', 'blue', 'green', 'red', 'pink']; - @endphp - <label class="form-label">Боја</label><br> - <div class="form-check form-check-inline"> - @foreach($colors as $color) - @php - $isChecked = !is_null($product->colors) && in_array($color, explode(',', $product->colors)); - @endphp - <input class="form-check-input color-checkbox-input" type="checkbox" id="color_{{ $color }}" name="colors[]" value="{{ $color }}" {{ $isChecked ? 'checked' : '' }}> - <label class="{{ $color }} color-checkbox" for="color_{{ $color }}"></label> - @endforeach - <div id="color-message" class="text-danger"></div> - </div> - </div> - <span class="text-danger stock_quantity_exceeded_for_colors"></span> - - - @error('colors') - <span class="text-danger">{{ $message }}</span> - @enderror - <div class="mb-3"> <label for="maintenance" class="form-label">ÐаÑоки за одржување</label> - <textarea class="form-control" id="maintenance" name="maintenance" rows="3">{{ $product->maintenance }}</textarea> + <textarea class="form-control" id="maintenance" name="maintenance" rows="3">{{ old('maintenance', $product->maintenance) }}</textarea> @error('maintenance') <span class="text-danger">{{ $message }}</span> @enderror @@ -136,7 +111,7 @@ <div class="mb-3"> <label for="tags" class="form-label">Ознаки</label> - <input type="text" class="form-control" id="tags" name="tags" value="{{ $product->tags }}"> + <input type="text" class="form-control" id="tags" name="tags" value="{{ old('tags', $product->tags) }}"> @error('tags') <span class="text-danger">{{ $message }}</span> @enderror @@ -145,30 +120,24 @@ <div class="mb-3"> <label for="images" class="form-label">Слики (МакÑимум 4)</label> <div class="image-grid"> - @foreach ($product->images as $image) + @for ($i = 0; $i < 4; $i++) <div class="image-upload-box"> <input type="file" class="form-control" accept="image/*" name="images[]"> <div class="image-container"> - <img src="{{ asset('storage/' . $image->image) }}" class="uploaded-image"> - </div> - </div> - @endforeach - @for ($i = count($product->images); $i < 4; $i++) - <div class="image-upload-box"> - <input type="file" class="form-control" accept="image/*" name="images[]"> - <div class="image-container"> - <img class="uploaded-image" src=""> + @if(Session::has('uploaded_image')) + <img src="{{ asset(Session::get('uploaded_image')) }}" class="uploaded-image"> + @else + <img src="{{ $oldImages[$i] ?? '' }}" class="uploaded-image"> + @endif </div> <i class="fas fa-plus"></i> </div> + @error('images.' . $i) + <span class="text-danger">{{ $message }}</span> + @enderror @endfor </div> </div> - </div> - @error('images.*') - <span class="text-danger">{{ $message }}</span> - @enderror - </div> <div class="row mb-3"> <div class="col-md-6"> @@ -176,9 +145,7 @@ <label for="category" class="form-label">Категорија</label> <select class="form-select" id="category" name="category_id"> @foreach($categories as $category) - <option value="{{ $category->id }}" {{ $category->id == $product->category_id ? 'selected' : '' }}> - {{ $category->name }} - </option> + <option value="{{ $category->id }}" {{ $category->id == $product->category_id ? 'selected' : '' }}>{{ $category->name }}</option> @endforeach </select> @error('category_id') @@ -186,14 +153,13 @@ @enderror </div> </div> + <div class="col-md-6"> <div class="mb-3"> <label for="brand" class="form-label">Бренд</label> <select class="form-select" id="brand" name="brand_id"> @foreach($brands as $brand) - <option value="{{ $brand->id }}" {{ $brand->id == $product->brand_id ? 'selected' : '' }}> - {{ $brand->name }} - </option> + <option value="{{ $brand->id }}" {{ $brand->id == $product->brand_id ? 'selected' : '' }}>{{ $brand->name }}</option> @endforeach </select> @error('brand_id') @@ -201,15 +167,9 @@ @enderror </div> </div> - </div> </div> <div class="mb-3"> - @if(isset($product->discount)) - <p id="alreadyAppliedDiscount" class="font-weight-bold">ПопуÑÑ‚ аплициран на овој продукт: "{{ $product->discount->name }}"</p> - @else - <p id="alreadyAppliedDiscount" class="font-weight-bold">Сеуште нема аплицирано попуÑÑ‚ на овој продукт.</p> - @endif <label for="discount" class="form-label">Додај ПопуÑÑ‚</label> <button type="button" class="m-2 btn btn-secondary background-dark-green" data-bs-toggle="modal" data-bs-target="#discountModal"> <i class="fa-solid fa-plus"></i> @@ -229,9 +189,9 @@ <div class="modal-body"> <ul id="discountList" class="list-group"> @forelse($discounts as $discount) - <li class="list-group-item"> + <li class="list-group-item d-flex align-items-center"> <input type="radio" name="discount_id" id="discount_{{ $discount->id }}" value="{{ $discount->id }}"> - <label for="discount_{{ $discount->id }}">{{ $discount->name }}</label> + <label for="discount_{{ $discount->id }}"> {{$discount->name }}</label> </li> @empty <li class="list-group-item">Ðема активни попуÑти</li> @@ -246,14 +206,65 @@ </div> </div> - <p id="newAppliedDiscount" class="font-weight-bold"></p> + <p id="newAppliedDiscount" class="text-success font-weight-bold"></p> <div class="row mb-3 d-flex justify-center"> - <button type="submit" class="btn btn-dark w-50">Зачувај промени</button> - <a href="{{ route('products.index') }}" class="cancel w-25">Откажи</a> + <button type="submit" class="btn btn-dark w-50">Зачувај</button> + <button id="cancel" type="button" class="cancel btn w-25">Откажи</button> </div> </form> </div> </div> </div> -@endsection \ No newline at end of file + + <script> + document.addEventListener('DOMContentLoaded', function () { + const addInputBtn = document.getElementById('addInputBtn'); + const dynamicInputsContainer = document.getElementById('dynamicInputs'); + let inputIndex = {{ count($product->productColors) }}; + + addInputBtn.addEventListener('click', function () { + const newInputGroup = document.createElement('div'); + newInputGroup.classList.add('mb-3', 'input-group'); + + newInputGroup.innerHTML = ` + <select class="form-select me-2" name="colors_and_sizes[${inputIndex}][size_id]" required> + <option value="" disabled selected>Величина</option> + <option value="1">XS</option> + <option value="2">S</option> + <option value="3">M</option> + <option value="4">L</option> + <option value="5">XL</option> + </select> + <select class="form-select me-2" name="colors_and_sizes[${inputIndex}][color]" required> + <option value="" disabled selected>Боја</option> + <option value="black">Black</option> + <option value="white">White</option> + <option value="yellow">Yellow</option> + <option value="blue">Blue</option> + <option value="green">Green</option> + <option value="red">Red</option> + <option value="pink">Pink</option> + </select> + <input type="number" class="form-control me-2" name="colors_and_sizes[${inputIndex}][stock]" min="0" placeholder="Количина" required> + <button type="button" class="btn btn-danger remove-input-btn"><i class="fa fa-minus"></i></button> + `; + + dynamicInputsContainer.appendChild(newInputGroup); + + newInputGroup.querySelector('.remove-input-btn').addEventListener('click', function () { + newInputGroup.remove(); + }); + + inputIndex++; + }); + + dynamicInputsContainer.addEventListener('click', function (event) { + if (event.target && event.target.classList.contains('remove-input-btn')) { + event.target.closest('.input-group').remove(); + } + }); + }); + </script> + +@endsection diff --git a/resources/views/products/index.blade.php b/resources/views/products/index.blade.php index 2fce629..315bd39 100644 --- a/resources/views/products/index.blade.php +++ b/resources/views/products/index.blade.php @@ -56,20 +56,20 @@ <div class="card-body"> <span class="text-left h2 product-name">{{ $product->name }}</span> <p class="card-text mt-3 h5">Боја: - @foreach($product->productColors as $color) - <span class="border" style="background-color: {{ $color->color_name }}; margin-right: 5px; padding:8px; display:inline-block;"></span> + @foreach($product->productColors->unique('color_name') as $productColor) + <span class="border" style="background-color: {{ $productColor->color_name }}; margin-right: 5px; padding: 8px; display: inline-block;"></span> @endforeach </p> <div class="row"> <div class="col"> <p class="card-text h5 mt-2"> Величина: - @foreach($product->productColors as $productColor) + @foreach($product->productColors->unique('size.name') as $productColor) @if(!$loop->first) <span class="ml-1">,</span> @endif <span class="badge badge-secondary">{{ strtoupper($productColor->size->name) }}</span> - @endforeach + @endforeach </p> </div> <div class="col text-right"> -- GitLab From e0febad9a1f816b53afd6774eaea00f1c67b303a Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Tue, 9 Jul 2024 02:14:01 +0200 Subject: [PATCH 03/16] sidebar in remaking, not finished yet --- public/css/app.css | 247 ++++++++++++++++++++++- public/js/script.js | 69 +++---- resources/views/layouts/app.blade.php | 6 +- resources/views/layouts/navbar.blade.php | 119 ++++++++++- 4 files changed, 388 insertions(+), 53 deletions(-) diff --git a/public/css/app.css b/public/css/app.css index 8d708ca..df697b7 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -1,4 +1,233 @@ +* { + font-family: sans-serif; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --vintage-pink: rgb(240, 178, 189); + --vintage-pink-light: rgb(247, 235, 237); + --text-color: rgb(65, 63, 63); + --text-color-light: rgb(109, 106, 106); + + --tran-02: all 0.2s ease; + --tran-03: all 0.3s ease; + --tran-04: all 0.4s ease; + --tran-05: all 0.5s ease; +} + body { + height: 100vh; + background: var(--vintage-pink-light); + transition: var(--tran-04); +} + +body.dark{ + --vintage-pink: rgb(80, 46, 55); + --vintage-pink-light: rgb(114, 80, 86); + --text-color: rgb(235, 226, 226); + --text-color-light: rgb(190, 183, 183); +} + +ul { + padding-left: 0 !important; + /* margin: 0; */ +} + +/* Sidebar */ +.sidebar { + position: fixed; + z-index: 999; + top: 0; + left: 0; + height: 100%; + width: 250px; + padding: 10px 14px; + background: var(--vintage-pink); + transition: var(--tran-05); +} + +.sidebar.close{ + width: 88px!important; +} + +/* reusable CSS */ +.sidebar .text { + font-size: 16px; + font-weight: 500; + color: var(--text-color); +} + +.sidebar.close .text{ + opacity: 0; +} + +.sidebar .image { + min-width: 60px; + display: flex; + align-items: center; +} + +.sidebar li { + height: 50px; + margin-top: 10px; + list-style: none; + display: flex; + align-items: center; +} + +.sidebar li .icon { + display: flex; + align-items: center; + justify-content: center; + min-width: 60px; + font-size: 20px; +} + +.sidebar li .icon, +.sidebar li .text { + color: var(--text-color); + transition: var(--tran-02); +} + +.sidebar header { + position: relative; +} + +.sidebar .image-text img { + width: 60%; + margin: auto; +} + +.sidebar header .image-text { + display: flex; + align-items: center; +} +header .image-text .header-text { + display: flex; + flex-direction: column; +} + +.header-text .name { + font-weight: 600; +} + +.header-text .profession { + margin-top: -2px; +} + +.sidebar header .toggle { + position: absolute; + top: 50%; + right: -25px; + transform: translateY(-50%); + height: 25px; + width: 25px; + background: var(--vintage-pink-light); + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: var(--text-color); + cursor: pointer; +} + +.sidebar .search-box { + background: var(--vintage-pink-light); +} + +.search-box input { + height: 100%; + width: 100%; + outline: none; + border: none; + border-radius: 6px; + background: var(--vintage-pink-light); +} + +.sidebar li a { + height: 100%; + width: 100%; + display: flex; + align-items: center; + text-decoration: none; + border-radius: 6px; + transition: var(--tran-04); +} + +.sidebar li a:hover { + background: var(--vintage-pink-light); +} +.sidebar li a:hover .icon, +.sidebar li a:hover .text { + color: var(--text-color-light); +} + +.sidebar .menu-bar { + /* background: red; */ + height: 80%; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.menu-bar .mode { + background: var(--vintage-pink-light); +} + +.menu-bar .mode .moon-sun { + height: 50px; + width: 60px; + display: flex; + align-items: center; +} + +.menu-bar .mode { + position: absolute; +} + +.menu-bar .mode i.sun{ + opacity: 0; +} + +.menu-bar .mode .toggle-switch{ + display: flex; + align-items: center; + justify-content: center; + height: 100%; + min-width: 80px; + border-radius: 25px; + border: none; + cursor: pointer; +} + +.toggle-switch .switch{ + position: relative; + height: 22px; + width: 44px; + border-radius: 25px; + background: rgb(61, 167, 152); +} + +.switch::before{ + content: ''; + position: absolute; + height: 15px; + width: 15px; + border-radius: 50%; + top: 50%; + left: 5px; + transform: translateY(-50%); + background: var(--vintage-pink); + transition: var(--tran-03); +} + +body.dark .switch::before{ + left: 24px; +} + +/* body { overflow-x: hidden; } @@ -71,7 +300,7 @@ li { .sidebar-expanded-text-container, .user-info { margin-right: 10px; -} +} */ #content { transition: margin-left 0.5s ease; padding: 15px; @@ -103,23 +332,27 @@ #add-product { text-align: right; } -#sidebar.collapsed #closeSidebarBtn { +/* #sidebar.collapsed #closeSidebarBtn { display: none; -} +} */ -.dark-green{ +.dark-green { color: rgb(124, 146, 0); } .background-dark-green { - background-image: linear-gradient(to right, rgb(124, 146, 0), rgb(173, 192, 91)) !important; + background-image: linear-gradient( + to right, + rgb(124, 146, 0), + rgb(173, 192, 91) + ) !important; border: none !important; } -#sidebar.expanded #closeSidebarBtn { +/* #sidebar.expanded #closeSidebarBtn { display: inline-block; top: 0; -} +} */ .show-password-button { position: absolute; diff --git a/public/js/script.js b/public/js/script.js index 4c14f03..45a9fe7 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -22,50 +22,35 @@ document.addEventListener("DOMContentLoaded", function () { const listViewBtn = document.getElementById("listViewBtn"); const listView = document.getElementById("listView"); - tableViewBtn.addEventListener("click", function () { - tableViewBtn.style.backgroundColor = "pink"; - listViewBtn.style.backgroundColor = ""; - tableView.classList.remove("d-none"); - listView.classList.add("d-none"); - }); - - listViewBtn.addEventListener("click", function () { - listViewBtn.style.backgroundColor = "pink"; - tableViewBtn.style.backgroundColor = ""; - listView.classList.remove("d-none"); - tableView.classList.add("d-none"); - }); -}); - -document.getElementById("sidebar").addEventListener("mouseenter", function () { - this.classList.remove("collapsed"); - this.classList.add("expanded"); -}); - -document.getElementById("sidebar").addEventListener("mouseleave", function () { - this.classList.remove("expanded"); - this.classList.add("collapsed"); -}); - -document.getElementById("closeSidebarBtn").addEventListener("click", function () { - document.getElementById("sidebar").classList.remove("expanded"); - document.getElementById("sidebar").classList.add("collapsed"); - }); - -var menuItems = document.querySelectorAll("#menu li"); -menuItems.forEach(function (menuItem) { - menuItem.addEventListener("click", function () { - menuItems.forEach(function (item) { - item.classList.remove("active"); + if (tableViewBtn) { + tableViewBtn.addEventListener("click", function () { + tableViewBtn.style.backgroundColor = "pink"; + listViewBtn.style.backgroundColor = ""; + tableView.classList.remove("d-none"); + listView.classList.add("d-none"); }); + } - this.classList.add("active"); + if (listViewBtn) { + listViewBtn.addEventListener("click", function () { + listViewBtn.style.backgroundColor = "pink"; + tableViewBtn.style.backgroundColor = ""; + listView.classList.remove("d-none"); + tableView.classList.add("d-none"); + }); + } + const body = document.querySelector("body"); + const sidebar = document.querySelector(".sidebar"); + const toggle = document.querySelector(".toggle"); + // searchBtn = document.querySelector(".search-box"), + const modeSwitch = document.querySelector(".toggle-switch"); + const modeText = document.querySelector(".mode-text"); + + toggle.addEventListener("click", () => { + sidebar.classList.toggle("close"); + }); - document.getElementById("sidebar").classList.remove("expanded"); - document.getElementById("sidebar").classList.add("collapsed"); + modeSwitch.addEventListener("click", () => { + body.classList.toggle("dark"); }); - if (menuItem.querySelector("a").getAttribute("href").includes(window.location.pathname) - ) { - menuItem.classList.add("active"); - } }); diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index a2d710c..da6cfc6 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -15,6 +15,8 @@ <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.18.0/font/bootstrap-icons.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous"> + {{-- Boxicons CSS --}} + <link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'> <link rel="stylesheet" href="{{ asset('css/app.css') }}?v=1"> @@ -28,14 +30,14 @@ <div class="col-3 px-0 bg-dar collapsed" id="sidebar"> @include('layouts.navbar') </div> - <div class="col-9 offset-3 offset-l-0" id="content"> + <div class="col-9 offset-3" id="content"> @yield('content') </div> </div> </div> <script src="{{ asset('js/script.js') }}"></script> - <script src="{{ asset('js/profile.js') }}"></script> + {{-- <script src="{{ asset('js/profile.js') }}"></script> --}} <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> diff --git a/resources/views/layouts/navbar.blade.php b/resources/views/layouts/navbar.blade.php index 1ad73cd..e161a8b 100644 --- a/resources/views/layouts/navbar.blade.php +++ b/resources/views/layouts/navbar.blade.php @@ -1,5 +1,120 @@ +<nav class="sidebar close"> + <header> + <div class="image-text"> + <span class="image m-3"> + <img src="{{ asset('images/logo.png') }}" alt="Logo"> + </span> + </div> + + <i class='bx bx-chevron-right toggle'></i> + </header> + + + <div class="menu-bar"> + <div class="menu"> + <li class="search-box"> + <i class='bx bx-search icon' ></i> + <input type="search" placeholder="Search..."> + </li> + <ul class="menu-links"> + <li class="nav-link"> + <a href="{{ route('products.index') }}"> + <i class='bx bx-home-alt icon' ></i> + <span class="text nav-text">Vintage Облека</span> + </a> + </li> + <li class="nav-link"> + <a href="{{ route('discounts.index') }}"> + <i class='bx bx-home-alt icon' ></i> + <span class="text nav-text">ПопуÑти</span> + </a> + </li> + <li class="nav-link"> + <a href="{{ route('brands.index') }}"> + <i class='bx bx-home-alt icon' ></i> + <span class="text nav-text">Брендови</span> + </a> + </li> + <li class="nav-link"> + <a href="{{ route('users.edit') }}"> + <i class='bx bx-home-alt icon' ></i> + <span class="text nav-text">Профил</span> + </a> + </li> + <li class="nav-link"> + <a href=""> + <i class='bx bx-home-alt icon' ></i> + <span class="text nav-text">Dashboard</span> + </a> + </li> + <li class="nav-link"> + <a href=""> + <i class='bx bx-home-alt icon' ></i> + <span class="text nav-text">Dashboard</span> + </a> + </li> + </ul> + </div> + + <div class="bottom-content"> + <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;"> + @csrf + </form> + <li class=""> + <a href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> + <i class='bx bx-log-out icon' ></i> + <span class="text nav-text">Loguot</span> + </a> + </li> + + <li class="mode"> + <div class="moon-sun"> + <i class='bx bx-moon icon moon' ></i> + <i class='bx bx-sun icon sun' ></i> + </div> + <span class="mode-text text">Dark Mode</span> + + <div class="toggle-switch"> + <span class="switch"></span> + </div> + </li> + </div> + </div> +</nav> + + + + + + + + + - <div class="row flex-nowrap "> + + + + + + + + + + + + + + + + + + + + + + + + {{-- <div class="row flex-nowrap "> <div class="col-12 col-md-3 col-xl-2 bg-dar collapsed" id="sidebar"> <div class="d-flex flex-column align-items-sm-start px-3 pt-2 text-white min-vh-100"> <ul class="nav nav-pills flex-column mb-sm-auto mb-0 align-items-center align-items-sm-start" id="menu"> @@ -66,5 +181,5 @@ </div> </div> </div> - </div> + </div> --}} -- GitLab From 1b8d28e7cab6d27594a2b42495406dc7ed0ec899 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Tue, 9 Jul 2024 23:27:23 +0200 Subject: [PATCH 04/16] sidebar done --- public/css/app.css | 93 ++++++++++++++++-------- public/js/script.js | 30 +++++++- resources/views/layouts/app.blade.php | 19 ++--- resources/views/layouts/navbar.blade.php | 9 +-- resources/views/products/index.blade.php | 4 +- 5 files changed, 102 insertions(+), 53 deletions(-) diff --git a/public/css/app.css b/public/css/app.css index df697b7..1e8a5d6 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -23,13 +23,17 @@ body { transition: var(--tran-04); } -body.dark{ +body.dark { --vintage-pink: rgb(80, 46, 55); --vintage-pink-light: rgb(114, 80, 86); --text-color: rgb(235, 226, 226); --text-color-light: rgb(190, 183, 183); } +.background-darker { + background-color: rgb(148, 104, 111); +} + ul { padding-left: 0 !important; /* margin: 0; */ @@ -45,11 +49,11 @@ .sidebar { width: 250px; padding: 10px 14px; background: var(--vintage-pink); - transition: var(--tran-05); + transition: var(--tran-03); } -.sidebar.close{ - width: 88px!important; +.sidebar.close { + width: 88px !important; } /* reusable CSS */ @@ -57,10 +61,13 @@ .sidebar .text { font-size: 16px; font-weight: 500; color: var(--text-color); + transition: var(--tran-04); + white-space: nowrap; + opacity: 1; } -.sidebar.close .text{ - opacity: 0; +.sidebar.close .text { + opacity: 0; } .sidebar .image { @@ -93,13 +100,19 @@ .sidebar li .text { .sidebar header { position: relative; + margin-bottom: 2em; } .sidebar .image-text img { - width: 60%; + position: relative; + left: -15px; + width: 80%; margin: auto; } +.sidebar.open .image-text img { + left: 0; +} .sidebar header .image-text { display: flex; align-items: center; @@ -121,7 +134,7 @@ .sidebar header .toggle { position: absolute; top: 50%; right: -25px; - transform: translateY(-50%); + transform: translateY(-50%) rotate(180deg); height: 25px; width: 25px; background: var(--vintage-pink-light); @@ -131,19 +144,11 @@ .sidebar header .toggle { border-radius: 50%; color: var(--text-color); cursor: pointer; + transition: var(--tran-03); } -.sidebar .search-box { - background: var(--vintage-pink-light); -} - -.search-box input { - height: 100%; - width: 100%; - outline: none; - border: none; - border-radius: 6px; - background: var(--vintage-pink-light); +.sidebar.close header .toggle { + transform: translateY(-50%); } .sidebar li a { @@ -165,7 +170,6 @@ .sidebar li a:hover .text { } .sidebar .menu-bar { - /* background: red; */ height: 80%; display: flex; flex-direction: column; @@ -173,6 +177,8 @@ .sidebar .menu-bar { } .menu-bar .mode { + position: relative; + border-radius: 6px; background: var(--vintage-pink-light); } @@ -183,15 +189,25 @@ .menu-bar .mode .moon-sun { align-items: center; } -.menu-bar .mode { +.menu-bar .mode i { position: absolute; + transition: var(--tran-03); +} + +.menu-bar .mode i.sun { + opacity: 0; } -.menu-bar .mode i.sun{ +body.dark .menu-bar .mode i.sun { + opacity: 1; +} +body.dark .menu-bar .mode i.moon-sun { opacity: 0; } -.menu-bar .mode .toggle-switch{ +.menu-bar .mode .toggle-switch { + position: absolute; + right: -10px; display: flex; align-items: center; justify-content: center; @@ -200,9 +216,10 @@ .menu-bar .mode .toggle-switch{ border-radius: 25px; border: none; cursor: pointer; + border-radius: 6px; } -.toggle-switch .switch{ +.toggle-switch .switch { position: relative; height: 22px; width: 44px; @@ -210,8 +227,8 @@ .toggle-switch .switch{ background: rgb(61, 167, 152); } -.switch::before{ - content: ''; +.switch::before { + content: ""; position: absolute; height: 15px; width: 15px; @@ -219,14 +236,28 @@ .switch::before{ top: 50%; left: 5px; transform: translateY(-50%); - background: var(--vintage-pink); - transition: var(--tran-03); + background: var(--vintage-pink); + transition: var(--tran-03); } -body.dark .switch::before{ +body.dark .switch::before { left: 24px; } +/* .home { + position: relative; + left: 250px; + height: 100vh; + width: calc(100%-250px); + background: var(--vintage-pink-light); +} + +.sidebar.close ~ .home { + left: 88px; + width: calc(100%-88px); +} */ + + /* body { overflow-x: hidden; } @@ -301,10 +332,10 @@ .sidebar-expanded-text-container, .user-info { margin-right: 10px; } */ -#content { +/* #content { transition: margin-left 0.5s ease; padding: 15px; -} +} */ #searchInput { border: 1px solid rgb(160, 154, 154); diff --git a/public/js/script.js b/public/js/script.js index 45a9fe7..5993220 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -39,18 +39,44 @@ document.addEventListener("DOMContentLoaded", function () { tableView.classList.add("d-none"); }); } + const body = document.querySelector("body"); const sidebar = document.querySelector(".sidebar"); const toggle = document.querySelector(".toggle"); - // searchBtn = document.querySelector(".search-box"), + const searchBtn = document.querySelector(".search-box"); const modeSwitch = document.querySelector(".toggle-switch"); const modeText = document.querySelector(".mode-text"); + const image = document.querySelector(".image"); + + // to set dark mode + function setDarkMode(isDark) { + if (isDark) { + body.classList.add("dark"); + body.classList.add("background-darker"); + } else { + body.classList.remove("dark"); + body.classList.remove("background-darker"); + } + } + + // Check for stored dark mode in local storage (doesnt work quite well...) + const darkMode = localStorage.getItem("darkMode") === "true"; + setDarkMode(darkMode); toggle.addEventListener("click", () => { sidebar.classList.toggle("close"); + sidebar.classList.toggle("open"); }); modeSwitch.addEventListener("click", () => { - body.classList.toggle("dark"); + const isDark = !body.classList.contains("dark"); + setDarkMode(isDark); + localStorage.setItem("darkMode", isDark); + + if (body.classList.contains("dark")) { + modeText.innerText = "Light Mode"; + } else { + modeText.innerText = "Dark Mode"; + } }); }); diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index da6cfc6..db5528e 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -25,23 +25,18 @@ @vite(['resources/sass/app.scss', 'resources/js/app.js']) </head> <body> - <div class="container-fluid"> - <div class="row"> - <div class="col-3 px-0 bg-dar collapsed" id="sidebar"> - @include('layouts.navbar') - </div> - <div class="col-9 offset-3" id="content"> - @yield('content') - </div> - </div> + <div> + @include('layouts.navbar') + </div> + + <div class="home" id="content"> + @yield('content') </div> <script src="{{ asset('js/script.js') }}"></script> - {{-- <script src="{{ asset('js/profile.js') }}"></script> --}} + <script src="{{ asset('js/profile.js') }}"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> - - </body> </html> diff --git a/resources/views/layouts/navbar.blade.php b/resources/views/layouts/navbar.blade.php index e161a8b..a5b92cd 100644 --- a/resources/views/layouts/navbar.blade.php +++ b/resources/views/layouts/navbar.blade.php @@ -8,14 +8,8 @@ <i class='bx bx-chevron-right toggle'></i> </header> - - <div class="menu-bar"> <div class="menu"> - <li class="search-box"> - <i class='bx bx-search icon' ></i> - <input type="search" placeholder="Search..."> - </li> <ul class="menu-links"> <li class="nav-link"> <a href="{{ route('products.index') }}"> @@ -82,6 +76,9 @@ </div> </nav> +{{-- <section class="home"> + <div class="text">Dashboard</div> +</section> --}} diff --git a/resources/views/products/index.blade.php b/resources/views/products/index.blade.php index 315bd39..014b74e 100644 --- a/resources/views/products/index.blade.php +++ b/resources/views/products/index.blade.php @@ -3,7 +3,7 @@ @section('content') <div class="container container-fluid"> <div class="row"> - <div class="col-12 text-right mt-5"> + <div class="col-8 offset-3 text-right mt-5"> @if(session('success')) <div class="alert alert-success text-center" role="alert"> {{ session('success') }} @@ -22,7 +22,7 @@ </div> <div class="row" id="tableView"> @foreach($products as $product) - <div class="col-12 col-md-6 col-xl-4 mb-4"> + <div class="col-9 offset-3 mb-4"> <div class="card px-1 py-4 product"> @if($product->stock_quantity === 1) <div class="badge badge-warning" style="position: absolute; top: 0; left: 0;">*Ñамо 1 парче</div> -- GitLab From 495c99cf83121f1d61c4e96add71b50d9eff1641 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Wed, 10 Jul 2024 00:00:54 +0200 Subject: [PATCH 05/16] to be continued --- public/css/app.css | 89 ------------------------ resources/views/layouts/app.blade.php | 2 +- resources/views/products/index.blade.php | 10 +-- 3 files changed, 6 insertions(+), 95 deletions(-) diff --git a/public/css/app.css b/public/css/app.css index 1e8a5d6..9fc3164 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -257,86 +257,6 @@ .sidebar.close ~ .home { width: calc(100%-88px); } */ - -/* body { - overflow-x: hidden; -} - -#sidebar { - position: fixed; - padding: 30px, 0px; - top: 0; - width: 300px; - height: 100%; - background-color: white; - transition: left 0.5s ease; - z-index: 1000; -} - -#sidebar.collapsed { - width: 120px; - transition: width 0.25s ease; -} - -#sidebar.expanded { - width: 500px; - transition: width 0.25s ease; -} - -#sidebar.expanded:hover { - width: 100%; -} - -#sidebar.collapsed .sidebar-expanded-text { - display: none; - opacity: 1; - text-align: left !important; -} - -#sidebar.expanded:hover .sidebar-expanded-text { - display: inline; - margin-left: 10px; - white-space: nowrap; - opacity: 1; - transition: margin-left 1.5s ease, white-space 1.5s ease, opacity 0.5s ease; -} - -#sidebar.expanded .sidebar-expanded-text-container { - display: flex; - width: 100% !important; - justify-content: center; -} - -#menu li:hover { - background-color: pink; - cursor: pointer; - transition: background-color 0.5s ease; -} - -#menu li.active { - background-color: pink; - transition: background-color 0.5s ease; - color: black !important; -} - -li { - padding: 0.75em 1em; - border-radius: 10px; - transition: background-color 0.5s ease; - height: 70px; - display: block; - width: 100%; -} - -.sidebar-expanded-text-container, -.user-info { - margin-right: 10px; -} */ -/* #content { - transition: margin-left 0.5s ease; - padding: 15px; -} */ - #searchInput { border: 1px solid rgb(160, 154, 154); border-radius: 5px; @@ -363,10 +283,6 @@ #add-product { text-align: right; } -/* #sidebar.collapsed #closeSidebarBtn { - display: none; -} */ - .dark-green { color: rgb(124, 146, 0); } @@ -380,11 +296,6 @@ .background-dark-green { border: none !important; } -/* #sidebar.expanded #closeSidebarBtn { - display: inline-block; - top: 0; -} */ - .show-password-button { position: absolute; top: 50%; diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index db5528e..e8d8cb2 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -34,7 +34,7 @@ </div> <script src="{{ asset('js/script.js') }}"></script> - <script src="{{ asset('js/profile.js') }}"></script> + {{-- <script src="{{ asset('js/profile.js') }}"></script> --}} <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> diff --git a/resources/views/products/index.blade.php b/resources/views/products/index.blade.php index 014b74e..247d3d1 100644 --- a/resources/views/products/index.blade.php +++ b/resources/views/products/index.blade.php @@ -55,15 +55,15 @@ </div> <div class="card-body"> <span class="text-left h2 product-name">{{ $product->name }}</span> - <p class="card-text mt-3 h5">Боја: + <p class="card-text mt-3 h5 small">ДоÑтапни бои: @foreach($product->productColors->unique('color_name') as $productColor) <span class="border" style="background-color: {{ $productColor->color_name }}; margin-right: 5px; padding: 8px; display: inline-block;"></span> @endforeach </p> <div class="row"> <div class="col"> - <p class="card-text h5 mt-2"> - Величина: + <p class="card-text h5 mt-2 small"> + Величини: @foreach($product->productColors->unique('size.name') as $productColor) @if(!$loop->first) <span class="ml-1">,</span> @@ -75,11 +75,11 @@ <div class="col text-right"> <p class="card-text h5 mt-3"> @if($product->discount_id && $product->discount->status === 'active') - <strong>Цена:</strong> + <strong class="small">Цена:</strong> <span class="original-price text-danger small" style="text-decoration: line-through;">{{ $product->price }} ден.</span> <p class="discounted-price text-success h4">{{ $product->price - ($product->price * ($product->discount->percentage / 100)) }} ден.</p> @else - <strong>Цена:</strong> {{ $product->price }} ден. + <strong class="small">Цена:</strong> {{ $product->price }} ден. @endif </p> </div> -- GitLab From 9751b02dc05680c6a451c882d1c840f16deca7d1 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Thu, 11 Jul 2024 00:58:15 +0200 Subject: [PATCH 06/16] finished the responsive sidebar with dark/light mode option --- public/css/app.css | 10 ++ public/js/script.js | 57 ++++++--- resources/views/layouts/app.blade.php | 6 +- resources/views/products/index.blade.php | 155 ++++++++++++----------- resources/views/users/edit.blade.php | 90 +++++++------ 5 files changed, 182 insertions(+), 136 deletions(-) diff --git a/public/css/app.css b/public/css/app.css index 9fc3164..23d371d 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -56,6 +56,16 @@ .sidebar.close { width: 88px !important; } +.main-content-sidebar-closed { + margin-left: 88px; + transition: var(--tran-03); +} + +.main-content-sidebar-opened { + margin-left: 250px; + transition: var(--tran-03); +} + /* reusable CSS */ .sidebar .text { font-size: 16px; diff --git a/public/js/script.js b/public/js/script.js index 5993220..3a29fbd 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -2,20 +2,22 @@ document.addEventListener("DOMContentLoaded", function () { const searchInput = document.getElementById("searchInput"); const products = document.querySelectorAll(".product"); - searchInput.addEventListener("input", function () { - const query = this.value.trim().toLowerCase(); + if (searchInput) { + searchInput.addEventListener("input", function () { + const query = this.value.trim().toLowerCase(); - products.forEach((product) => { - const productName = product - .querySelector(".product-name") - .innerText.toLowerCase(); - if (productName.includes(query)) { - product.style.display = "block"; - } else { - product.style.display = "none"; - } + products.forEach((product) => { + const productName = product + .querySelector(".product-name") + .innerText.toLowerCase(); + if (productName.includes(query)) { + product.style.display = "block"; + } else { + product.style.display = "none"; + } + }); }); - }); + } const tableView = document.getElementById("tableView"); const tableViewBtn = document.getElementById("tableViewBtn"); @@ -47,6 +49,32 @@ document.addEventListener("DOMContentLoaded", function () { const modeSwitch = document.querySelector(".toggle-switch"); const modeText = document.querySelector(".mode-text"); const image = document.querySelector(".image"); + const mainContent = document.querySelector(".main-content"); + + // Check the sidebar state on page load + const sidebarState = localStorage.getItem("sidebarState"); + if (sidebarState === "open") { + sidebar.classList.remove("close"); + mainContent.classList.add("main-content-sidebar-opened"); + mainContent.classList.remove("main-content-sidebar-closed"); + } else { + sidebar.classList.add("close"); + mainContent.classList.add("main-content-sidebar-closed"); + mainContent.classList.remove("main-content-sidebar-opened"); + } + + toggle.addEventListener("click", function () { + sidebar.classList.toggle("close"); + if (sidebar.classList.contains("close")) { + mainContent.classList.add("main-content-sidebar-closed"); + mainContent.classList.remove("main-content-sidebar-opened"); + localStorage.setItem("sidebarState", "closed"); + } else { + mainContent.classList.add("main-content-sidebar-opened"); + mainContent.classList.remove("main-content-sidebar-closed"); + localStorage.setItem("sidebarState", "open"); + } + }); // to set dark mode function setDarkMode(isDark) { @@ -63,11 +91,6 @@ document.addEventListener("DOMContentLoaded", function () { const darkMode = localStorage.getItem("darkMode") === "true"; setDarkMode(darkMode); - toggle.addEventListener("click", () => { - sidebar.classList.toggle("close"); - sidebar.classList.toggle("open"); - }); - modeSwitch.addEventListener("click", () => { const isDark = !body.classList.contains("dark"); setDarkMode(isDark); diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index e8d8cb2..33bde58 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -15,6 +15,7 @@ <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.18.0/font/bootstrap-icons.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous"> + {{-- Boxicons CSS --}} <link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'> @@ -25,16 +26,17 @@ @vite(['resources/sass/app.scss', 'resources/js/app.js']) </head> <body> + <div> @include('layouts.navbar') </div> - <div class="home" id="content"> + <div class="main-content" id="content"> @yield('content') </div> <script src="{{ asset('js/script.js') }}"></script> - {{-- <script src="{{ asset('js/profile.js') }}"></script> --}} + <script src="{{ asset('js/profile.js') }}"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> diff --git a/resources/views/products/index.blade.php b/resources/views/products/index.blade.php index 247d3d1..60d4900 100644 --- a/resources/views/products/index.blade.php +++ b/resources/views/products/index.blade.php @@ -3,14 +3,14 @@ @section('content') <div class="container container-fluid"> <div class="row"> - <div class="col-8 offset-3 text-right mt-5"> + <div class="col-12 text-right mt-5"> @if(session('success')) <div class="alert alert-success text-center" role="alert"> {{ session('success') }} </div> @endif <div class="mb-3"> - <input type="text" id="searchInput" placeholder="Пребарувај..." class="mr-2"> + <input type="text" id="searchInput" placeholder="Пребарувај..."> <button id="tableViewBtn" class="btn border border-rounded mr-2"><i class="fa-solid fa-table"></i></button> <button id="listViewBtn" class="btn border border-rounded"><i class="fa-solid fa-bars"></i></button> </div> @@ -20,92 +20,97 @@ </div> </div> </div> - <div class="row" id="tableView"> - @foreach($products as $product) - <div class="col-9 offset-3 mb-4"> - <div class="card px-1 py-4 product"> - @if($product->stock_quantity === 1) - <div class="badge badge-warning" style="position: absolute; top: 0; left: 0;">*Ñамо 1 парче</div> - @endif - @if($product->stock_quantity === 0) - <div class="badge badge-danger" style="position: absolute; top: 0; right: 0;">РаÑпродадено</div> - @endif - <div class="carousel slide" data-ride="carousel" id="carousel-{{ $product->id }}"> - <div class="carousel-inner"> - @if($product->images->isEmpty()) - <div class="carousel-item active" style="height: 300px;"> - <p class="d-flex justify-content-center align-items-center h-100">Сеуште нема Ñлики за овој продукт</p> - </div> - @else - @foreach($product->images as $index => $image) - <div class="carousel-item {{ $index === 0 ? 'active' : '' }}" style="position: relative; height: 300px;"> - <img src="{{ Storage::url($image->image) }}" class="d-block img-fluid rounded mx-auto" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-height: 100%; max-width: 100%; object-fit: contain;" alt="Product Image"> - </div> - @endforeach + + <div class="row mt-5"> + <div class="col-12"> + <div id="tableView" class="row"> + @foreach($products as $product) + <div class="col-12 col-md-6 col-lg-4 mb-4"> + <div class="card px-1 py-4 product"> + @if($product->stock_quantity === 1) + <div class="badge badge-warning" style="position: absolute; top: 0; left: 0;">*Ñамо 1 парче</div> @endif - </div> - <a class="carousel-control-prev" href="#carousel-{{ $product->id }}" role="button" data-slide="prev"> - <span class="carousel-control-prev-icon" aria-hidden="true"></span> - <span class="sr-only">Previous</span> - </a> - <a class="carousel-control-next" href="#carousel-{{ $product->id }}" role="button" data-slide="next"> - <span class="carousel-control-next-icon" aria-hidden="true"></span> - <span class="sr-only">Next</span> - </a> - </div> - <div class="card-body"> - <span class="text-left h2 product-name">{{ $product->name }}</span> - <p class="card-text mt-3 h5 small">ДоÑтапни бои: - @foreach($product->productColors->unique('color_name') as $productColor) - <span class="border" style="background-color: {{ $productColor->color_name }}; margin-right: 5px; padding: 8px; display: inline-block;"></span> - @endforeach - </p> - <div class="row"> - <div class="col"> - <p class="card-text h5 mt-2 small"> - Величини: - @foreach($product->productColors->unique('size.name') as $productColor) - @if(!$loop->first) - <span class="ml-1">,</span> - @endif - <span class="badge badge-secondary">{{ strtoupper($productColor->size->name) }}</span> - @endforeach - </p> - </div> - <div class="col text-right"> - <p class="card-text h5 mt-3"> - @if($product->discount_id && $product->discount->status === 'active') - <strong class="small">Цена:</strong> - <span class="original-price text-danger small" style="text-decoration: line-through;">{{ $product->price }} ден.</span> - <p class="discounted-price text-success h4">{{ $product->price - ($product->price * ($product->discount->percentage / 100)) }} ден.</p> + @if($product->stock_quantity === 0) + <div class="badge badge-danger" style="position: absolute; top: 0; right: 0;">РаÑпродадено</div> + @endif + <div class="carousel slide" data-ride="carousel" id="carousel-{{ $product->id }}"> + <div class="carousel-inner"> + @if($product->images->isEmpty()) + <div class="carousel-item active" style="height: 300px;"> + <p class="d-flex justify-content-center align-items-center h-100">Сеуште нема Ñлики за овој продукт</p> + </div> @else - <strong class="small">Цена:</strong> {{ $product->price }} ден. + @foreach($product->images as $index => $image) + <div class="carousel-item {{ $index === 0 ? 'active' : '' }}" style="position: relative; height: 300px;"> + <img src="{{ Storage::url($image->image) }}" class="d-block img-fluid rounded mx-auto" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-height: 100%; max-width: 100%; object-fit: contain;" alt="Product Image"> + </div> + @endforeach @endif + </div> + <a class="carousel-control-prev" href="#carousel-{{ $product->id }}" role="button" data-slide="prev"> + <span class="carousel-control-prev-icon" aria-hidden="true"></span> + <span class="sr-only">Previous</span> + </a> + <a class="carousel-control-next" href="#carousel-{{ $product->id }}" role="button" data-slide="next"> + <span class="carousel-control-next-icon" aria-hidden="true"></span> + <span class="sr-only">Next</span> + </a> + </div> + <div class="card-body"> + <span class="text-left h2 product-name">{{ $product->name }}</span> + <p class="card-text mt-3 h5 small">ДоÑтапни бои: + @foreach($product->productColors->unique('color_name') as $productColor) + <span class="border" style="background-color: {{ $productColor->color_name }}; margin-right: 5px; padding: 8px; display: inline-block;"></span> + @endforeach </p> + <div class="row"> + <div class="col"> + <p class="card-text h5 mt-2 small"> + Величини: + @foreach($product->productColors->unique('size.name') as $productColor) + @if(!$loop->first) + <span class="ml-1">,</span> + @endif + <span class="badge badge-secondary">{{ strtoupper($productColor->size->name) }}</span> + @endforeach + </p> + </div> + <div class="col text-right"> + <p class="card-text h5 mt-3"> + @if($product->discount_id && $product->discount->status === 'active') + <strong class="small">Цена:</strong> + <span class="original-price text-danger small" style="text-decoration: line-through;">{{ $product->price }} ден.</span> + <p class="discounted-price text-success h4">{{ $product->price - ($product->price * ($product->discount->percentage / 100)) }} ден.</p> + @else + <strong class="small">Цена:</strong> {{ $product->price }} ден. + @endif + </p> + </div> + </div> </div> </div> </div> - </div> + @endforeach </div> - @endforeach - </div> - <div class="row d-none" id="listView"> - @foreach($products as $product) - <div class="col-12 mb-4 product"> - <div class="card p-4"> - <div class="row"> - <div class="col dark-green font-weight-bold">0{{ $product->id }}</div> - <div class="col product-name">{{ $product->name }}</div> - <div class="col text-right"> - <a href="{{ route('products.edit', $product) }}" class="btn border rounded-circle"><i class="fas fa-edit"></i></a> + <div id="listView" class="d-none row"> + @foreach($products as $product) + <div class="col-12 mb-4 product"> + <div class="card p-4"> + <div class="row"> + <div class="col dark-green font-weight-bold">#{{ $product->id }}</div> + <div class="col product-name">{{ $product->name }}</div> + <div class="col text-right"> + <a href="{{ route('products.edit', $product) }}" class="btn border rounded-circle"><i class="fas fa-edit"></i></a> + </div> + </div> </div> </div> - </div> + @endforeach </div> - @endforeach + </div> </div> </div> <div class="d-flex justify-content-center mt-4 pagination"> {{ $products->links('pagination::bootstrap-4') }} </div> -@endsection +@endsection \ No newline at end of file diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index d8f875d..c97a5bd 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -1,16 +1,25 @@ @extends('layouts.app') @section('content') - <div class="container px-2 py-5"> + <div class="container container-fluid"> <div class="row"> + <div class="col-12 text-right mt-5"> + @if(session('success')) + <div class="alert alert-success text-center" role="alert"> + {{ session('success') }} + </div> + @endif + </div> + </div> + + <div class="row mt-5"> <div class="col-md-8 offset-md-2 pr-4"> <form id="edit_user" method="POST" action="{{ route('users.update', $user->id) }}" enctype="multipart/form-data"> @csrf - @method('PUT') @if(session('success')) - <div class="alert alert-success w-75"> + <div class="alert alert-success w-75 mx-auto text-center"> {{ session('success') }} </div> @endif @@ -22,17 +31,17 @@ </div> </div> - <div class="mb-3"> + <div class="mb-3 text-center"> @if($user->profile_picture) - <div class="rounded-circle overflow-hidden d-flex justify-content-center align-items-center" style="width: 100px; height: 100px;"> - <img id="profilePicture" src="{{ asset('storage/' . $user->profile_picture) }}" alt="Profile Picture" class="w-auto h-100"> - </div> + <div class="rounded-circle overflow-hidden d-flex justify-content-center align-items-center mx-auto" style="width: 100px; height: 100px;"> + <img id="profilePicture" src="{{ asset('storage/' . $user->profile_picture) }}" alt="Profile Picture" class="w-auto h-100"> + </div> @else - <div class="rounded-circle bg-secondary text-light d-flex justify-content-center align-items-center" style="width: 200px; height: 200px;"> + <div class="rounded-circle bg-secondary text-light d-flex justify-content-center align-items-center mx-auto" style="width: 200px; height: 200px;"> <span class="fs-1">No Image</span> </div> @endif - <a href="#" id="changePhotoLink" class="text-decoration-none d-block text-left mt-2 font-weight-bold dark-green">Промени Ñлика</a> + <a href="#" id="changePhotoLink" class="text-decoration-none d-block text-center mt-2 font-weight-bold dark-green">Промени Ñлика</a> <input type="file" class="form-control mt-2" id="profile_picture" name="profile_picture" hidden> @error('profile_picture') <span class="text-danger">{{ $message }}</span> @@ -49,7 +58,7 @@ <div class="mb-3"> <label for="email" class="form-label font-weight-bold">Email адреÑа</label> - <input type="text" class="form-control" id="email" name="email" value="{{ $user->email }}"></input> + <input type="text" class="form-control" id="email" name="email" value="{{ $user->email }}"> @error('email') <span class="text-danger">{{ $message }}</span> @enderror @@ -63,74 +72,71 @@ @enderror </div> - <label for="password" class="form-label font-weight-bold">Лозинка</label> <div class="mb-1 d-flex align-items-center"> - <div class="position-relative w-75"> - <input type="password" class="form-control w-100" id="password" name="password" - {{-- намерно е оÑтавен Ñо звездички паÑвордо --}} - value="*******" data-original-type="password"> - <button id="showPasswordButton" class="btn show-password-button" type="button"> - <i class="fa-solid fa-eye-slash"></i> - </button> - </div> + <div class="position-relative w-75"> + <input type="password" class="form-control w-100" id="password" name="password" value="*******" data-original-type="password"> + <button id="showPasswordButton" class="btn show-password-button" type="button"> + <i class="fa-solid fa-eye-slash"></i> + </button> + </div> @error('password') <span class="text-danger">{{ $message }}</span> @enderror </div> @if(session('error-password-change')) - <div class="alert alert-danger w-75"> + <div class="alert alert-danger w-75 mx-auto text-center"> {{ session('error-password-change') }} </div> @endif - <div class="mb-3 dark-green"> + <div class="mb-3 text-center"> <button id="changePasswordBtn" class="btn btn-link font-weight-bold dark-green" type="button">Промени лозинка</button> </div> <div class="mb-1 d-none" id="passwordChangeFields"> <label for="current-password" class="form-label font-weight-bold">Тековна Лозинка</label> <div class="mb-1 d-flex align-items-center"> - <div class="position-relative w-75"> - <input type="password" class="form-control w-100" id="current-password" name="current-password" value="" data-original-type="password"> - <button id="showCurrentPasswordButton" class="btn show-password-button" type="button"> - <i class="fa-solid fa-eye-slash"></i> - </button> - </div> + <div class="position-relative w-75"> + <input type="password" class="form-control w-100" id="current-password" name="current-password" value="" data-original-type="password"> + <button id="showCurrentPasswordButton" class="btn show-password-button" type="button"> + <i class="fa-solid fa-eye-slash"></i> + </button> + </div> @error('current-password') {{-- <span class="password-change text-danger">{{ $message }}</span> --}} @enderror </div> - + <label for="new-password" class="form-label font-weight-bold">Ðова Лозинка</label> <div class="mb-1 d-flex align-items-center"> - <div class="position-relative w-75"> - <input type="password" class="form-control w-100" id="new-password" name="new-password" value="" data-original-type="password"> - <button id="showNewPasswordButton" class="btn show-password-button" type="button"> - <i class="fa-solid fa-eye-slash"></i> - </button> - </div> + <div class="position-relative w-75"> + <input type="password" class="form-control w-100" id="new-password" name="new-password" value="" data-original-type="password"> + <button id="showNewPasswordButton" class="btn show-password-button" type="button"> + <i class="fa-solid fa-eye-slash"></i> + </button> + </div> @error('new-password') <span class="password-change text-danger">{{ $message }}</span> @enderror </div> - + <label for="password-repeat" class="form-label font-weight-bold">Повтори Лозинка</label> <div class="mb-1 d-flex align-items-center"> - <div class="position-relative w-75"> - <input type="password" class="form-control w-100" id="password-repeat" name="password-repeat" value="" data-original-type="password"> - <button id="showPasswordRepeatButton" class="btn show-password-button" type="button"> - <i class="fa-solid fa-eye-slash"></i> - </button> - </div> + <div class="position-relative w-75"> + <input type="password" class="form-control w-100" id="password-repeat" name="password-repeat" value="" data-original-type="password"> + <button id="showPasswordRepeatButton" class="btn show-password-button" type="button"> + <i class="fa-solid fa-eye-slash"></i> + </button> + </div> @error('password-repeat') <span class="password-change text-danger">{{ $message }}</span> @enderror </div> </div> - <div class="row mb-3 d-flex justify-center"> + <div class="row mb-3 d-flex justify-content-center"> <button type="submit" class="btn btn-dark w-50">Зачувај</button> </div> </form> -- GitLab From 5779349f6604f438cd00011a4a3aed51235abdc2 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Mon, 7 Oct 2024 20:19:09 +0200 Subject: [PATCH 07/16] semi finished product features --- app/Http/Controllers/ProductController.php | 4 +- public/css/app.css | 13 -- public/js/product.js | 137 --------------------- resources/views/products/edit.blade.php | 6 +- 4 files changed, 8 insertions(+), 152 deletions(-) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 678dd8e..852b186 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -109,12 +109,14 @@ public function edit(Product $product) $brands = Brand::all(); $product->load('productColors'); $discounts = Discount::where('status', 'active')->get(); + $images = ProductImage::where('product_id', $product->id)->get(); + // dd($images); $productColors = $product->productColors->pluck('color_name')->map(function ($color) { return trim($color); })->toArray(); - return view('products.edit', compact('product', 'categories', 'brands', 'productColors', 'discounts')); + return view('products.edit', compact('product', 'categories', 'brands', 'productColors', 'discounts', 'images')); } /** diff --git a/public/css/app.css b/public/css/app.css index 23d371d..329f5cb 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -254,19 +254,6 @@ body.dark .switch::before { left: 24px; } -/* .home { - position: relative; - left: 250px; - height: 100vh; - width: calc(100%-250px); - background: var(--vintage-pink-light); -} - -.sidebar.close ~ .home { - left: 88px; - width: calc(100%-88px); -} */ - #searchInput { border: 1px solid rgb(160, 154, 154); border-radius: 5px; diff --git a/public/js/product.js b/public/js/product.js index cf4c2bb..b6fbe9d 100644 --- a/public/js/product.js +++ b/public/js/product.js @@ -2,143 +2,6 @@ document.addEventListener("DOMContentLoaded", function () { // плуÑ/Ð¼Ð¸Ð½ÑƒÑ ÐºÐ¾Ð¿Ñ‡Ðµ кај количина на продукт document.getElementById("name").focus(); - var minusBtn = document.getElementById("minus-btn"); - if (minusBtn) { - minusBtn.addEventListener("click", function () { - var input = document.getElementById("stock_quantity"); - var value = parseInt(input.value); - if (!isNaN(value) && value > 0) { - input.value = value - 1; - } - updateColorCheckboxValidity(); - updateCheckboxValidity(); - }); - } - - var plusBtn = document.getElementById("plus-btn"); - if (plusBtn) { - plusBtn.addEventListener("click", function () { - var input = document.getElementById("stock_quantity"); - var value = parseInt(input.value); - if (!isNaN(value)) { - input.value = value + 1; - } - updateColorCheckboxValidity(); - updateCheckboxValidity(); - }); - } - - // чек бокÑовите за боја - const colorCheckboxes = document.querySelectorAll(".color-checkbox-input"); - colorCheckboxes.forEach(function (checkbox) { - checkbox.addEventListener("change", function () { - const label = this.nextElementSibling; - if (this.checked) { - label.classList.add("checked"); - } else { - label.classList.remove("checked"); - } - updateColorCheckboxValidity(); - updateCheckboxValidity(); - }); - }); - - const sizeCheckboxes = document.querySelectorAll(".size-checkbox-input"); - sizeCheckboxes.forEach(function (checkbox) { - checkbox.addEventListener("change", function () { - updateCheckboxValidity(); - }); - }); - - function updateCheckboxValidity() { - const stockQuantity = parseInt( - document.getElementById("stock_quantity").value - ); - - // Проверка за боите - const checkedColorCheckboxes = document.querySelectorAll( - ".color-checkbox-input:checked" - ); - if (checkedColorCheckboxes.length > stockQuantity) { - checkedColorCheckboxes.forEach(function (checkbox) { - checkbox.checked = false; - checkbox.nextElementSibling.classList.remove("checked"); - }); - } - - // Проверка за величините - const checkedSizeCheckboxes = document.querySelectorAll( - ".size-checkbox-input:checked" - ); - if (checkedSizeCheckboxes.length > stockQuantity) { - checkedSizeCheckboxes.forEach(function (checkbox) { - checkbox.checked = false; - checkbox.nextElementSibling.classList.remove("checked"); - }); - } - - const uncheckedSizeCheckboxes = document.querySelectorAll( - ".size-checkbox-input:not(:checked)" - ); - - uncheckedSizeCheckboxes.forEach(function (checkbox) { - checkbox.disabled = checkedSizeCheckboxes.length >= stockQuantity; - if (checkbox.disabled) { - const messageElement = document.querySelector( - ".stock_quantity_exceeded_for_sizes" - ); - messageElement.innerText = `МакÑимален број на различни величини е доÑтигнат. (Количина ${ - document.getElementById("stock_quantity").value - })`; - } else { - checkbox.nextElementSibling.style.backgroundColor = ""; - document.querySelector( - ".stock_quantity_exceeded_for_sizes" - ).innerText = ""; - } - }); - } - - function updateColorCheckboxValidity() { - const stockQuantity = parseInt( - document.getElementById("stock_quantity").value - ); - const checkedColorCheckboxes = document.querySelectorAll( - ".color-checkbox-input:checked" - ); - - if (checkedColorCheckboxes.length > stockQuantity) { - const excessCheckedCheckboxes = Array.from( - checkedColorCheckboxes - ).slice(stockQuantity); - excessCheckedCheckboxes.forEach(function (checkbox) { - checkbox.checked = false; - checkbox.nextElementSibling.classList.remove("checked"); - }); - } - - const uncheckedColorCheckboxes = document.querySelectorAll( - ".color-checkbox-input:not(:checked)" - ); - uncheckedColorCheckboxes.forEach(function (checkbox) { - checkbox.disabled = checkedColorCheckboxes.length >= stockQuantity; - if (checkbox.disabled) { - checkbox.nextElementSibling.style.backgroundColor = "white"; - const messageElement = document.querySelector( - ".stock_quantity_exceeded_for_colors" - ); - messageElement.innerText = `МакÑимален број на различни бои е доÑтигнат. (Количина ${ - document.getElementById("stock_quantity").value - })`; - } else { - checkbox.nextElementSibling.style.backgroundColor = ""; - document.querySelector( - ".stock_quantity_exceeded_for_colors" - ).innerText = ""; - } - }); - } - // делот Ñо 4-те Ñлики document .querySelectorAll('.image-upload-box input[type="file"]') diff --git a/resources/views/products/edit.blade.php b/resources/views/products/edit.blade.php index 91436b6..2e259c0 100644 --- a/resources/views/products/edit.blade.php +++ b/resources/views/products/edit.blade.php @@ -118,7 +118,10 @@ </div> <div class="mb-3"> - <label for="images" class="form-label">Слики (МакÑимум 4)</label> + <label for="images" class="form-label">Слики</label> + {{-- <div> + <img src="{{$images}}" alt=""> + </div> --}} <div class="image-grid"> @for ($i = 0; $i < 4; $i++) <div class="image-upload-box"> @@ -132,6 +135,7 @@ </div> <i class="fas fa-plus"></i> </div> + @error('images.' . $i) <span class="text-danger">{{ $message }}</span> @enderror -- GitLab From 130b895d52c98be581be8c9b54da3e415e524e9f Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Mon, 7 Oct 2024 21:42:30 +0200 Subject: [PATCH 08/16] image slots showing the images on edit product --- app/Http/Controllers/ProductController.php | 18 ++++++++++++------ resources/views/products/create.blade.php | 10 ++++++---- resources/views/products/edit.blade.php | 17 ++++++++--------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 852b186..5035ed1 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -110,13 +110,15 @@ public function edit(Product $product) $product->load('productColors'); $discounts = Discount::where('status', 'active')->get(); $images = ProductImage::where('product_id', $product->id)->get(); + $oldImages = $images->pluck('image')->toArray(); // Fetch only the image paths + // dd($images); $productColors = $product->productColors->pluck('color_name')->map(function ($color) { return trim($color); })->toArray(); - return view('products.edit', compact('product', 'categories', 'brands', 'productColors', 'discounts', 'images')); + return view('products.edit', compact('product', 'categories', 'brands', 'productColors', 'discounts', 'images', 'oldImages')); } /** @@ -126,11 +128,13 @@ public function update(ProductRequest $request, Product $product) { $validated = $request->validated(); + // Update basic product information $product->update($validated); + // Handle colors and sizes $colorsAndSizes = $request->input('colors_and_sizes', []); - // прво избриши ги Ñите рекорди, за да ги креираме наново + // Delete old colors and sizes, then recreate them ProductColor::where('product_id', $product->id)->delete(); foreach ($colorsAndSizes as $colorAndSize) { @@ -142,10 +146,11 @@ public function update(ProductRequest $request, Product $product) ]); } + // Handle images $images = $request->file('images'); - if ($images) { - $product->images()->delete(); + // If new images are uploaded, add them to the existing ones + if ($images) { foreach ($images as $image) { if ($image && $image->isValid()) { $path = $image->store('images/products', 'public'); @@ -157,14 +162,15 @@ public function update(ProductRequest $request, Product $product) } } + // Handle discount if ($request->filled('selectedDiscountId')) { $product->discount_id = $request->input('selectedDiscountId'); - $product->save(); } else { $product->discount_id = null; - $product->save(); } + $product->save(); + return redirect()->route('products.index')->with('success', 'Продуктот е уÑпешно ажуриран!'); } } diff --git a/resources/views/products/create.blade.php b/resources/views/products/create.blade.php index cdc321d..ae2ea37 100644 --- a/resources/views/products/create.blade.php +++ b/resources/views/products/create.blade.php @@ -99,10 +99,8 @@ <div class="image-upload-box"> <input type="file" class="form-control" accept="image/*" name="images[]"> <div class="image-container"> - @if(Session::has('uploaded_image')) - <img src="{{ asset(Session::get('uploaded_image')) }}" class="uploaded-image"> - @else - <img src="{{ $oldImages[$i] ?? '' }}" class="uploaded-image"> + @if(isset($oldImages[$i])) + <img src="{{ asset($oldImages[$i]) }}" class="uploaded-image" alt="Uploaded image"> @endif </div> <i class="fas fa-plus"></i> @@ -111,6 +109,10 @@ <span class="text-danger">{{ $errors->first('images.' . $i) }}</span> @endif @endfor + </div> + @if ($errors->has('images.' . $i)) + <span class="text-danger">{{ $errors->first('images.' . $i) }}</span> + @endif </div> </div> diff --git a/resources/views/products/edit.blade.php b/resources/views/products/edit.blade.php index 2e259c0..9251d48 100644 --- a/resources/views/products/edit.blade.php +++ b/resources/views/products/edit.blade.php @@ -119,29 +119,28 @@ <div class="mb-3"> <label for="images" class="form-label">Слики</label> - {{-- <div> - <img src="{{$images}}" alt=""> - </div> --}} <div class="image-grid"> @for ($i = 0; $i < 4; $i++) <div class="image-upload-box"> <input type="file" class="form-control" accept="image/*" name="images[]"> <div class="image-container"> - @if(Session::has('uploaded_image')) - <img src="{{ asset(Session::get('uploaded_image')) }}" class="uploaded-image"> - @else - <img src="{{ $oldImages[$i] ?? '' }}" class="uploaded-image"> + @if(isset($oldImages[$i])) + <img src="{{ asset('storage/' . $oldImages[$i]) }}" class="uploaded-image" alt="Uploaded image"> @endif </div> <i class="fas fa-plus"></i> </div> - + @if ($errors->has('images.' . $i)) + <span class="text-danger">{{ $errors->first('images.' . $i) }}</span> + @endif + @endfor + </div> @error('images.' . $i) <span class="text-danger">{{ $message }}</span> @enderror - @endfor </div> </div> + </div> <div class="row mb-3"> <div class="col-md-6"> -- GitLab From 3413823d47fa7913bd79dfa950430ed737d25830 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Tue, 8 Oct 2024 22:52:48 +0200 Subject: [PATCH 09/16] image slots startingwith only one done, still need some modifying in the JavaScript --- app/Http/Controllers/ProductController.php | 17 ++ public/js/product.js | 287 +++++++++++++++++++-- resources/views/products/create.blade.php | 86 ++---- resources/views/products/edit.blade.php | 45 ++-- 4 files changed, 342 insertions(+), 93 deletions(-) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 5035ed1..b5f84a1 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -12,6 +12,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use App\Http\Requests\ProductRequest; +use Illuminate\Support\Facades\Storage; class ProductController extends Controller { @@ -162,6 +163,22 @@ public function update(ProductRequest $request, Product $product) } } + // Handle image deletions if necessary + if ($request->has('deleted_images')) { + $deletedImages = explode(',', $request->input('deleted_images')[0]); + foreach ($deletedImages as $deletedImage) { + if (!empty($deletedImage)) { + $productImage = ProductImage::find($deletedImage); + if ($productImage) { + // Delete the image file from storage + Storage::disk('public')->delete($productImage->image); + // Delete the record from the database + $productImage->delete(); + } + } + } + } + // Handle discount if ($request->filled('selectedDiscountId')) { $product->discount_id = $request->input('selectedDiscountId'); diff --git a/public/js/product.js b/public/js/product.js index b6fbe9d..371c1fc 100644 --- a/public/js/product.js +++ b/public/js/product.js @@ -3,25 +3,66 @@ document.addEventListener("DOMContentLoaded", function () { document.getElementById("name").focus(); // делот Ñо 4-те Ñлики - document - .querySelectorAll('.image-upload-box input[type="file"]') - .forEach(function (input) { - input.addEventListener("change", function () { - const reader = new FileReader(); - const imageContainer = this.parentNode.querySelector( - ".image-container img" - ); + // document + // .querySelectorAll('.image-upload-box input[type="file"]') + // .forEach(function (input) { + // input.addEventListener("change", function () { + // const reader = new FileReader(); + // const imageContainer = this.parentNode.querySelector( + // ".image-container img" + // ); + + // reader.onload = function (e) { + // imageContainer.src = e.target.result; + // }; + // input.parentElement.querySelector( + // ".image-container + i" + // ).style.display = "none"; + + // reader.readAsDataURL(this.files[0]); + // }); + // }); + const addInputBtn = document.getElementById("addInputBtn"); + const dynamicInputsContainer = document.getElementById("dynamicInputs"); + let inputIndex = 0; + + addInputBtn.addEventListener("click", function () { + const newInputGroup = document.createElement("div"); + newInputGroup.classList.add("mb-3", "input-group"); - reader.onload = function (e) { - imageContainer.src = e.target.result; - }; - input.parentElement.querySelector( - ".image-container + i" - ).style.display = "none"; + newInputGroup.innerHTML = ` + <select class="form-select me-2" name="colors_and_sizes[${inputIndex}][size_id]" required> + <option value="" disabled selected>Величина</option> + <option value="1">XS</option> + <option value="2">S</option> + <option value="3">M</option> + <option value="4">L</option> + <option value="5">XL</option> + </select> + <select class="form-select me-2" name="colors_and_sizes[${inputIndex}][color]" required> + <option value="" disabled selected>Боја</option> + <option value="black">Black</option> + <option value="white">White</option> + <option value="yellow">Yellow</option> + <option value="blue">Blue</option> + <option value="green">Green</option> + <option value="red">Red</option> + <option value="pink">Pink</option> + </select> + <input type="number" class="form-control me-2" name="colors_and_sizes[${inputIndex}][stock]" min="0" placeholder="Количина" required> + <button type="button" class="btn btn-danger remove-input-btn"><i class="fa fa-minus"></i></button> + `; - reader.readAsDataURL(this.files[0]); + dynamicInputsContainer.appendChild(newInputGroup); + + newInputGroup + .querySelector(".remove-input-btn") + .addEventListener("click", function () { + newInputGroup.remove(); }); - }); + + inputIndex++; + }); //копчето -откажи- на крајо од формата const cancelButton = document.getElementById("cancel"); @@ -105,4 +146,218 @@ document.addEventListener("DOMContentLoaded", function () { alert("Please select a discount."); } }); + + let imageSlotCount = 1; + const addImageBtn = document.getElementById("addImageBtn"); + const imageUploadsContainer = document.getElementById("image-uploads"); + + // Event listener for adding image slots + addImageBtn.addEventListener("click", addImageSlot); + + // Add initial event listener for the first image slot + setupImageSlotEventListeners(0); + + function setupImageSlotEventListeners(index) { + const fileInput = document.getElementById(`image_${index}`); + if (fileInput) { + fileInput.addEventListener("change", function () { + previewImage(this, index); + }); + } + + const deleteButton = document.getElementById(`delete_image_${index}`); + if (deleteButton) { + deleteButton.addEventListener("click", function () { + deleteImage(index); + }); + } + + const changeButton = document.getElementById(`change_image_${index}`); + if (changeButton) { + changeButton.addEventListener("click", function () { + changeImage(index); + }); + } + } + + function previewImage(input, index) { + const file = input.files[0]; + const previewImage = document.querySelector(`#image_preview_${index} img`); + const addImageBtn = document.getElementById("addImageBtn"); + + if (file) { + const reader = new FileReader(); + + reader.onload = function (e) { + if (previewImage) { // Ensure the preview image exists + previewImage.src = e.target.result; + previewImage.classList.remove("d-none"); + } + + // Enable the add image button when the image slot is populated + addImageBtn.disabled = false; + + // Define deleteButton and changeButton variables + const deleteButton = document.getElementById(`delete_image_${index}`); + const changeButton = document.getElementById(`change_image_${index}`); + + // Show the delete and change buttons for the image slot + if (deleteButton) { // Check if delete button exists + deleteButton.classList.remove("d-none"); + } + if (changeButton) { // Check if change button exists + changeButton.classList.remove("d-none"); + } + }; + + reader.readAsDataURL(file); + } else { + // Reset when no file is selected + resetImageSlot(index); + } + } + + function resetImageSlot(index) { + const previewImage = document.querySelector( + `#image_preview_${index} img` + ); + if (previewImage) { + // Check if the image exists + previewImage.src = ""; + previewImage.classList.add("d-none"); + } + + const deleteButton = document.getElementById(`delete_image_${index}`); + if (deleteButton) { + // Check if the delete button exists + deleteButton.classList.add("d-none"); + } + + const changeButton = document.getElementById(`change_image_${index}`); + if (changeButton) { + // Check if the change button exists + changeButton.classList.add("d-none"); + } + + // Clear the file input value + const fileInput = document.getElementById(`image_${index}`); + if (fileInput) { + // Check if the file input exists + fileInput.value = ""; + } + + // Disable the add image button if no other slot is populated + checkImageSlots(); + } + + function deleteImage(index) { + resetImageSlot(index); + rearrangeImageSlots(); + checkImageSlots(); + } + + function rearrangeImageSlots() { + const imageUploads = document.getElementById("image-uploads"); + const slots = imageUploads.querySelectorAll(".mb-3"); + + // Create an array to hold new slots + let newSlots = []; + + // Loop through existing slots and rearrange them + slots.forEach((slot, currentIndex) => { + // Get the current file input and check if it's not empty + const fileInput = slot.querySelector('input[type="file"]'); + if (fileInput && fileInput.files.length > 0) { + // Update the IDs of the file input and its associated elements + const newIndex = newSlots.length; // New index based on the new array length + fileInput.id = `image_${newIndex}`; + const previewImage = slot.querySelector(".image-container img"); + previewImage.id = `image_preview_${newIndex}`; + slot.querySelector( + ".image-actions #change_image_" + currentIndex + ).id = `change_image_${newIndex}`; + slot.querySelector( + ".btn-danger" + ).id = `delete_image_${newIndex}`; + + newSlots.push(slot); + } + }); + + // Clear the existing slots and add the new slots + imageUploads.innerHTML = ""; + newSlots.forEach((slot) => imageUploads.appendChild(slot)); + + // Update the image slot count based on new slots + imageSlotCount = newSlots.length; + + // Ensure there's always at least one image slot + if (imageSlotCount === 0) { + addImageSlot(); // Call the function to add a new image slot + } + + // Re-setup event listeners for the remaining slots + newSlots.forEach((slot, index) => { + setupImageSlotEventListeners(index); + }); + + // Enable or disable the add image button based on the number of populated slots + addImageBtn.disabled = imageSlotCount >= 8; + } + + function changeImage(index) { + document.getElementById(`image_${index}`).click(); + } + + function addImageSlot() { + if (imageSlotCount < 8) { + const newSlot = document.createElement("div"); + newSlot.classList.add("mb-3"); + newSlot.innerHTML = ` + <div class="image-upload-box"> + <input type="file" class="form-control" accept="image/*" name="images[]" id="image_${imageSlotCount}"> + <div class="image-container" id="image_preview_${imageSlotCount}"> + <img src="" class="uploaded-image d-none" alt="Preview Image"> + </div> + <div class="image-actions"> + <button type="button" class="btn btn-sm btn-secondary d-none m-1" id="change_image_${imageSlotCount}">Промени Ñлика</button> + </div> + </div> + <div class="mt-2"> + <button type="button" class="btn btn-sm btn-danger d-none m-1" id="delete_image_${imageSlotCount}">Избриши Ñлика</button> + </div> + `; + imageUploadsContainer.appendChild(newSlot); + setupImageSlotEventListeners(imageSlotCount); + imageSlotCount++; + + // Disable the add image button after adding a new empty slot + if (imageSlotCount >= 8) { + addImageBtn.disabled = true; + document + .getElementById("maxImagesWarning") + .classList.remove("d-none"); + } else { + addImageBtn.disabled = true; // Disable initially until an image is added + } + } + } + + function checkImageSlots() { + // Check if any of the image slots are populated + let isAnySlotPopulated = false; + for (let i = 0; i < imageSlotCount; i++) { + const fileInput = document.getElementById(`image_${i}`); + if (fileInput && fileInput.files.length > 0) { + isAnySlotPopulated = true; + break; + } + } + + // Enable or disable the add image button + addImageBtn.disabled = !isAnySlotPopulated; + } + + // Initially disable the add image button because no slots are populated + addImageBtn.disabled = true; }); diff --git a/resources/views/products/create.blade.php b/resources/views/products/create.blade.php index ae2ea37..94f09cb 100644 --- a/resources/views/products/create.blade.php +++ b/resources/views/products/create.blade.php @@ -93,26 +93,37 @@ </div> <div class="mb-3"> - <label for="images" class="form-label">Слики (МакÑимум 4)</label> - <div class="image-grid"> - @for ($i = 0; $i < 4; $i++) + <label for="images" class="form-label">Слики (МакÑимум 8)</label> + + <!-- Container for image slots --> + <div class="row" id="image-uploads"> + <div class="mb-3"> <div class="image-upload-box"> - <input type="file" class="form-control" accept="image/*" name="images[]"> - <div class="image-container"> - @if(isset($oldImages[$i])) - <img src="{{ asset($oldImages[$i]) }}" class="uploaded-image" alt="Uploaded image"> - @endif + <input type="file" class="form-control" accept="image/*" name="images[]" id="image_0"> + <div class="image-container" id="image_preview_0"> + <img src="" class="uploaded-image d-none" alt="Preview Image"> + </div> + <div class="image-actions"> + <button type="button" class="btn btn-sm btn-secondary d-none m-1" id="change_image_0">Промени Ñлика</button> </div> - <i class="fas fa-plus"></i> </div> - @if ($errors->has('images.' . $i)) - <span class="text-danger">{{ $errors->first('images.' . $i) }}</span> - @endif - @endfor + <!-- Delete image button outside the slot --> + <div class="mt-2"> + <button type="button" class="btn btn-sm btn-danger d-none m-1" id="delete_image_0">Избриши Ñлика</button> + </div> + </div> </div> - @if ($errors->has('images.' . $i)) - <span class="text-danger">{{ $errors->first('images.' . $i) }}</span> - @endif + + <!-- Hidden input to track deleted images --> + <input type="hidden" id="deleted_images" name="deleted_images[]"> + + <!-- Button to add more image slots --> + <button type="button" class="btn btn-secondary mt-2" id="addImageBtn">Додај уште една Ñлика</button> + <p id="maxImagesWarning" class="text-danger d-none">Можете да додадете макÑимум 8 Ñлики.</p> + </div> + {{-- @if ($errors->has('images.' . $i)) + <span class="text-danger">{{ $errors->first('images.' . $i) }}</span> + @endif --}} </div> </div> @@ -195,48 +206,5 @@ </div> @endsection -<script> - document.addEventListener('DOMContentLoaded', function () { - const addInputBtn = document.getElementById('addInputBtn'); - const dynamicInputsContainer = document.getElementById('dynamicInputs'); - let inputIndex = 0; - - addInputBtn.addEventListener('click', function () { - const newInputGroup = document.createElement('div'); - newInputGroup.classList.add('mb-3', 'input-group'); - - newInputGroup.innerHTML = ` - <select class="form-select me-2" name="colors_and_sizes[${inputIndex}][size_id]" required> - <option value="" disabled selected>Величина</option> - <option value="1">XS</option> - <option value="2">S</option> - <option value="3">M</option> - <option value="4">L</option> - <option value="5">XL</option> - </select> - <select class="form-select me-2" name="colors_and_sizes[${inputIndex}][color]" required> - <option value="" disabled selected>Боја</option> - <option value="black">Black</option> - <option value="white">White</option> - <option value="yellow">Yellow</option> - <option value="blue">Blue</option> - <option value="green">Green</option> - <option value="red">Red</option> - <option value="pink">Pink</option> - </select> - <input type="number" class="form-control me-2" name="colors_and_sizes[${inputIndex}][stock]" min="0" placeholder="Количина" required> - <button type="button" class="btn btn-danger remove-input-btn"><i class="fa fa-minus"></i></button> - `; - - dynamicInputsContainer.appendChild(newInputGroup); - - newInputGroup.querySelector('.remove-input-btn').addEventListener('click', function () { - newInputGroup.remove(); - }); - - inputIndex++; - }); - }); -</script> diff --git a/resources/views/products/edit.blade.php b/resources/views/products/edit.blade.php index 9251d48..bca376e 100644 --- a/resources/views/products/edit.blade.php +++ b/resources/views/products/edit.blade.php @@ -118,27 +118,36 @@ </div> <div class="mb-3"> - <label for="images" class="form-label">Слики</label> - <div class="image-grid"> - @for ($i = 0; $i < 4; $i++) - <div class="image-upload-box"> - <input type="file" class="form-control" accept="image/*" name="images[]"> - <div class="image-container"> - @if(isset($oldImages[$i])) - <img src="{{ asset('storage/' . $oldImages[$i]) }}" class="uploaded-image" alt="Uploaded image"> - @endif + <label for="images" class="form-label">Слики (МакÑимум 8)</label> + + <!-- Container for image slots --> + <div class="row" id="image-uploads"> + @foreach ($oldImages as $index => $image) + <div class="mb-3"> + <div class="image-upload-box"> + <input type="file" class="form-control" accept="image/*" name="images[]" id="image_{{ $index }}"> + <div class="image-container" id="image_preview_{{ $index }}"> + <img src="{{ asset('storage/' . $image) }}" class="uploaded-image" alt="Uploaded image"> + </div> + <div class="image-actions"> + <button type="button" class="btn btn-sm btn-secondary d-none m-1" id="change_image_{{ $index }}">Промени Ñлика</button> + </div> + </div> + <!-- Delete image button outside the slot --> + <div class="mt-2"> + <button type="button" class="btn btn-sm btn-danger" id="delete_image_{{ $index }}">Избриши Ñлика</button> </div> - <i class="fas fa-plus"></i> </div> - @if ($errors->has('images.' . $i)) - <span class="text-danger">{{ $errors->first('images.' . $i) }}</span> - @endif - @endfor - </div> - @error('images.' . $i) - <span class="text-danger">{{ $message }}</span> - @enderror + @endforeach </div> + + <!-- Hidden input to track deleted images --> + <input type="hidden" id="deleted_images" name="deleted_images[]"> + + <!-- Button to add more image slots --> + <button type="button" class="btn btn-secondary mt-2" id="addImageBtn" >Додај уште една Ñлика</button> + <p id="maxImagesWarning" class="text-danger d-none">Можете да додадете макÑимум 8 Ñлики.</p> + </div> </div> </div> -- GitLab From 29401df5bbd9b007fbfd1005348ebb89c8f6a24f Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Wed, 9 Oct 2024 22:39:45 +0200 Subject: [PATCH 10/16] changed the image section into one multiple input, having error with uploading images... will do tomorrow --- app/Http/Controllers/ProductController.php | 33 ++- public/css/forms-style.css | 71 ++++++ public/js/product.js | 267 +++++---------------- resources/views/products/create.blade.php | 33 +-- resources/views/products/edit.blade.php | 75 +++--- 5 files changed, 207 insertions(+), 272 deletions(-) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index b5f84a1..eabc1ef 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -111,7 +111,7 @@ public function edit(Product $product) $product->load('productColors'); $discounts = Discount::where('status', 'active')->get(); $images = ProductImage::where('product_id', $product->id)->get(); - $oldImages = $images->pluck('image')->toArray(); // Fetch only the image paths + $oldImages = ProductImage::where('product_id', $product->id)->get(); // dd($images); @@ -149,23 +149,9 @@ public function update(ProductRequest $request, Product $product) // Handle images $images = $request->file('images'); - // If new images are uploaded, add them to the existing ones - if ($images) { - foreach ($images as $image) { - if ($image && $image->isValid()) { - $path = $image->store('images/products', 'public'); - ProductImage::create([ - 'product_id' => $product->id, - 'image' => $path, - ]); - } - } - } - - // Handle image deletions if necessary - if ($request->has('deleted_images')) { - $deletedImages = explode(',', $request->input('deleted_images')[0]); + if ($request->has('deleted_images') && $request->input('deleted_images') !== '') { + $deletedImages = explode(',', $request->input('deleted_images')); foreach ($deletedImages as $deletedImage) { if (!empty($deletedImage)) { $productImage = ProductImage::find($deletedImage); @@ -179,6 +165,19 @@ public function update(ProductRequest $request, Product $product) } } + // If new images are uploaded, add them to the existing ones + if ($images) { + foreach ($images as $image) { + if ($image && $image->isValid()) { + $path = $image->store('images/products', 'public'); + ProductImage::create([ + 'product_id' => $product->id, + 'image' => $path, + ]); + } + } + } + // Handle discount if ($request->filled('selectedDiscountId')) { $product->discount_id = $request->input('selectedDiscountId'); diff --git a/public/css/forms-style.css b/public/css/forms-style.css index 84d1125..003a16f 100644 --- a/public/css/forms-style.css +++ b/public/css/forms-style.css @@ -126,3 +126,74 @@ .uploaded-image { object-fit: cover; } +.drag-drop-area { + border: 2px dashed #ccc; + padding: 10px; + width: 100%; + min-height: 300px; + position: relative; + text-align: center; + background-color: #f9f9f9; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + overflow-y: auto; +} + +.preview-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + width: 100%; +} + +.preview-container img { + max-width: 100%; + margin: auto; + max-height: 100px; + object-fit: cover; + border-radius: 5px; +} + +.image-preview .delete-btn { + position: relative; + top: 2px; + right: 2px; + background-color: red; + color: white; + border: none; + border-radius: 50%; + width: 20px; + height: 20px; + font-size: 12px; + cursor: pointer; + z-index: 10; +} + +/* for the edit blade */ + +.image-preview { + position: relative; + display: inline-block; +} + +.uploaded-image { + max-width: 100%; + margin: auto; + max-height: 100px; + object-fit: cover; + border-radius: 5px; +} + +.delete-btn { + position: absolute; + top: 5px; + right: 5px; + background-color: red; + color: white; + border: none; + border-radius: 50%; + padding: 5px; + cursor: pointer; +} \ No newline at end of file diff --git a/public/js/product.js b/public/js/product.js index 371c1fc..0c25990 100644 --- a/public/js/product.js +++ b/public/js/product.js @@ -147,217 +147,82 @@ document.addEventListener("DOMContentLoaded", function () { } }); - let imageSlotCount = 1; - const addImageBtn = document.getElementById("addImageBtn"); - const imageUploadsContainer = document.getElementById("image-uploads"); - - // Event listener for adding image slots - addImageBtn.addEventListener("click", addImageSlot); - - // Add initial event listener for the first image slot - setupImageSlotEventListeners(0); - - function setupImageSlotEventListeners(index) { - const fileInput = document.getElementById(`image_${index}`); - if (fileInput) { - fileInput.addEventListener("change", function () { - previewImage(this, index); - }); - } - - const deleteButton = document.getElementById(`delete_image_${index}`); - if (deleteButton) { - deleteButton.addEventListener("click", function () { - deleteImage(index); - }); - } - - const changeButton = document.getElementById(`change_image_${index}`); - if (changeButton) { - changeButton.addEventListener("click", function () { - changeImage(index); - }); - } - } - - function previewImage(input, index) { - const file = input.files[0]; - const previewImage = document.querySelector(`#image_preview_${index} img`); - const addImageBtn = document.getElementById("addImageBtn"); - - if (file) { - const reader = new FileReader(); - - reader.onload = function (e) { - if (previewImage) { // Ensure the preview image exists - previewImage.src = e.target.result; - previewImage.classList.remove("d-none"); - } - - // Enable the add image button when the image slot is populated - addImageBtn.disabled = false; - - // Define deleteButton and changeButton variables - const deleteButton = document.getElementById(`delete_image_${index}`); - const changeButton = document.getElementById(`change_image_${index}`); - - // Show the delete and change buttons for the image slot - if (deleteButton) { // Check if delete button exists - deleteButton.classList.remove("d-none"); - } - if (changeButton) { // Check if change button exists - changeButton.classList.remove("d-none"); - } - }; - - reader.readAsDataURL(file); + // images part + const dropArea = document.getElementById("dropArea"); + const fileElem = document.getElementById("fileElem"); + const previewContainer = document.getElementById("previewContainer"); + const maxImagesWarning = document.getElementById("maxImagesWarning"); + + let imageCount = 0; + const maxImages = 8; + + // Event listener for click to open file dialog + dropArea.addEventListener("click", () => { + if (imageCount < maxImages) { + fileElem.click(); } else { - // Reset when no file is selected - resetImageSlot(index); - } - } - - function resetImageSlot(index) { - const previewImage = document.querySelector( - `#image_preview_${index} img` - ); - if (previewImage) { - // Check if the image exists - previewImage.src = ""; - previewImage.classList.add("d-none"); - } - - const deleteButton = document.getElementById(`delete_image_${index}`); - if (deleteButton) { - // Check if the delete button exists - deleteButton.classList.add("d-none"); - } - - const changeButton = document.getElementById(`change_image_${index}`); - if (changeButton) { - // Check if the change button exists - changeButton.classList.add("d-none"); - } - - // Clear the file input value - const fileInput = document.getElementById(`image_${index}`); - if (fileInput) { - // Check if the file input exists - fileInput.value = ""; + maxImagesWarning.classList.remove("d-none"); } + }); - // Disable the add image button if no other slot is populated - checkImageSlots(); - } - - function deleteImage(index) { - resetImageSlot(index); - rearrangeImageSlots(); - checkImageSlots(); - } - - function rearrangeImageSlots() { - const imageUploads = document.getElementById("image-uploads"); - const slots = imageUploads.querySelectorAll(".mb-3"); - - // Create an array to hold new slots - let newSlots = []; - - // Loop through existing slots and rearrange them - slots.forEach((slot, currentIndex) => { - // Get the current file input and check if it's not empty - const fileInput = slot.querySelector('input[type="file"]'); - if (fileInput && fileInput.files.length > 0) { - // Update the IDs of the file input and its associated elements - const newIndex = newSlots.length; // New index based on the new array length - fileInput.id = `image_${newIndex}`; - const previewImage = slot.querySelector(".image-container img"); - previewImage.id = `image_preview_${newIndex}`; - slot.querySelector( - ".image-actions #change_image_" + currentIndex - ).id = `change_image_${newIndex}`; - slot.querySelector( - ".btn-danger" - ).id = `delete_image_${newIndex}`; - - newSlots.push(slot); - } - }); - - // Clear the existing slots and add the new slots - imageUploads.innerHTML = ""; - newSlots.forEach((slot) => imageUploads.appendChild(slot)); - - // Update the image slot count based on new slots - imageSlotCount = newSlots.length; + // Event listener for handling file input changes + fileElem.addEventListener("change", handleFiles); - // Ensure there's always at least one image slot - if (imageSlotCount === 0) { - addImageSlot(); // Call the function to add a new image slot - } + // Event listeners for drag and drop + dropArea.addEventListener("dragover", (event) => { + event.preventDefault(); + dropArea.classList.add("highlight"); + }); - // Re-setup event listeners for the remaining slots - newSlots.forEach((slot, index) => { - setupImageSlotEventListeners(index); - }); + dropArea.addEventListener("dragleave", () => { + dropArea.classList.remove("highlight"); + }); - // Enable or disable the add image button based on the number of populated slots - addImageBtn.disabled = imageSlotCount >= 8; - } + dropArea.addEventListener("drop", (event) => { + event.preventDefault(); + dropArea.classList.remove("highlight"); + const files = event.dataTransfer.files; + handleFiles({ target: { files } }); + }); - function changeImage(index) { - document.getElementById(`image_${index}`).click(); - } + function handleFiles(event) { + const files = event.target.files; + const totalFiles = files.length + imageCount; - function addImageSlot() { - if (imageSlotCount < 8) { - const newSlot = document.createElement("div"); - newSlot.classList.add("mb-3"); - newSlot.innerHTML = ` - <div class="image-upload-box"> - <input type="file" class="form-control" accept="image/*" name="images[]" id="image_${imageSlotCount}"> - <div class="image-container" id="image_preview_${imageSlotCount}"> - <img src="" class="uploaded-image d-none" alt="Preview Image"> - </div> - <div class="image-actions"> - <button type="button" class="btn btn-sm btn-secondary d-none m-1" id="change_image_${imageSlotCount}">Промени Ñлика</button> - </div> - </div> - <div class="mt-2"> - <button type="button" class="btn btn-sm btn-danger d-none m-1" id="delete_image_${imageSlotCount}">Избриши Ñлика</button> - </div> - `; - imageUploadsContainer.appendChild(newSlot); - setupImageSlotEventListeners(imageSlotCount); - imageSlotCount++; - - // Disable the add image button after adding a new empty slot - if (imageSlotCount >= 8) { - addImageBtn.disabled = true; - document - .getElementById("maxImagesWarning") - .classList.remove("d-none"); - } else { - addImageBtn.disabled = true; // Disable initially until an image is added - } + if (totalFiles > maxImages) { + maxImagesWarning.classList.remove("d-none"); + } else { + maxImagesWarning.classList.add("d-none"); } - } - function checkImageSlots() { - // Check if any of the image slots are populated - let isAnySlotPopulated = false; - for (let i = 0; i < imageSlotCount; i++) { - const fileInput = document.getElementById(`image_${i}`); - if (fileInput && fileInput.files.length > 0) { - isAnySlotPopulated = true; - break; + // Loop through files and add to preview + Array.from(files).forEach((file) => { + if (imageCount < maxImages && file.type.startsWith("image/")) { + const reader = new FileReader(); + reader.onload = function (e) { + const imgDiv = document.createElement("div"); + imgDiv.classList.add("image-preview"); + + const img = document.createElement("img"); + img.src = e.target.result; + + const deleteBtn = document.createElement("button"); + deleteBtn.innerHTML = "x"; + deleteBtn.classList.add("delete-btn"); + deleteBtn.addEventListener("click", (event) => { + event.stopPropagation(); + imgDiv.remove(); + imageCount--; + maxImagesWarning.classList.add("d-none"); + }); + + imgDiv.appendChild(img); + imgDiv.appendChild(deleteBtn); + previewContainer.appendChild(imgDiv); + }; + reader.readAsDataURL(file); + imageCount++; } - } - - // Enable or disable the add image button - addImageBtn.disabled = !isAnySlotPopulated; + }); } - - // Initially disable the add image button because no slots are populated - addImageBtn.disabled = true; }); diff --git a/resources/views/products/create.blade.php b/resources/views/products/create.blade.php index 94f09cb..91a2efa 100644 --- a/resources/views/products/create.blade.php +++ b/resources/views/products/create.blade.php @@ -94,32 +94,17 @@ <div class="mb-3"> <label for="images" class="form-label">Слики (МакÑимум 8)</label> - - <!-- Container for image slots --> - <div class="row" id="image-uploads"> - <div class="mb-3"> - <div class="image-upload-box"> - <input type="file" class="form-control" accept="image/*" name="images[]" id="image_0"> - <div class="image-container" id="image_preview_0"> - <img src="" class="uploaded-image d-none" alt="Preview Image"> - </div> - <div class="image-actions"> - <button type="button" class="btn btn-sm btn-secondary d-none m-1" id="change_image_0">Промени Ñлика</button> - </div> - </div> - <!-- Delete image button outside the slot --> - <div class="mt-2"> - <button type="button" class="btn btn-sm btn-danger d-none m-1" id="delete_image_0">Избриши Ñлика</button> - </div> + + <!-- Drag and Drop Area --> + <div id="dropArea" class="drag-drop-area"> + <p>Drag & Drop your images here or click to upload (Max 8)</p> + <!-- Container for the image previews --> + <div id="previewContainer" class="preview-container"> + <!-- Image previews will be injected here --> </div> </div> - - <!-- Hidden input to track deleted images --> - <input type="hidden" id="deleted_images" name="deleted_images[]"> - - <!-- Button to add more image slots --> - <button type="button" class="btn btn-secondary mt-2" id="addImageBtn">Додај уште една Ñлика</button> - <p id="maxImagesWarning" class="text-danger d-none">Можете да додадете макÑимум 8 Ñлики.</p> + <input type="file" id="fileElem" name="images[]" multiple accept="image/*" style="display:none;"> + <p id="maxImagesWarning" class="text-danger d-none">You can upload a maximum of 8 images.</p> </div> {{-- @if ($errors->has('images.' . $i)) <span class="text-danger">{{ $errors->first('images.' . $i) }}</span> diff --git a/resources/views/products/edit.blade.php b/resources/views/products/edit.blade.php index bca376e..0680c4e 100644 --- a/resources/views/products/edit.blade.php +++ b/resources/views/products/edit.blade.php @@ -120,37 +120,24 @@ <div class="mb-3"> <label for="images" class="form-label">Слики (МакÑимум 8)</label> - <!-- Container for image slots --> - <div class="row" id="image-uploads"> - @foreach ($oldImages as $index => $image) - <div class="mb-3"> - <div class="image-upload-box"> - <input type="file" class="form-control" accept="image/*" name="images[]" id="image_{{ $index }}"> - <div class="image-container" id="image_preview_{{ $index }}"> - <img src="{{ asset('storage/' . $image) }}" class="uploaded-image" alt="Uploaded image"> - </div> - <div class="image-actions"> - <button type="button" class="btn btn-sm btn-secondary d-none m-1" id="change_image_{{ $index }}">Промени Ñлика</button> - </div> + <!-- Drag and Drop Area --> + <div id="dropArea" class="drag-drop-area"> + <p>Драг & Дроп вашите Ñлики тука или кликнете за да отпратите (МакÑимум 8)</p> + + <!-- Container for the image previews --> + <div id="previewContainer" class="preview-container"> + @foreach ($oldImages as $image) + <div class="image-preview"> + <img src="{{ asset('storage/' . $image->image) }}" class="uploaded-image" alt="Uploaded image"> + <button type="button" class="delete-btn" data-image-id="{{ $image->id }}">x</button> <!-- Delete button --> </div> - <!-- Delete image button outside the slot --> - <div class="mt-2"> - <button type="button" class="btn btn-sm btn-danger" id="delete_image_{{ $index }}">Избриши Ñлика</button> - </div> - </div> - @endforeach + @endforeach + </div> </div> - - <!-- Hidden input to track deleted images --> - <input type="hidden" id="deleted_images" name="deleted_images[]"> - - <!-- Button to add more image slots --> - <button type="button" class="btn btn-secondary mt-2" id="addImageBtn" >Додај уште една Ñлика</button> - <p id="maxImagesWarning" class="text-danger d-none">Можете да додадете макÑимум 8 Ñлики.</p> - </div> + + <input type="file" id="fileElem" name="images[]" multiple accept="image/*" style="display:none;"> + <p id="maxImagesWarning" class="text-danger d-none">Можете да отпратите макÑимум 8 Ñлики.</p> </div> - </div> - <div class="row mb-3"> <div class="col-md-6"> <div class="mb-3"> @@ -231,6 +218,7 @@ <script> document.addEventListener('DOMContentLoaded', function () { + const addInputBtn = document.getElementById('addInputBtn'); const dynamicInputsContainer = document.getElementById('dynamicInputs'); let inputIndex = {{ count($product->productColors) }}; @@ -276,7 +264,34 @@ event.target.closest('.input-group').remove(); } }); + + document.querySelectorAll('.delete-btn').forEach(button => { + button.addEventListener('click', (event) => { + event.stopPropagation(); // Prevent drag-and-drop event from firing + button.parentElement.remove(); // Remove the image preview + }); }); - </script> - + + const deletedImagesInput = document.createElement('input'); + deletedImagesInput.type = 'hidden'; + deletedImagesInput.name = 'deleted_images'; + document.querySelector('form').appendChild(deletedImagesInput); + + const deletedImages = []; + + document.querySelectorAll('.delete-btn').forEach(button => { + button.addEventListener('click', function () { + const imageId = button.getAttribute('data-image-id'); + + if (imageId) { + deletedImages.push(imageId); // Add the image ID to the array + deletedImagesInput.value = deletedImages.join(','); // Store in hidden input + button.closest('.image-preview').remove(); // Remove the preview + } + }); + }); + + + }); +</script> @endsection -- GitLab From a34e5769d10d1e98ac745525958cfb1f7b7d1c50 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Tue, 15 Oct 2024 23:56:00 +0200 Subject: [PATCH 11/16] debugged some issues with the images, still have one more at the edit blade when drag and drop new images... must resolve in upcomming days --- app/Http/Controllers/BrandController.php | 67 +++++++++-------- app/Http/Controllers/DiscountController.php | 4 +- app/Http/Controllers/ProductController.php | 30 ++++---- app/Http/Controllers/UserController.php | 8 +-- app/Http/Requests/BrandRequest.php | 11 +-- public/css/admin-login.css | 13 ++-- public/css/app.css | 4 +- public/css/forms-style.css | 64 ++--------------- public/js/discount.js | 34 +++++++++ public/js/product.js | 38 +++------- resources/views/brands/create.blade.php | 27 ++++--- resources/views/brands/edit.blade.php | 71 +++++++++---------- resources/views/discounts/create.blade.php | 4 ++ resources/views/discounts/edit.blade.php | 6 +- resources/views/layouts/app.blade.php | 4 +- resources/views/layouts/create-edit.blade.php | 3 +- resources/views/layouts/navbar.blade.php | 3 - resources/views/products/create.blade.php | 15 ++-- resources/views/products/edit.blade.php | 23 ++++-- resources/views/users/edit.blade.php | 6 -- 20 files changed, 200 insertions(+), 235 deletions(-) diff --git a/app/Http/Controllers/BrandController.php b/app/Http/Controllers/BrandController.php index 9ba4120..bb7b81c 100644 --- a/app/Http/Controllers/BrandController.php +++ b/app/Http/Controllers/BrandController.php @@ -9,6 +9,7 @@ use Illuminate\Http\Request; use App\Http\Requests\BrandRequest; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; class BrandController extends Controller { @@ -45,23 +46,24 @@ public function store(BrandRequest $request) 'name' => $validatedData['name'], 'description' => $validatedData['description'], 'tags' => $validatedData['tags'], + 'status' => $validatedData['status'] ?? 'active', ]); + if ($request->hasFile('image') && $request->file('image')->isValid()) { + $path = $request->file('image')->store('images/brands', 'public'); + BrandImage::create([ + 'brand_id' => $brand->id, + 'image' => $path, + ]); + } + if ($request->has('categories')) { $brand->categories()->sync($validatedData['categories']); } - $images = $request->file('images'); - if ($images) { - foreach ($images as $image) { - if ($image && $image->isValid()) { - $path = $image->store('images/brands', 'public'); - BrandImage::create([ - 'brand_id' => $brand->id, - 'image' => $path, - ]); - } - } + if ($request->filled('selectedDiscountId')) { + $discountId = $request->input('selectedDiscountId'); + $brand->products()->update(['discount_id' => $discountId]); } return redirect()->route('brands.index')->with('success', 'Брендот е уÑпешно креиран'); @@ -97,31 +99,34 @@ public function update(BrandRequest $request, Brand $brand) ]); if ($request->has('categories')) { - $brand->categories()->sync($validatedData['categories']); - } else { - $brand->categories()->detach(); - } + $categories = $validatedData['categories']; - $images = $request->file('images'); - - if ($images) { - $brand->images()->delete(); - - foreach ($images as $image) { - if ($image && $image->isValid()) { - $path = $image->store('images/brands', 'public'); - BrandImage::create([ - 'brand_id' => $brand->id, - 'image' => $path, - ]); - } else { - $error = $image->getError(); - Log::error("File upload error: $error"); - return redirect()->back()->with('error', 'Error uploading file: ' . $error); + if (!empty($categories)) { + // Ensure no out-of-bounds access + if (count($categories) > 5) { + // Handle or log this condition if necessary } + + $brand->categories()->sync($categories); + } else { + $brand->categories()->detach(); } } + if ($request->hasFile('image') && $request->file('image')->isValid()) { + if ($brand->images()->exists()) { + $oldImage = $brand->images()->first(); + Storage::disk('public')->delete($oldImage->image); + $oldImage->delete(); + } + + $path = $request->file('image')->store('images/brands', 'public'); + BrandImage::create([ + 'brand_id' => $brand->id, + 'image' => $path, + ]); + } + $selectedDiscountId = $request->input('selectedDiscountId'); if ($selectedDiscountId) { diff --git a/app/Http/Controllers/DiscountController.php b/app/Http/Controllers/DiscountController.php index d79b4b5..bad2b36 100644 --- a/app/Http/Controllers/DiscountController.php +++ b/app/Http/Controllers/DiscountController.php @@ -46,14 +46,14 @@ public function store(DiscountRequest $request) 'status' => 'active', ]; - //ако е Ñелектиран ÑтатуÑ, да го земе + //to get the status if selected if ($request->has('status')) { $discountData['status'] = $request->input('status'); } $discount = Discount::create($discountData); - //за аплицирање на попуÑÑ‚ на одредени продукти + //for apply discounts on targeted products with #1,#2... if ($request->filled('products')) { $productIds = explode('#', $request->input('products')); diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index eabc1ef..1421b23 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -12,6 +12,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use App\Http\Requests\ProductRequest; +use Illuminate\Auth\Events\Validated; use Illuminate\Support\Facades\Storage; class ProductController extends Controller @@ -46,15 +47,11 @@ public function store(ProductRequest $request) { $validated = $request->validated(); - Log::info('Request Data:', $request->all()); $product = Product::create($validated); $colorsAndSizes = $request->input('colors_and_sizes', []); - // за дебагирање - Log::info('Colors and Sizes Data:', $colorsAndSizes); - foreach ($colorsAndSizes as $colorAndSize) { if (isset($colorAndSize['size_id']) && isset($colorAndSize['color']) && isset($colorAndSize['stock'])) { try { @@ -113,8 +110,6 @@ public function edit(Product $product) $images = ProductImage::where('product_id', $product->id)->get(); $oldImages = ProductImage::where('product_id', $product->id)->get(); - // dd($images); - $productColors = $product->productColors->pluck('color_name')->map(function ($color) { return trim($color); })->toArray(); @@ -128,14 +123,16 @@ public function edit(Product $product) public function update(ProductRequest $request, Product $product) { $validated = $request->validated(); + // dd($validated); + Log::info('Received files:', $request->file('images') ?? []); + - // Update basic product information $product->update($validated); - // Handle colors and sizes + // handle colors and sizes $colorsAndSizes = $request->input('colors_and_sizes', []); - // Delete old colors and sizes, then recreate them + // delete old colors and sizes, then recreate them ProductColor::where('product_id', $product->id)->delete(); foreach ($colorsAndSizes as $colorAndSize) { @@ -147,38 +144,41 @@ public function update(ProductRequest $request, Product $product) ]); } - // Handle images $images = $request->file('images'); - // If new images are uploaded, add them to the existing ones + if ($request->has('deleted_images') && $request->input('deleted_images') !== '') { $deletedImages = explode(',', $request->input('deleted_images')); + // debugging purposes + Log::info('Deleting images:', $deletedImages); foreach ($deletedImages as $deletedImage) { if (!empty($deletedImage)) { $productImage = ProductImage::find($deletedImage); if ($productImage) { - // Delete the image file from storage Storage::disk('public')->delete($productImage->image); - // Delete the record from the database $productImage->delete(); } } } } - // If new images are uploaded, add them to the existing ones if ($images) { + Log::info('Uploading new images:', $images); foreach ($images as $image) { if ($image && $image->isValid()) { $path = $image->store('images/products', 'public'); + // debugging purposes + Log::info('Stored image:', ['path' => $path]); ProductImage::create([ 'product_id' => $product->id, 'image' => $path, ]); + } else { + Log::warning('Invalid image:', $image); } } } - // Handle discount + // handle discount if ($request->filled('selectedDiscountId')) { $product->discount_id = $request->input('selectedDiscountId'); } else { diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 0abfa25..a7fb3a5 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -40,16 +40,11 @@ public function update(UserRequest $request, $id) $user->email = $request->email; $user->phone_number = $request->phone_number; - //за промена на паÑворд - //прво проверка дали барем 1 инпут е пополнет + //changing password if ($request->filled('current-password') || $request->filled('new-password') || $request->filled('password-repeat')) { - // второ проверка дали Ñите 3 Ñе пополнети if ($request->filled('current-password') && $request->filled('new-password') && $request->filled('password-repeat')) { - // ако да, проверка дали тековна лозинка одговара Ñо лозинка во датабаза if (Hash::check($request->input('current-password'), $user->password)) { - // ако да, проверка дали нова лозинка одговара Ñо повтори лозинка if ($request->input('new-password') === $request->input('password-repeat')) { - // ако и ова да, Ñмени лозинка во датабаза $user->password = Hash::make($request->input('new-password')); $user->save(); } else { @@ -62,7 +57,6 @@ public function update(UserRequest $request, $id) return back()->with('error-password-change', 'Сите три полиња треба да бидат пополнети за да Ñе Ñмени лозинката.'); } } else { - //ако ниеден од промена на паÑворд не е пополнет, продолжи Ñо другите инпути (телефон, име, меил) $user->save(); } return redirect()->route('users.edit')->with('success', 'Следните креденцијали важат од веднаш.'); diff --git a/app/Http/Requests/BrandRequest.php b/app/Http/Requests/BrandRequest.php index 35c162d..0fa1da0 100644 --- a/app/Http/Requests/BrandRequest.php +++ b/app/Http/Requests/BrandRequest.php @@ -27,11 +27,15 @@ public function rules(): array 'tags' => 'nullable|string|max:255', 'images.*' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:6048', 'categories' => 'required|array|min:1', - 'categories.*' => 'exists:categories,id', ]; - - // името да е уникатно Ñамо кога креираме ноњ продукт + 'categories.*' => 'exists:categories,id', + ]; + + // the name should be unique only when we create the new brand if ($this->isMethod('post')) { $rules['name'] .= '|unique:brands'; + } elseif ($this->isMethod('put') || $this->isMethod('patch')) { + // for update, check for uniqueness excluding the current brand + $rules['name'] .= '|unique:brands,name,' . $this->brand->id; } return $rules; @@ -56,5 +60,4 @@ public function messages() 'categories.*.exists' => 'Категоријата не поÑтои во ÑиÑтемот', ]; } - } diff --git a/public/css/admin-login.css b/public/css/admin-login.css index 3d690d3..692ce80 100644 --- a/public/css/admin-login.css +++ b/public/css/admin-login.css @@ -3,26 +3,25 @@ body { background: linear-gradient(to bottom, #f7c6ef, #ffffff); } -p.igralishte{ +p.igralishte { font-weight: bold; } label, -footer{ +footer { font-weight: bold; } -input.form-control{ +input.form-control { height: 2.95em; background: inherit; border: 1px solid grey; } -div.forgot a{ - color: #8A8328; +div.forgot a { + color: #8a8328; } -img{ +img { max-width: 100%; } - diff --git a/public/css/app.css b/public/css/app.css index 329f5cb..9c0c05a 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -36,10 +36,9 @@ .background-darker { ul { padding-left: 0 !important; - /* margin: 0; */ } -/* Sidebar */ +/* sidebar */ .sidebar { position: fixed; z-index: 999; @@ -174,6 +173,7 @@ .sidebar li a { .sidebar li a:hover { background: var(--vintage-pink-light); } + .sidebar li a:hover .icon, .sidebar li a:hover .text { color: var(--text-color-light); diff --git a/public/css/forms-style.css b/public/css/forms-style.css index 003a16f..90f767a 100644 --- a/public/css/forms-style.css +++ b/public/css/forms-style.css @@ -72,60 +72,6 @@ .color-checkbox.checked { border: 5px double; } -/* делот Ñо Ñликите */ -.image-grid { - display: flex; - gap: 10px; -} - -.image-upload-box { - position: relative; - width: calc(25% - 10px); - padding-top: calc(25% - 1px); - padding-bottom: 14px; - border: 2px solid #ccc; - cursor: pointer; - overflow: hidden; -} - -.image-upload-box input[type="file"] { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 0; - cursor: pointer; - z-index: 1; -} - -.image-upload-box i { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-size: 24px; -} - -.image-container { - position: relative; - width: 100%; - padding-top: 100%; - overflow: hidden; - background-color: #f0f0f0; - margin-top: -100%; - z-index: 0; -} - -.uploaded-image { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; -} - .drag-drop-area { border: 2px dashed #ccc; padding: 10px; @@ -172,7 +118,6 @@ .image-preview .delete-btn { } /* for the edit blade */ - .image-preview { position: relative; display: inline-block; @@ -181,7 +126,7 @@ .image-preview { .uploaded-image { max-width: 100%; margin: auto; - max-height: 100px; + height: 100px; object-fit: cover; border-radius: 5px; } @@ -190,10 +135,13 @@ .delete-btn { position: absolute; top: 5px; right: 5px; - background-color: red; + background-color: rgba(38, 39, 39, 0.562); color: white; border: none; border-radius: 50%; padding: 5px; cursor: pointer; -} \ No newline at end of file + display: flex; + justify-content: center; + align-items: center; +} diff --git a/public/js/discount.js b/public/js/discount.js index 1b49ff9..ee7d388 100644 --- a/public/js/discount.js +++ b/public/js/discount.js @@ -17,4 +17,38 @@ document.addEventListener("DOMContentLoaded", function () { document.getElementById("name").focus(); }); } + + //модалот за попуÑÑ‚ + var selectDiscountBtn = document.getElementById("selectDiscountBtn"); + var discountList = document.getElementById("discountList"); + var alreadyAppliedDiscount = document.getElementById( + "alreadyAppliedDiscount" + ); + var newAppliedDiscount = document.getElementById("newAppliedDiscount"); + + selectDiscountBtn.addEventListener("click", function () { + var selectedDiscountRadio = discountList.querySelector( + 'input[type="radio"]:checked' + ); + + if (selectedDiscountRadio) { + var discountId = selectedDiscountRadio.value; + var discountName = selectedDiscountRadio.parentElement + .querySelector("label") + .textContent.trim(); + + document.getElementById("selectedDiscountId").value = discountId; + + console.log("Selected discount ID:", discountId); + newAppliedDiscount.textContent = + "ПопуÑтот '" + + discountName + + "' ќе биде аплициран на овој продукт"; + if (alreadyAppliedDiscount) { + alreadyAppliedDiscount.textContent = ""; + } + } else { + alert("Please select a discount."); + } + }); }); diff --git a/public/js/product.js b/public/js/product.js index 0c25990..145c688 100644 --- a/public/js/product.js +++ b/public/js/product.js @@ -1,27 +1,7 @@ document.addEventListener("DOMContentLoaded", function () { - // плуÑ/Ð¼Ð¸Ð½ÑƒÑ ÐºÐ¾Ð¿Ñ‡Ðµ кај количина на продукт + // +/- buttons for quantity/size/color document.getElementById("name").focus(); - // делот Ñо 4-те Ñлики - // document - // .querySelectorAll('.image-upload-box input[type="file"]') - // .forEach(function (input) { - // input.addEventListener("change", function () { - // const reader = new FileReader(); - // const imageContainer = this.parentNode.querySelector( - // ".image-container img" - // ); - - // reader.onload = function (e) { - // imageContainer.src = e.target.result; - // }; - // input.parentElement.querySelector( - // ".image-container + i" - // ).style.display = "none"; - - // reader.readAsDataURL(this.files[0]); - // }); - // }); const addInputBtn = document.getElementById("addInputBtn"); const dynamicInputsContainer = document.getElementById("dynamicInputs"); let inputIndex = 0; @@ -64,7 +44,7 @@ document.addEventListener("DOMContentLoaded", function () { inputIndex++; }); - //копчето -откажи- на крајо од формата + //cancel button at the end of the form const cancelButton = document.getElementById("cancel"); if (cancelButton) { cancelButton.addEventListener("click", function () { @@ -113,7 +93,7 @@ document.addEventListener("DOMContentLoaded", function () { }); } - //модалот за попуÑÑ‚ + //discount modal var selectDiscountBtn = document.getElementById("selectDiscountBtn"); var discountList = document.getElementById("discountList"); var alreadyAppliedDiscount = document.getElementById( @@ -153,10 +133,10 @@ document.addEventListener("DOMContentLoaded", function () { const previewContainer = document.getElementById("previewContainer"); const maxImagesWarning = document.getElementById("maxImagesWarning"); - let imageCount = 0; + let imageCount = previewContainer.querySelectorAll("img").length; // Count existing images const maxImages = 8; - // Event listener for click to open file dialog + // event listener for click to open file dialog dropArea.addEventListener("click", () => { if (imageCount < maxImages) { fileElem.click(); @@ -165,10 +145,10 @@ document.addEventListener("DOMContentLoaded", function () { } }); - // Event listener for handling file input changes + // event listener for handling file input changes fileElem.addEventListener("change", handleFiles); - // Event listeners for drag and drop + // event listeners for drag and drop dropArea.addEventListener("dragover", (event) => { event.preventDefault(); dropArea.classList.add("highlight"); @@ -195,8 +175,10 @@ document.addEventListener("DOMContentLoaded", function () { maxImagesWarning.classList.add("d-none"); } - // Loop through files and add to preview + // loop through files and add to preview Array.from(files).forEach((file) => { + console.log("File dragged and dropped:", file); + if (imageCount < maxImages && file.type.startsWith("image/")) { const reader = new FileReader(); reader.onload = function (e) { diff --git a/resources/views/brands/create.blade.php b/resources/views/brands/create.blade.php index b81c2cf..5a93ae1 100644 --- a/resources/views/brands/create.blade.php +++ b/resources/views/brands/create.blade.php @@ -47,20 +47,13 @@ @enderror </div> - <div> - <label for="images" class="form-label">Слики (МакÑимум 4)</label> - <div class="image-grid"> - @for ($i = 0; $i < 4; $i++) - <div class="image-upload-box"> - <input type="file" class="form-control" accept="image/*" name="images[]"> - <div class="image-container"> - <img class="uploaded-image" src=""> - </div> - <i class="fas fa-plus"></i> - </div> - @endfor - </div> - + <div class="mb-3"> + <label for="image" class="form-label">Слика на бренд</label> + <input type="file" class="form-control" id="image" name="image" accept="image/*"> + <small class="form-text text-muted">Една Ñлика е дозволена.</small> + @error('image') + <span class="text-danger">{{ $message }}</span> + @enderror </div> <div class="my-5"> @@ -124,4 +117,8 @@ </div> </div> </div> -@endsection \ No newline at end of file +@endsection + +@push('scripts') + <script src="{{ asset('js/discount.js') }}"></script> +@endpush \ No newline at end of file diff --git a/resources/views/brands/edit.blade.php b/resources/views/brands/edit.blade.php index fd54020..ace5480 100644 --- a/resources/views/brands/edit.blade.php +++ b/resources/views/brands/edit.blade.php @@ -49,27 +49,15 @@ </div> <div class="mb-3"> - <label for="images" class="form-label">Слики (МакÑимум 4)</label> - <div class="image-grid"> - @foreach ($brand->images as $image) - <div class="image-upload-box"> - <input type="file" class="form-control" accept="image/*" name="images[]"> - <div class="image-container"> - <img src="{{ asset('storage/' . $image->image) }}" class="uploaded-image"> - </div> - </div> - @endforeach - @for ($i = count($brand->images); $i < 4; $i++) - <div class="image-upload-box"> - <input type="file" class="form-control" accept="image/*" name="images[]"> - <div class="image-container"> - <img class="uploaded-image" src=""> - </div> - <i class="fas fa-plus"></i> - </div> - @endfor - </div> - @error('images.*') + <label for="image" class="form-label">Слика на бренд</label> + @if($brand->images->isNotEmpty()) + <div class="mb-3"> + <img src="{{ asset('storage/' . $brand->images->first()->image) }}" alt="Brand Image" class="img-thumbnail" style="max-width: 200px;"> + </div> + @endif + <input type="file" class="form-control" id="image" name="image" accept="image/*"> + <small class="form-text text-muted">Upload a new image to replace the current one.</small> + @error('image') <span class="text-danger">{{ $message }}</span> @enderror </div> @@ -85,42 +73,45 @@ </div> <div class="my-5"> - {{-- проверка дали Ñите продукти од овој бренд имат аплицирано попуÑÑ‚ --}} + {{-- Check if there are products for this brand --}} @if(!$brand->products->isEmpty()) @php $firstDiscountId = null; - $discountApplied = true; - + $allSameDiscount = true; + $noDiscountApplied = true; + foreach($brand->products as $product) { if(!$product->discount_id) { - $discountApplied = false; - break; - } - - if($firstDiscountId === null) { - $firstDiscountId = $product->discount_id; - } elseif($firstDiscountId !== $product->discount_id) { - $discountApplied = false; - break; + $allSameDiscount = false; // Not all products have a discount + } else { + $noDiscountApplied = false; // At least one product has a discount + if($firstDiscountId === null) { + $firstDiscountId = $product->discount_id; // Set the first discount ID + } elseif($firstDiscountId !== $product->discount_id) { + $allSameDiscount = false; // Discounts differ between products + } } } @endphp - - @if($discountApplied) - <p id="alreadyAppliedDiscount" class="font-weight-bold">ПопуÑÑ‚ аплициран на продуктите од овој Бренд: "{{ $brand->products->first()->discount->name }}"</p> - @else + + @if($noDiscountApplied) <p id="alreadyAppliedDiscount" class="font-weight-bold">Ðема аплицирано попуÑÑ‚ на продуктите од овој бренд.</p> + @elseif($allSameDiscount) + <p id="alreadyAppliedDiscount" class="font-weight-bold">ПопуÑÑ‚ аплициран на продуктите од овој бренд: "{{ $brand->products->first()->discount->name }}"</p> + @else + <p id="alreadyAppliedDiscount" class="font-weight-bold">Различни попуÑти Ñе аплицирани на производите од овој бренд.</p> @endif @else <p id="alreadyAppliedDiscount" class="font-weight-bold">Сеуште нема продукти од овој бренд.</p> @endif + <label for="discount" class="form-label">Додај ПопуÑÑ‚</label> <button type="button" class="m-2 btn btn-secondary background-dark-green" data-bs-toggle="modal" data-bs-target="#discountModal"> <i class="fa-solid fa-plus"></i> </button> </div> - <!-- модал за попуÑÑ‚ --> + <!-- modal for the discount --> <input type="hidden" id="selectedDiscountId" name="selectedDiscountId" value=""> <div class="modal fade" id="discountModal" tabindex="-1" aria-labelledby="discountModalLabel" aria-hidden="true"> @@ -161,3 +152,7 @@ </div> </div> @endsection + +@push('scripts') + <script src="{{ asset('js/discount.js') }}"></script> +@endpush \ No newline at end of file diff --git a/resources/views/discounts/create.blade.php b/resources/views/discounts/create.blade.php index 89319fb..26a11c6 100644 --- a/resources/views/discounts/create.blade.php +++ b/resources/views/discounts/create.blade.php @@ -69,3 +69,7 @@ </div> </div> @endsection + +@push('scripts') + <script src="{{ asset('js/discount.js') }}"></script> +@endpush \ No newline at end of file diff --git a/resources/views/discounts/edit.blade.php b/resources/views/discounts/edit.blade.php index 6eb9ef9..6db2589 100644 --- a/resources/views/discounts/edit.blade.php +++ b/resources/views/discounts/edit.blade.php @@ -70,4 +70,8 @@ </div> </div> </div> -@endsection \ No newline at end of file +@endsection + +@push('scripts') + <script src="{{ asset('js/discount.js') }}"></script> +@endpush \ No newline at end of file diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 33bde58..bd08a27 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -35,10 +35,10 @@ @yield('content') </div> - <script src="{{ asset('js/script.js') }}"></script> - <script src="{{ asset('js/profile.js') }}"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> + <script src="{{ asset('js/script.js') }}"></script> + <script src="{{ asset('js/profile.js') }}"></script> </body> </html> diff --git a/resources/views/layouts/create-edit.blade.php b/resources/views/layouts/create-edit.blade.php index 8970f15..02360e9 100644 --- a/resources/views/layouts/create-edit.blade.php +++ b/resources/views/layouts/create-edit.blade.php @@ -27,8 +27,7 @@ <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> - <script src="{{ asset('js/product.js') }}"></script> - <script src="{{ asset('js/discount.js') }}"></script> + @stack('scripts') </body> </html> \ No newline at end of file diff --git a/resources/views/layouts/navbar.blade.php b/resources/views/layouts/navbar.blade.php index a5b92cd..de82484 100644 --- a/resources/views/layouts/navbar.blade.php +++ b/resources/views/layouts/navbar.blade.php @@ -76,9 +76,6 @@ </div> </nav> -{{-- <section class="home"> - <div class="text">Dashboard</div> -</section> --}} diff --git a/resources/views/products/create.blade.php b/resources/views/products/create.blade.php index 91a2efa..10828e7 100644 --- a/resources/views/products/create.blade.php +++ b/resources/views/products/create.blade.php @@ -5,7 +5,7 @@ <div class="row"> <div class="col-md-8 offset-md-2"> - {{-- валидациÑки ерори: --}} + {{-- validation errors --}} @if ($errors->any()) <div class="alert alert-danger"> <ul> @@ -63,7 +63,7 @@ <div class="mb-3"> <label for="dynamicInputs" class="form-label">Величина, Боја, Количина</label> <div id="dynamicInputs"> - <!-- динамични инпути за величина/боја/парчиња на залиха --> + <!-- dinamic inputs for size/color/quantity --> </div> <button type="button" id="addInputBtn" class="btn btn-secondary mt-2">Додај</button> </div> @@ -97,7 +97,7 @@ <!-- Drag and Drop Area --> <div id="dropArea" class="drag-drop-area"> - <p>Drag & Drop your images here or click to upload (Max 8)</p> + <p>МеÑто за Ñлики за вашиот производ (Max 8)</p> <!-- Container for the image previews --> <div id="previewContainer" class="preview-container"> <!-- Image previews will be injected here --> @@ -106,9 +106,6 @@ <input type="file" id="fileElem" name="images[]" multiple accept="image/*" style="display:none;"> <p id="maxImagesWarning" class="text-danger d-none">You can upload a maximum of 8 images.</p> </div> - {{-- @if ($errors->has('images.' . $i)) - <span class="text-danger">{{ $errors->first('images.' . $i) }}</span> - @endif --}} </div> </div> @@ -149,7 +146,7 @@ </button> </div> - <!-- модал за попуÑÑ‚ --> + <!-- discount modal --> <input type="hidden" id="selectedDiscountId" name="selectedDiscountId" value=""> <div class="modal fade" id="discountModal" tabindex="-1" aria-labelledby="discountModalLabel" aria-hidden="true"> @@ -191,5 +188,9 @@ </div> @endsection +@push('scripts') + <script src="{{ asset('js/product.js') }}"></script> +@endpush + diff --git a/resources/views/products/edit.blade.php b/resources/views/products/edit.blade.php index 0680c4e..6e2bc23 100644 --- a/resources/views/products/edit.blade.php +++ b/resources/views/products/edit.blade.php @@ -5,7 +5,7 @@ <div class="row"> <div class="col-md-8 offset-md-2"> - {{-- валидациÑки ерори: --}} + {{-- validation errors --}} @if ($errors->any()) <div class="alert alert-danger"> <ul> @@ -120,23 +120,23 @@ <div class="mb-3"> <label for="images" class="form-label">Слики (МакÑимум 8)</label> - <!-- Drag and Drop Area --> + <!-- drag and Drop Area --> <div id="dropArea" class="drag-drop-area"> - <p>Драг & Дроп вашите Ñлики тука или кликнете за да отпратите (МакÑимум 8)</p> + <p>МеÑто за Ñлики за вашиот производ (Max 8)</p> - <!-- Container for the image previews --> + <!-- container for the image previews --> <div id="previewContainer" class="preview-container"> @foreach ($oldImages as $image) <div class="image-preview"> <img src="{{ asset('storage/' . $image->image) }}" class="uploaded-image" alt="Uploaded image"> - <button type="button" class="delete-btn" data-image-id="{{ $image->id }}">x</button> <!-- Delete button --> + <button type="button" class="delete-btn" data-image-id="{{ $image->id }}">x</button> </div> @endforeach </div> </div> <input type="file" id="fileElem" name="images[]" multiple accept="image/*" style="display:none;"> - <p id="maxImagesWarning" class="text-danger d-none">Можете да отпратите макÑимум 8 Ñлики.</p> + <p id="maxImagesWarning" class="text-danger d-none">Можете да закачите макÑимум 8 Ñлики.</p> </div> <div class="row mb-3"> <div class="col-md-6"> @@ -173,9 +173,15 @@ <button type="button" class="m-2 btn btn-secondary background-dark-green" data-bs-toggle="modal" data-bs-target="#discountModal"> <i class="fa-solid fa-plus"></i> </button> + <!-- display current applied discount --> + @if($product->discount) + <p id="currentDiscountInfo" class="text-info"> + Тековно избран попуÑÑ‚: <strong>{{ $product->discount->name }} - {{$product->discount->percentage }}%</strong> + </p> + @endif </div> - <!-- модал за попуÑÑ‚ --> + <!-- modal for discount --> <input type="hidden" id="selectedDiscountId" name="selectedDiscountId" value=""> <div class="modal fade" id="discountModal" tabindex="-1" aria-labelledby="discountModalLabel" aria-hidden="true"> @@ -295,3 +301,6 @@ }); </script> @endsection +@push('scripts') + <script src="{{ asset('js/product.js') }}"></script> +@endpush \ No newline at end of file diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index c97a5bd..751521c 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -18,12 +18,6 @@ @csrf @method('PUT') - @if(session('success')) - <div class="alert alert-success w-75 mx-auto text-center"> - {{ session('success') }} - </div> - @endif - <div class="row mb-3"> <div class="col d-flex align-center"> <a href="{{ route('products.index') }}" class="text-secondary"><i class="fa-solid fa-left-long fa-2x"></i></a> -- GitLab From 1bc74ea6f2e5efd59186cd002d0961c413d58d82 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Wed, 16 Oct 2024 22:52:35 +0200 Subject: [PATCH 12/16] resolved the problem with the images --- app/Http/Controllers/ProductController.php | 7 +- public/js/product.js | 199 +++++++++++++++------ 2 files changed, 151 insertions(+), 55 deletions(-) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 1421b23..2447ca5 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -187,6 +187,9 @@ public function update(ProductRequest $request, Product $product) $product->save(); - return redirect()->route('products.index')->with('success', 'Продуктот е уÑпешно ажуриран!'); - } + return response()->json([ + 'success' => true, + 'message' => 'Продуктот е уÑпешно ажуриран!', + 'redirect_url' => route('products.index') // Provide the redirect URL if needed + ]); } } diff --git a/public/js/product.js b/public/js/product.js index 145c688..b864356 100644 --- a/public/js/product.js +++ b/public/js/product.js @@ -133,78 +133,171 @@ document.addEventListener("DOMContentLoaded", function () { const previewContainer = document.getElementById("previewContainer"); const maxImagesWarning = document.getElementById("maxImagesWarning"); - let imageCount = previewContainer.querySelectorAll("img").length; // Count existing images + // Count existing images + let imageCount = previewContainer.querySelectorAll("img").length; const maxImages = 8; + const uploadedFiles = []; + + // check which form is present + const createForm = document.getElementById("create_product"); + const editForm = document.getElementById("edit_product"); + const form = createForm || editForm; // event listener for click to open file dialog - dropArea.addEventListener("click", () => { - if (imageCount < maxImages) { - fileElem.click(); - } else { - maxImagesWarning.classList.remove("d-none"); - } - }); - // event listener for handling file input changes - fileElem.addEventListener("change", handleFiles); + if (form) { + attachDeleteListenersToExistingImages(); - // event listeners for drag and drop - dropArea.addEventListener("dragover", (event) => { - event.preventDefault(); - dropArea.classList.add("highlight"); - }); + dropArea.addEventListener("click", () => { + if (imageCount < maxImages) { + fileElem.click(); + } else { + maxImagesWarning.classList.remove("d-none"); + } + }); - dropArea.addEventListener("dragleave", () => { - dropArea.classList.remove("highlight"); - }); + // event listener for handling file input changes + fileElem.addEventListener("change", handleFiles); - dropArea.addEventListener("drop", (event) => { - event.preventDefault(); - dropArea.classList.remove("highlight"); - const files = event.dataTransfer.files; - handleFiles({ target: { files } }); - }); + // event listeners for drag and drop + dropArea.addEventListener("dragover", (event) => { + event.preventDefault(); + dropArea.classList.add("highlight"); + }); - function handleFiles(event) { - const files = event.target.files; - const totalFiles = files.length + imageCount; + dropArea.addEventListener("dragleave", () => { + dropArea.classList.remove("highlight"); + }); - if (totalFiles > maxImages) { - maxImagesWarning.classList.remove("d-none"); - } else { - maxImagesWarning.classList.add("d-none"); + dropArea.addEventListener("drop", (event) => { + event.preventDefault(); + dropArea.classList.remove("highlight"); + const files = event.dataTransfer.files; + handleFiles({ target: { files } }); + }); + + function handleFiles(event) { + const files = event.target.files; + const totalFiles = files.length + imageCount; + + if (totalFiles > maxImages) { + maxImagesWarning.classList.remove("d-none"); + } else { + maxImagesWarning.classList.add("d-none"); + } + + // loop through files and add to preview + Array.from(files).forEach((file) => { + console.log("File dragged and dropped:", file); + + if (imageCount < maxImages && file.type.startsWith("image/")) { + const reader = new FileReader(); + reader.onload = function (e) { + const imgDiv = document.createElement("div"); + imgDiv.classList.add("image-preview"); + + const img = document.createElement("img"); + img.src = e.target.result; + + const deleteBtn = document.createElement("button"); + deleteBtn.innerHTML = "x"; + deleteBtn.classList.add("delete-btn"); + + imgDiv.dataset.fileIndex = uploadedFiles.length; + + deleteBtn.addEventListener("click", (event) => { + event.stopPropagation(); + const fileIndex = imgDiv.dataset.fileIndex; + + imgDiv.remove(); + + uploadedFiles.splice(fileIndex, 1); + imageCount--; + + console.log( + `Image count after deletion: ${imageCount}` + ); + + reindexImagePreviews(); + maxImagesWarning.classList.add("d-none"); + }); + + imgDiv.appendChild(img); + imgDiv.appendChild(deleteBtn); + previewContainer.appendChild(imgDiv); + }; + reader.readAsDataURL(file); + uploadedFiles.push(file); + imageCount++; + + console.log(`Image count after addition: ${imageCount}`); + } + }); } - // loop through files and add to preview - Array.from(files).forEach((file) => { - console.log("File dragged and dropped:", file); + function reindexImagePreviews() { + const imagePreviews = + previewContainer.querySelectorAll(".image-preview"); + imagePreviews.forEach((imgDiv, index) => { + imgDiv.dataset.fileIndex = index; // Update the file index for each preview + }); + } - if (imageCount < maxImages && file.type.startsWith("image/")) { - const reader = new FileReader(); - reader.onload = function (e) { - const imgDiv = document.createElement("div"); - imgDiv.classList.add("image-preview"); + // Attach delete listeners to existing images on page load (for already uploaded images) + function attachDeleteListenersToExistingImages() { + const existingImages = + previewContainer.querySelectorAll(".image-preview"); - const img = document.createElement("img"); - img.src = e.target.result; + existingImages.forEach((imgDiv) => { + const deleteBtn = imgDiv.querySelector(".delete-btn"); - const deleteBtn = document.createElement("button"); - deleteBtn.innerHTML = "x"; - deleteBtn.classList.add("delete-btn"); - deleteBtn.addEventListener("click", (event) => { + // If there's a delete button (for each existing image), add a click event listener + if (deleteBtn) { + deleteBtn.addEventListener("click", function (event) { event.stopPropagation(); imgDiv.remove(); - imageCount--; + imageCount--; // Decrease the image count for deleted images + console.log( + `Image count after deletion: ${imageCount}` + ); + maxImagesWarning.classList.add("d-none"); }); + } + }); + } - imgDiv.appendChild(img); - imgDiv.appendChild(deleteBtn); - previewContainer.appendChild(imgDiv); - }; - reader.readAsDataURL(file); - imageCount++; - } + form.addEventListener("submit", function (event) { + event.preventDefault(); + + const formData = new FormData(form); + + fetch(form.action, { + method: form.method, + body: formData, + }) + .then((response) => { + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); // Parse the JSON response from the server + }) + .then((data) => { + console.log("Success:", data); + // If a redirect URL is provided, redirect the user + if (data.redirect_url) { + window.location.href = data.redirect_url; // Redirect to the products index page + } else { + // Optionally show a success message + alert(data.message || "Product updated successfully!"); + } + }) + .catch((error) => { + console.error("Error:", error); + // Optionally, display an error message to the user + alert("There was an error updating the product."); + }); }); } + console.log(imageCount); }); -- GitLab From 92376d4e39e29f8eeeb21317c79febd41cb43b0c Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Thu, 7 Nov 2024 18:35:37 +0100 Subject: [PATCH 13/16] navbar symbols added --- public/css/app.css | 2 +- public/js/script.js | 1 + resources/views/layouts/navbar.blade.php | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/public/css/app.css b/public/css/app.css index 9c0c05a..bb7b48e 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -299,4 +299,4 @@ .show-password-button { right: 1px; transform: translateY(-50%); color: rgb(216, 215, 215); -} +} \ No newline at end of file diff --git a/public/js/script.js b/public/js/script.js index 3a29fbd..421774b 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -18,6 +18,7 @@ document.addEventListener("DOMContentLoaded", function () { }); }); } + const tableView = document.getElementById("tableView"); const tableViewBtn = document.getElementById("tableViewBtn"); diff --git a/resources/views/layouts/navbar.blade.php b/resources/views/layouts/navbar.blade.php index de82484..6d7751c 100644 --- a/resources/views/layouts/navbar.blade.php +++ b/resources/views/layouts/navbar.blade.php @@ -19,19 +19,19 @@ </li> <li class="nav-link"> <a href="{{ route('discounts.index') }}"> - <i class='bx bx-home-alt icon' ></i> + <i class='fa-sharp fa-solid fa-tag icon' ></i> <span class="text nav-text">ПопуÑти</span> </a> </li> <li class="nav-link"> <a href="{{ route('brands.index') }}"> - <i class='bx bx-home-alt icon' ></i> + <i class='fa-sharp fa-solid fa-copyright icon' ></i> <span class="text nav-text">Брендови</span> </a> </li> <li class="nav-link"> <a href="{{ route('users.edit') }}"> - <i class='bx bx-home-alt icon' ></i> + <i class='fa-solid fa-user icon' ></i> <span class="text nav-text">Профил</span> </a> </li> -- GitLab From 393334b050224648af52750468fafd192e317db2 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Thu, 7 Nov 2024 20:38:02 +0100 Subject: [PATCH 14/16] resolved the problem with the search bar searching only on the loaded pag --- app/Http/Controllers/ProductController.php | 25 +++-- public/js/script.js | 44 ++++++--- resources/views/products/index.blade.php | 91 +------------------ .../products/partials/product-list.blade.php | 89 ++++++++++++++++++ routes/web.php | 3 +- 5 files changed, 140 insertions(+), 112 deletions(-) create mode 100644 resources/views/products/partials/product-list.blade.php diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 2447ca5..af70390 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -123,9 +123,6 @@ public function edit(Product $product) public function update(ProductRequest $request, Product $product) { $validated = $request->validated(); - // dd($validated); - Log::info('Received files:', $request->file('images') ?? []); - $product->update($validated); @@ -148,8 +145,7 @@ public function update(ProductRequest $request, Product $product) if ($request->has('deleted_images') && $request->input('deleted_images') !== '') { $deletedImages = explode(',', $request->input('deleted_images')); - // debugging purposes - Log::info('Deleting images:', $deletedImages); + foreach ($deletedImages as $deletedImage) { if (!empty($deletedImage)) { $productImage = ProductImage::find($deletedImage); @@ -166,8 +162,7 @@ public function update(ProductRequest $request, Product $product) foreach ($images as $image) { if ($image && $image->isValid()) { $path = $image->store('images/products', 'public'); - // debugging purposes - Log::info('Stored image:', ['path' => $path]); + ProductImage::create([ 'product_id' => $product->id, 'image' => $path, @@ -190,6 +185,18 @@ public function update(ProductRequest $request, Product $product) return response()->json([ 'success' => true, 'message' => 'Продуктот е уÑпешно ажуриран!', - 'redirect_url' => route('products.index') // Provide the redirect URL if needed - ]); } + 'redirect_url' => route('products.index') + ]); + } + + public function search(Request $request) + { + $search = $request->input('search'); + + $products = Product::when($search, function ($query, $search) { + return $query->where('name', 'like', '%' . $search . '%'); + })->paginate(10); + + return view('products.partials.product-list', ['products' => $products]); + } } diff --git a/public/js/script.js b/public/js/script.js index 421774b..1d736b2 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -2,23 +2,39 @@ document.addEventListener("DOMContentLoaded", function () { const searchInput = document.getElementById("searchInput"); const products = document.querySelectorAll(".product"); - if (searchInput) { - searchInput.addEventListener("input", function () { - const query = this.value.trim().toLowerCase(); + document.getElementById("search").addEventListener("input", function () { + let query = this.value; - products.forEach((product) => { - const productName = product - .querySelector(".product-name") - .innerText.toLowerCase(); - if (productName.includes(query)) { - product.style.display = "block"; - } else { - product.style.display = "none"; + let searchUrl = document.getElementById("search").dataset.url; + + console.log( + "Fetching data from:", + `${searchUrl}?search=${encodeURIComponent(query)}` + ); + + fetch( + `${searchUrl}?search=${encodeURIComponent(query)}`, + { + headers: { + "X-CSRF-TOKEN": document.querySelector( + 'meta[name="csrf-token"]' + ).content, + }, + } + ) + .then((response) => { + if (!response.ok) { + throw new Error("Network response was not ok"); } + return response.text(); + }) + .then((data) => { + document.getElementById("product-list").innerHTML = data; + }) + .catch((error) => { + console.error("Error during fetch operation:", error); }); - }); - } - + }); const tableView = document.getElementById("tableView"); const tableViewBtn = document.getElementById("tableViewBtn"); diff --git a/resources/views/products/index.blade.php b/resources/views/products/index.blade.php index 60d4900..3adcb11 100644 --- a/resources/views/products/index.blade.php +++ b/resources/views/products/index.blade.php @@ -10,7 +10,7 @@ </div> @endif <div class="mb-3"> - <input type="text" id="searchInput" placeholder="Пребарувај..."> + <input type="text" id="search" data-url="{{ route('products.search') }}" placeholder="Пребарувај..."> <button id="tableViewBtn" class="btn border border-rounded mr-2"><i class="fa-solid fa-table"></i></button> <button id="listViewBtn" class="btn border border-rounded"><i class="fa-solid fa-bars"></i></button> </div> @@ -20,97 +20,12 @@ </div> </div> </div> - <div class="row mt-5"> <div class="col-12"> - <div id="tableView" class="row"> - @foreach($products as $product) - <div class="col-12 col-md-6 col-lg-4 mb-4"> - <div class="card px-1 py-4 product"> - @if($product->stock_quantity === 1) - <div class="badge badge-warning" style="position: absolute; top: 0; left: 0;">*Ñамо 1 парче</div> - @endif - @if($product->stock_quantity === 0) - <div class="badge badge-danger" style="position: absolute; top: 0; right: 0;">РаÑпродадено</div> - @endif - <div class="carousel slide" data-ride="carousel" id="carousel-{{ $product->id }}"> - <div class="carousel-inner"> - @if($product->images->isEmpty()) - <div class="carousel-item active" style="height: 300px;"> - <p class="d-flex justify-content-center align-items-center h-100">Сеуште нема Ñлики за овој продукт</p> - </div> - @else - @foreach($product->images as $index => $image) - <div class="carousel-item {{ $index === 0 ? 'active' : '' }}" style="position: relative; height: 300px;"> - <img src="{{ Storage::url($image->image) }}" class="d-block img-fluid rounded mx-auto" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-height: 100%; max-width: 100%; object-fit: contain;" alt="Product Image"> - </div> - @endforeach - @endif - </div> - <a class="carousel-control-prev" href="#carousel-{{ $product->id }}" role="button" data-slide="prev"> - <span class="carousel-control-prev-icon" aria-hidden="true"></span> - <span class="sr-only">Previous</span> - </a> - <a class="carousel-control-next" href="#carousel-{{ $product->id }}" role="button" data-slide="next"> - <span class="carousel-control-next-icon" aria-hidden="true"></span> - <span class="sr-only">Next</span> - </a> - </div> - <div class="card-body"> - <span class="text-left h2 product-name">{{ $product->name }}</span> - <p class="card-text mt-3 h5 small">ДоÑтапни бои: - @foreach($product->productColors->unique('color_name') as $productColor) - <span class="border" style="background-color: {{ $productColor->color_name }}; margin-right: 5px; padding: 8px; display: inline-block;"></span> - @endforeach - </p> - <div class="row"> - <div class="col"> - <p class="card-text h5 mt-2 small"> - Величини: - @foreach($product->productColors->unique('size.name') as $productColor) - @if(!$loop->first) - <span class="ml-1">,</span> - @endif - <span class="badge badge-secondary">{{ strtoupper($productColor->size->name) }}</span> - @endforeach - </p> - </div> - <div class="col text-right"> - <p class="card-text h5 mt-3"> - @if($product->discount_id && $product->discount->status === 'active') - <strong class="small">Цена:</strong> - <span class="original-price text-danger small" style="text-decoration: line-through;">{{ $product->price }} ден.</span> - <p class="discounted-price text-success h4">{{ $product->price - ($product->price * ($product->discount->percentage / 100)) }} ден.</p> - @else - <strong class="small">Цена:</strong> {{ $product->price }} ден. - @endif - </p> - </div> - </div> - </div> - </div> - </div> - @endforeach - </div> - <div id="listView" class="d-none row"> - @foreach($products as $product) - <div class="col-12 mb-4 product"> - <div class="card p-4"> - <div class="row"> - <div class="col dark-green font-weight-bold">#{{ $product->id }}</div> - <div class="col product-name">{{ $product->name }}</div> - <div class="col text-right"> - <a href="{{ route('products.edit', $product) }}" class="btn border rounded-circle"><i class="fas fa-edit"></i></a> - </div> - </div> - </div> - </div> - @endforeach + <div id="product-list"> + @include('products.partials.product-list', ['products' => $products]) </div> </div> </div> </div> - <div class="d-flex justify-content-center mt-4 pagination"> - {{ $products->links('pagination::bootstrap-4') }} - </div> @endsection \ No newline at end of file diff --git a/resources/views/products/partials/product-list.blade.php b/resources/views/products/partials/product-list.blade.php new file mode 100644 index 0000000..8cc3148 --- /dev/null +++ b/resources/views/products/partials/product-list.blade.php @@ -0,0 +1,89 @@ + + <div id="tableView" class="row"> + @foreach($products as $product) + <div class="col-12 col-md-6 col-lg-4 mb-4"> + <div class="card px-1 py-4 product"> + @if($product->stock_quantity === 1) + <div class="badge badge-warning" style="position: absolute; top: 0; left: 0;">*Ñамо 1 парче</div> + @endif + @if($product->stock_quantity === 0) + <div class="badge badge-danger" style="position: absolute; top: 0; right: 0;">РаÑпродадено</div> + @endif + <div class="carousel slide" data-ride="carousel" id="carousel-{{ $product->id }}"> + <div class="carousel-inner"> + @if($product->images->isEmpty()) + <div class="carousel-item active" style="height: 300px;"> + <p class="d-flex justify-content-center align-items-center h-100">Сеуште нема Ñлики за овој продукт</p> + </div> + @else + @foreach($product->images as $index => $image) + <div class="carousel-item {{ $index === 0 ? 'active' : '' }}" style="position: relative; height: 300px;"> + <img src="{{ Storage::url($image->image) }}" class="d-block img-fluid rounded mx-auto" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-height: 100%; max-width: 100%; object-fit: contain;" alt="Product Image"> + </div> + @endforeach + @endif + </div> + <a class="carousel-control-prev" href="#carousel-{{ $product->id }}" role="button" data-slide="prev"> + <span class="carousel-control-prev-icon" aria-hidden="true"></span> + <span class="sr-only">Previous</span> + </a> + <a class="carousel-control-next" href="#carousel-{{ $product->id }}" role="button" data-slide="next"> + <span class="carousel-control-next-icon" aria-hidden="true"></span> + <span class="sr-only">Next</span> + </a> + </div> + <div class="card-body"> + <span class="text-left h2 product-name">{{ $product->name }}</span> + <p class="card-text mt-3 h5 small">ДоÑтапни бои: + @foreach($product->productColors->unique('color_name') as $productColor) + <span class="border" style="background-color: {{ $productColor->color_name }}; margin-right: 5px; padding: 8px; display: inline-block;"></span> + @endforeach + </p> + <div class="row"> + <div class="col"> + <p class="card-text h5 mt-2 small"> + Величини: + @foreach($product->productColors->unique('size.name') as $productColor) + @if(!$loop->first) + <span class="ml-1">,</span> + @endif + <span class="badge badge-secondary">{{ strtoupper($productColor->size->name) }}</span> + @endforeach + </p> + </div> + <div class="col text-right"> + <p class="card-text h5 mt-3"> + @if($product->discount_id && $product->discount->status === 'active') + <strong class="small">Цена:</strong> + <span class="original-price text-danger small" style="text-decoration: line-through;">{{ $product->price }} ден.</span> + <p class="discounted-price text-success h4">{{ $product->price - ($product->price * ($product->discount->percentage / 100)) }} ден.</p> + @else + <strong class="small">Цена:</strong> {{ $product->price }} ден. + @endif + </p> + </div> + </div> + </div> + </div> + </div> + @endforeach + </div> + <div id="listView" class="d-none row"> + @foreach($products as $product) + <div class="col-12 mb-4 product"> + <div class="card p-4"> + <div class="row"> + <div class="col dark-green font-weight-bold">#{{ $product->id }}</div> + <div class="col product-name">{{ $product->name }}</div> + <div class="col text-right"> + <a href="{{ route('products.edit', $product) }}" class="btn border rounded-circle"><i class="fas fa-edit"></i></a> + </div> + </div> + </div> + </div> + @endforeach + </div> + +<div class="d-flex justify-content-center mt-4 pagination"> + {{ $products->links('pagination::bootstrap-4') }} +</div> \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 5716c24..12d5846 100644 --- a/routes/web.php +++ b/routes/web.php @@ -55,7 +55,8 @@ Route::get('/user/edit', [UserController::class, 'edit'])->name('users.edit'); Route::put('/user/{id}', [UserController::class, 'update'])->name('users.update'); - +//search products handle +Route::get('/products/search', [ProductController::class, 'search'])->name('products.search'); Route::group(['middleware' => ['admin', 'super_admin']], function () { -- GitLab From bec93b4476292a3f6e82c90cb765458c33ff48d0 Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Thu, 7 Nov 2024 21:20:49 +0100 Subject: [PATCH 15/16] search css done --- public/css/app.css | 2 +- public/js/script.js | 4 ++-- resources/views/brands/index.blade.php | 2 +- resources/views/discounts/index.blade.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/css/app.css b/public/css/app.css index bb7b48e..dd04cba 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -254,7 +254,7 @@ body.dark .switch::before { left: 24px; } -#searchInput { +#search { border: 1px solid rgb(160, 154, 154); border-radius: 5px; height: 3em; diff --git a/public/js/script.js b/public/js/script.js index 1d736b2..d56d0fd 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -1,6 +1,6 @@ document.addEventListener("DOMContentLoaded", function () { - const searchInput = document.getElementById("searchInput"); - const products = document.querySelectorAll(".product"); + // const searchInput = document.getElementById("searchInput"); + // const products = document.querySelectorAll(".product"); document.getElementById("search").addEventListener("input", function () { let query = this.value; diff --git a/resources/views/brands/index.blade.php b/resources/views/brands/index.blade.php index 8819605..d4a9b48 100644 --- a/resources/views/brands/index.blade.php +++ b/resources/views/brands/index.blade.php @@ -10,7 +10,7 @@ </div> @endif <div class="mb-3"> - <input type="text" id="searchInput" placeholder="Пребарувај..."> + <input type="text" id="search" placeholder="Пребарувај..."> </div> <div id="add-brand"> Додај нов бренд<a href="{{ route('brands.create') }}" class="m-2 btn btn-secondary background-dark-green"><i class="fa-solid fa-plus"></i></a> diff --git a/resources/views/discounts/index.blade.php b/resources/views/discounts/index.blade.php index 3d6a0d2..09f43dd 100644 --- a/resources/views/discounts/index.blade.php +++ b/resources/views/discounts/index.blade.php @@ -10,7 +10,7 @@ </div> @endif <div class="mb-3"> - <input type="text" id="searchInput" placeholder="Пребарувај..."> + <input type="text" id="search" placeholder="Пребарувај..."> </div> <div id="add-discount"> Додај нов попуÑÑ‚ / промо код<a href="{{ route('discounts.create') }}" class="m-2 btn btn-secondary background-dark-green"> -- GitLab From e53ad9290cdaa8bd64d42452a110f5358cdf1def Mon Sep 17 00:00:00 2001 From: unknown <vaskomitevski@yahoo.com> Date: Wed, 4 Dec 2024 22:40:16 +0100 Subject: [PATCH 16/16] got some comments erased, and some translated to english... --- app/Http/Controllers/BrandController.php | 4 ---- app/Http/Controllers/DiscountController.php | 2 +- database/seeders/ProductSeeder.php | 6 +++--- public/js/discount.js | 2 +- public/js/product.js | 14 ++++++-------- public/js/profile.js | 5 ++--- public/js/script.js | 4 ++-- resources/views/brands/create.blade.php | 2 +- resources/views/brands/edit.blade.php | 10 +++++----- resources/views/products/create.blade.php | 6 +++--- resources/views/products/edit.blade.php | 10 +++++----- 11 files changed, 29 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/BrandController.php b/app/Http/Controllers/BrandController.php index bb7b81c..4176ce1 100644 --- a/app/Http/Controllers/BrandController.php +++ b/app/Http/Controllers/BrandController.php @@ -102,10 +102,6 @@ public function update(BrandRequest $request, Brand $brand) $categories = $validatedData['categories']; if (!empty($categories)) { - // Ensure no out-of-bounds access - if (count($categories) > 5) { - // Handle or log this condition if necessary - } $brand->categories()->sync($categories); } else { diff --git a/app/Http/Controllers/DiscountController.php b/app/Http/Controllers/DiscountController.php index bad2b36..06e807b 100644 --- a/app/Http/Controllers/DiscountController.php +++ b/app/Http/Controllers/DiscountController.php @@ -53,7 +53,7 @@ public function store(DiscountRequest $request) $discount = Discount::create($discountData); - //for apply discounts on targeted products with #1,#2... + //for applying discounts on targeted products with #1,#2... if ($request->filled('products')) { $productIds = explode('#', $request->input('products')); diff --git a/database/seeders/ProductSeeder.php b/database/seeders/ProductSeeder.php index dfbcf9a..fedcc30 100644 --- a/database/seeders/ProductSeeder.php +++ b/database/seeders/ProductSeeder.php @@ -31,7 +31,7 @@ public function run() $product = new Product(); $product->name = $faker->sentence(2); $product->description = $faker->text; - // да завршува цената на xx90 (1190, 1390, 2990, 3290, etc.) + // price ending on "xx90" (1190, 1390, 2990, 3290, etc.) $randomPrice = $faker->numberBetween(99, 399); $roundedPrice = ceil($randomPrice / 10) * 10; $product->price = ($roundedPrice * 10) - 10; @@ -44,11 +44,11 @@ public function run() $product->category_id = $faker->randomElement($categories); $product->save(); - // боите + // colors $maxColors = min($product->stock_quantity, count($colors)); $randomColors = $faker->randomElements($colors, $faker->numberBetween(1, $maxColors)); foreach ($randomColors as $color) { - // рандом величини за Ñекоја од креираните бои + // random sizes for created colors $randomSizes = $faker->randomElements($sizes, $faker->numberBetween(1, 3)); foreach ($randomSizes as $sizeId) { ProductColor::create([ diff --git a/public/js/discount.js b/public/js/discount.js index ee7d388..dd97906 100644 --- a/public/js/discount.js +++ b/public/js/discount.js @@ -18,7 +18,7 @@ document.addEventListener("DOMContentLoaded", function () { }); } - //модалот за попуÑÑ‚ + //modal for choosing discount var selectDiscountBtn = document.getElementById("selectDiscountBtn"); var discountList = document.getElementById("discountList"); var alreadyAppliedDiscount = document.getElementById( diff --git a/public/js/product.js b/public/js/product.js index b864356..6183ff6 100644 --- a/public/js/product.js +++ b/public/js/product.js @@ -133,7 +133,7 @@ document.addEventListener("DOMContentLoaded", function () { const previewContainer = document.getElementById("previewContainer"); const maxImagesWarning = document.getElementById("maxImagesWarning"); - // Count existing images + // count existing images let imageCount = previewContainer.querySelectorAll("img").length; const maxImages = 8; const uploadedFiles = []; @@ -243,7 +243,7 @@ document.addEventListener("DOMContentLoaded", function () { }); } - // Attach delete listeners to existing images on page load (for already uploaded images) + // attach delete listeners to existing images on page load (for already uploaded images) function attachDeleteListenersToExistingImages() { const existingImages = previewContainer.querySelectorAll(".image-preview"); @@ -251,7 +251,7 @@ document.addEventListener("DOMContentLoaded", function () { existingImages.forEach((imgDiv) => { const deleteBtn = imgDiv.querySelector(".delete-btn"); - // If there's a delete button (for each existing image), add a click event listener + // if there's a delete button (for each existing image), add a click event listener if (deleteBtn) { deleteBtn.addEventListener("click", function (event) { event.stopPropagation(); @@ -280,21 +280,19 @@ document.addEventListener("DOMContentLoaded", function () { if (!response.ok) { throw new Error("Network response was not ok"); } - return response.json(); // Parse the JSON response from the server + return response.json(); }) .then((data) => { console.log("Success:", data); - // If a redirect URL is provided, redirect the user + if (data.redirect_url) { - window.location.href = data.redirect_url; // Redirect to the products index page + window.location.href = data.redirect_url; } else { - // Optionally show a success message alert(data.message || "Product updated successfully!"); } }) .catch((error) => { console.error("Error:", error); - // Optionally, display an error message to the user alert("There was an error updating the product."); }); }); diff --git a/public/js/profile.js b/public/js/profile.js index a54560a..9a2c87a 100644 --- a/public/js/profile.js +++ b/public/js/profile.js @@ -1,5 +1,4 @@ document.addEventListener("DOMContentLoaded", function () { - //за Ñликата одма да Ñе Ñмене во кругот за Ñлика document.getElementById("changePhotoLink").addEventListener("click", function (event) { event.preventDefault(); @@ -18,7 +17,7 @@ document.addEventListener("DOMContentLoaded", function () { } }); - //промени паÑворд toggle (display-none / display-block) + //change password toggle (display-none / display-block) const passwordChangeFields = document.getElementById("passwordChangeFields"); const changePasswordLink = document.getElementById("changePasswordBtn"); @@ -36,7 +35,7 @@ document.addEventListener("DOMContentLoaded", function () { togglePasswordChangeFields(); }); - //покажи паÑворд (одблендирај) + //show password (unblend) function togglePasswordVisibility(button) { const passwordField = button.parentNode.querySelector("input"); const icon = button.querySelector("i.fa-solid"); diff --git a/public/js/script.js b/public/js/script.js index d56d0fd..41a7b83 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -68,7 +68,7 @@ document.addEventListener("DOMContentLoaded", function () { const image = document.querySelector(".image"); const mainContent = document.querySelector(".main-content"); - // Check the sidebar state on page load + // check the sidebar state on page load const sidebarState = localStorage.getItem("sidebarState"); if (sidebarState === "open") { sidebar.classList.remove("close"); @@ -104,7 +104,7 @@ document.addEventListener("DOMContentLoaded", function () { } } - // Check for stored dark mode in local storage (doesnt work quite well...) + // check for stored dark mode in local storage (doesnt work quite well...) const darkMode = localStorage.getItem("darkMode") === "true"; setDarkMode(darkMode); diff --git a/resources/views/brands/create.blade.php b/resources/views/brands/create.blade.php index 5a93ae1..41f38a6 100644 --- a/resources/views/brands/create.blade.php +++ b/resources/views/brands/create.blade.php @@ -77,7 +77,7 @@ </button> </div> - <!-- модал за попуÑÑ‚ --> + <!-- modal for discount --> <input type="hidden" id="selectedDiscountId" name="selectedDiscountId" value=""> <div class="modal fade" id="discountModal" tabindex="-1" aria-labelledby="discountModalLabel" aria-hidden="true"> diff --git a/resources/views/brands/edit.blade.php b/resources/views/brands/edit.blade.php index ace5480..0208cff 100644 --- a/resources/views/brands/edit.blade.php +++ b/resources/views/brands/edit.blade.php @@ -73,7 +73,7 @@ </div> <div class="my-5"> - {{-- Check if there are products for this brand --}} + {{-- check if there are products for this brand --}} @if(!$brand->products->isEmpty()) @php $firstDiscountId = null; @@ -82,13 +82,13 @@ foreach($brand->products as $product) { if(!$product->discount_id) { - $allSameDiscount = false; // Not all products have a discount + $allSameDiscount = false; } else { - $noDiscountApplied = false; // At least one product has a discount + $noDiscountApplied = false; if($firstDiscountId === null) { - $firstDiscountId = $product->discount_id; // Set the first discount ID + $firstDiscountId = $product->discount_id; } elseif($firstDiscountId !== $product->discount_id) { - $allSameDiscount = false; // Discounts differ between products + $allSameDiscount = false; } } } diff --git a/resources/views/products/create.blade.php b/resources/views/products/create.blade.php index 10828e7..e6d58c5 100644 --- a/resources/views/products/create.blade.php +++ b/resources/views/products/create.blade.php @@ -95,12 +95,12 @@ <div class="mb-3"> <label for="images" class="form-label">Слики (МакÑимум 8)</label> - <!-- Drag and Drop Area --> + <!-- drag and drop area --> <div id="dropArea" class="drag-drop-area"> <p>МеÑто за Ñлики за вашиот производ (Max 8)</p> - <!-- Container for the image previews --> + <!-- container for the image previews --> <div id="previewContainer" class="preview-container"> - <!-- Image previews will be injected here --> + <!-- image previews will be injected here --> </div> </div> <input type="file" id="fileElem" name="images[]" multiple accept="image/*" style="display:none;"> diff --git a/resources/views/products/edit.blade.php b/resources/views/products/edit.blade.php index 6e2bc23..d6ef49c 100644 --- a/resources/views/products/edit.blade.php +++ b/resources/views/products/edit.blade.php @@ -273,8 +273,8 @@ document.querySelectorAll('.delete-btn').forEach(button => { button.addEventListener('click', (event) => { - event.stopPropagation(); // Prevent drag-and-drop event from firing - button.parentElement.remove(); // Remove the image preview + event.stopPropagation(); + button.parentElement.remove(); }); }); @@ -290,9 +290,9 @@ const imageId = button.getAttribute('data-image-id'); if (imageId) { - deletedImages.push(imageId); // Add the image ID to the array - deletedImagesInput.value = deletedImages.join(','); // Store in hidden input - button.closest('.image-preview').remove(); // Remove the preview + deletedImages.push(imageId); + deletedImagesInput.value = deletedImages.join(','); + button.closest('.image-preview').remove(); } }); }); -- GitLab