Skip to content

Commit 6be1200

Browse files
productDetail2
1 parent d032b26 commit 6be1200

File tree

6 files changed

+109
-133
lines changed

6 files changed

+109
-133
lines changed

app/Http/Controllers/Front/HomeController.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,22 @@ public function index()
3131
'brands' => $brands,
3232
]);
3333
}
34-
public function productDetail(Request $request ,$slug)
34+
public function productDetail(Request $request, $slug)
3535
{
36-
$product=Product::where('slug',$slug)->firstOrFail();
37-
$productResources=new ProductResources($product);
38-
$relatedProducts=PorductListResurce::collection(
39-
Product::where('category_id',$product->category_id)
40-
->where('id','!=',$product->id)
41-
->limit(4)
42-
->get()
36+
37+
$product = Product::where('slug', $slug)->firstOrFail();
38+
$productResource = new ProductResources($product);
39+
$relatedProducts = PorductListResurce::collection(
40+
Product::where('category_id', $product->category_id)
41+
->where('id', '!=', $product->id)
42+
->limit(4)
43+
->get()
4344
);
44-
return inertia::render('Ecommerce/ProductDetail', [
45-
'product' => $productResources->resolve(),
45+
46+
47+
return Inertia::render('Ecommerce/ProductDetail', [
48+
'product' => $productResource->resolve(),
49+
'variationOptions' => request('options', []),
4650
'relatedProducts' => $relatedProducts->resolve(),
4751
]);
4852
}

app/Http/Resources/PorductListResurce.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public function toArray(Request $request): array
1818
'id' => $this->id,
1919
'name' => $this->name,
2020
'slug' => $this->slug,
21+
'category_id' => $this->category_id,
2122
'description' => $this->description,
2223
'price' => $this->getPriceForFirstOption(),
2324
'image' => $this->getFirstImageUrl('images', 'small'),

app/Http/Resources/ProductResources.php

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,12 @@
77

88
class ProductResources extends JsonResource
99
{
10-
/**
11-
* Transform the resource into an array.
12-
*
13-
* @return array<string, mixed>
14-
*/
1510
public function toArray(Request $request): array
1611
{
1712
$options = $request->input('options') ?: [];
18-
if ($options) {
19-
$images = $this->getImagesForOptions($options);
20-
} else {
21-
$images = $this->getImages();
22-
}
13+
14+
// صور المنتج حسب الخيارات أو صور عامة
15+
$images = $options ? $this->getImagesForOptions($options) : $this->getImages();
2316

2417
return [
2518
'id' => $this->id,
@@ -28,14 +21,16 @@ public function toArray(Request $request): array
2821
'price' => $this->price,
2922
'quantity' => $this->quantity,
3023
'image' => $this->getFirstImageUrl('images'),
31-
'images' => $images->map(function ($image) {
24+
25+
'images' => collect($images)->map(function ($image) {
3226
return [
3327
'id' => $image->id,
3428
'thumb' => $image->getUrl('thumb'),
3529
'small' => $image->getUrl('small'),
3630
'large' => $image->getUrl('large'),
3731
];
3832
}),
33+
3934
'variationTypes' => $this->variationTypes->map(function ($variationType) {
4035
return [
4136
'id' => $variationType->id,
@@ -56,6 +51,7 @@ public function toArray(Request $request): array
5651
}),
5752
];
5853
}),
54+
5955
'variations' => $this->variations->map(function ($variation) {
6056
return [
6157
'id' => $variation->id,
@@ -64,8 +60,9 @@ public function toArray(Request $request): array
6460
'variation_type_option_ids' => $variation->variation_type_option_ids,
6561
];
6662
}),
63+
6764
'rating' => 4,
68-
'reviews_count' => 100
65+
'reviews_count' => 100,
6966
];
7067
}
7168
}

app/Models/Product.php

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use Spatie\Sluggable\SlugOptions;
1414
use Spatie\MediaLibrary\MediaCollections\Models\Media;
1515

16-
1716
class Product extends Model implements HasMedia
1817
{
1918
use HasSlug, InteractsWithMedia;
@@ -26,6 +25,7 @@ public function getSlugOptions(): SlugOptions
2625
->generateSlugsFrom('name')
2726
->saveSlugsTo('slug');
2827
}
28+
2929
public function registerMediaConversions(?Media $media = null): void
3030
{
3131
$this->addMediaConversion('thumb')->width(100);
@@ -55,45 +55,54 @@ public function variationTypes(): HasMany
5555

5656
public function options(): HasManyThrough
5757
{
58-
return $this->hasManyThrough(VariationTypeOption::class, VariationType::class, 'product_id', 'variation_type_id', 'id', 'id');
58+
return $this->hasManyThrough(
59+
VariationTypeOption::class,
60+
VariationType::class,
61+
'product_id',
62+
'variation_type_id',
63+
'id',
64+
'id'
65+
);
5966
}
6067

6168
public function variations(): HasMany
6269
{
6370
return $this->hasMany(ProductVariation::class);
6471
}
72+
6573
public function getFirstImageUrl(string $collectionName = 'images', string $conversion = 'thumb'): ?string
6674
{
67-
if($this->options()->count() > 0) {
75+
if ($this->options()->count() > 0) {
6876
foreach ($this->options as $option) {
69-
$imageUrl = $option->getFirstMediaUrl($collectionName,$conversion);
77+
$imageUrl = $option->getFirstMediaUrl($collectionName, $conversion);
7078
if ($imageUrl) {
7179
return $imageUrl;
7280
}
7381
}
7482
}
7583
return $this->getFirstMediaUrl($collectionName, $conversion);
7684
}
85+
7786
public function getPriceForFirstOption()
7887
{
7988
$firstOptions = $this->getFirstOptionMap();
80-
if ($firstOptions) {
81-
return $this->getpriceForOptions($firstOptions);
82-
}
83-
return $this->price;
89+
return $firstOptions
90+
? $this->getpriceForOptions($firstOptions)
91+
: $this->price;
8492
}
93+
8594
public function getFirstOptionMap()
8695
{
87-
return $this->variationTypes->mapWithKeys(fn($type)
88-
=>[$type->id=>$type->options[0]->id])->toArray();
89-
90-
96+
return $this->variationTypes->mapWithKeys(function ($type) {
97+
return [$type->id => $type->options[0]->id];
98+
})->toArray();
9199
}
100+
92101
public function getpriceForOptions(array $optionsIds = [])
93102
{
94103
$optionsIds = array_values($optionsIds);
95104
sort($optionsIds);
96-
json_encode($optionsIds);
105+
97106
foreach ($this->variations as $variation) {
98107
$a = $variation->variation_type_options_ids;
99108
if ($optionsIds == $a) {
@@ -103,36 +112,35 @@ public function getpriceForOptions(array $optionsIds = [])
103112

104113
return $this->price;
105114
}
115+
106116
public function getImages()
107117
{
108118
if ($this->options->count() > 0) {
109-
foreach ($this->options as $option){
119+
foreach ($this->options as $option) {
110120
$images = $option->getMedia('images');
111-
if ($images->count() > 0) {
112-
return $images;
121+
if ($images->count() > 0) {
122+
return $images;
123+
}
113124
}
114125
}
115-
}
116126
return $this->getMedia('images');
117127
}
118-
public function getImagesForOptions(array $optionsIds = null)
128+
129+
public function getImagesForOptions(array $optionsIds = []): \Illuminate\Support\Collection
119130
{
120-
if ($optionsIds) {
121-
$optionsIds = array_values($optionsIds);
122-
sort($optionsIds);
123-
$options = $this->variationTypes::whereIn('id', $optionsIds)->get();
124-
foreach ($options as $option) {
125-
$image= $option->getFirstImageUrl('images','small');
126-
if ($image) {
127-
return $image;
128-
}
129-
}
131+
$images = collect();
130132

133+
$matchedOptions = $this->options()->whereIn('variation_type_options.id', $optionsIds)->get();
134+
135+
foreach ($matchedOptions as $option) {
136+
$mediaItems = $option->getMedia('images');
137+
if ($mediaItems->isNotEmpty()) {
138+
$images = $images->merge($mediaItems);
139+
}
131140
}
132141

133-
return $this->getFirstImageUrl('images', 'small')?:asset('/storage/uploads/admins/placeholder.png');
142+
return $images->isNotEmpty()
143+
? $images
144+
: $this->getMedia('images');
134145
}
135-
136-
137-
138146
}

resources/js/components/ecommerce/HomePage/ProductCard.tsx

Lines changed: 22 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,48 @@
1-
import React from 'react';
2-
import { router } from '@inertiajs/react';
31
import { ProductListItem } from '@/types';
4-
import {
5-
FaShoppingCart,
6-
FaHeart,
7-
FaSearch,
8-
FaStar,
9-
FaStarHalfAlt,
10-
} from "react-icons/fa";
11-
12-
interface ProductCardProps {
13-
product: ProductListItem;
14-
}
15-
const stripHtml = (html: string) => {
16-
const div = document.createElement('div');
17-
div.innerHTML = html;
18-
return div.textContent || div.innerText || '';
19-
};
20-
2+
import { router } from '@inertiajs/react';
3+
import { Heart, ShoppingBag } from 'lucide-react';
214

22-
const ProductCard: React.FC<ProductCardProps> = ({ product }) => {
5+
const ProductCard = (product: ProductListItem) => {
236
const handleDetail = (slug: string) => {
247
router.visit(route('product.detail', { slug }));
258
};
9+
const stripHtml = (html: string) => {
10+
const div = document.createElement('div');
11+
div.innerHTML = html;
12+
return div.textContent || div.innerText || '';
13+
};
2614
return (
2715
<div className="group overflow-hidden rounded-lg bg-white shadow-sm">
2816
<div className="relative">
29-
<img
30-
src={product.image.replace('localhost', '127.0.0.1:8000')}
31-
alt="Product"
32-
className="h-64 w-full object-cover"
33-
/>
17+
<img src={product.image.replace('localhost', '127.0.0.1:8000')} alt="Product" className="h-64 w-full object-cover" />
3418
{product.isDiscount && (
35-
<div className="absolute top-0 right-0 m-2 rounded-md bg-red-500 px-2 py-1 text-sm text-white">
36-
{product.discount}
37-
</div>
19+
<div className="absolute top-0 right-0 m-2 rounded-md bg-red-500 px-2 py-1 text-sm text-white">-{product.discount}%</div>
3820
)}
39-
40-
<div className="absolute inset-0 z-20 flex items-center justify-center bg-black/30 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
41-
<button className="mx-2 rounded-full bg-white p-3 text-gray-800 transition hover:bg-indigo-600 hover:text-white">
42-
<FaShoppingCart />
43-
</button>
21+
<div className="bg-opacity-20 absolute inset-0 flex items-center justify-center bg-black/30 opacity-0 transition-opacity group-hover:opacity-100">
4422
<button className="mx-2 rounded-full bg-white p-3 text-gray-800 transition hover:bg-indigo-600 hover:text-white">
45-
<FaHeart />
23+
<ShoppingBag className="h-5 w-5" />
4624
</button>
4725
<button className="mx-2 rounded-full bg-white p-3 text-gray-800 transition hover:bg-indigo-600 hover:text-white">
48-
<FaSearch />
26+
<Heart className="h-5 w-5" />
4927
</button>
5028
</div>
5129
</div>
52-
5330
<div className="p-4" onClick={() => handleDetail(product.slug)}>
5431
<h3 className="mb-2 text-lg font-medium">{product.name}</h3>
5532
<p className="mb-3 text-sm text-gray-600">
5633
{stripHtml(product.description).substring(0, 100)}...
5734
</p>
58-
5935
<div className="flex items-center justify-between">
60-
<span className="font-bold text-indigo-600">
61-
${product.price}
62-
{product.isDiscount && (
63-
<span className="ml-2 text-gray-400 line-through">
64-
$119.99
65-
</span>
66-
)}
67-
</span>
36+
<div>
37+
<span className="font-bold text-indigo-600">${product.price}</span>
38+
{product.isDiscount && <span className="ml-2 text-gray-400 line-through">$119.99</span>}
39+
</div>
6840
<div className="flex text-yellow-400">
69-
<FaStar />
70-
<FaStar />
71-
<FaStar />
72-
<FaStar />
73-
<FaStarHalfAlt />
41+
<i className="fas fa-star"></i>
42+
<i className="fas fa-star"></i>
43+
<i className="fas fa-star"></i>
44+
<i className="fas fa-star"></i>
45+
<i className="fas fa-star-half-alt"></i>
7446
</div>
7547
</div>
7648
</div>

0 commit comments

Comments
 (0)