Skip to content

Commit 4a6cf38

Browse files
thapacodes4uankitcodes4uSlimDeluxe
authored
feat: implement categories
* feat: 7/23 done , 2 work in progress * feat: completed 6 more task * improve list UI, refactor hierarchy selects, update seeder images, fix sorting breadcrumbs, add i18n * add unique SEF key validation and auto-generation from category name * incomplete test cases * refactor: make tenancy configurable * feat: refactoring, adding test cases * feat: adding product filter test * fix: implement requested changes from code review * fix: formatting code * fix: reverting changes back to original for .gitignore * refactor: applying requested changes * fix: unable to update due to sef key issue --------- Co-authored-by: ankitcodes4u <ankitcodes4u@gmail.com> Co-authored-by: Omer Šabić <omer@datalinx.si>
1 parent a8eb163 commit 4a6cf38

28 files changed

Lines changed: 1228 additions & 75 deletions

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"eclipsephp/common": "dev-main",
5151
"filament/filament": "^3.3",
5252
"filament/spatie-laravel-translatable-plugin": "^3.2",
53+
"solution-forest/filament-tree": "^2.1",
5354
"spatie/laravel-package-tools": "^1.19",
5455
"spatie/laravel-translatable": "^6.11"
5556
},

config/eclipse-catalogue.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

33
return [
4-
//
4+
5+
/*
6+
|--------------------------------------------------------------------------
7+
| Multi-tenancy config
8+
|--------------------------------------------------------------------------
9+
*/
10+
'tenancy' => [
11+
'model' => null,
12+
'foreign_key' => null,
13+
],
514
];

database/factories/CategoryFactory.php

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,95 @@
33
namespace Eclipse\Catalogue\Factories;
44

55
use Eclipse\Catalogue\Models\Category;
6-
use Eclipse\Core\Models\Site;
76
use Illuminate\Database\Eloquent\Factories\Factory;
87
use Illuminate\Support\Carbon;
8+
use Illuminate\Support\Str;
99

1010
class CategoryFactory extends Factory
1111
{
1212
protected $model = Category::class;
1313

1414
public function definition(): array
1515
{
16+
$englishName = mb_ucfirst(fake()->words(2, true));
17+
$slovenianName = 'SI: '.$englishName;
18+
19+
$englishShortDesc = fake()->sentence();
20+
$slovenianShortDesc = 'SI: '.$englishShortDesc;
21+
22+
$englishDesc = fake()->paragraphs(3, true);
23+
$slovenianDesc = 'SI: '.$englishDesc;
24+
1625
return [
17-
'name' => $this->faker->words(),
18-
'sort' => $this->faker->randomNumber(),
19-
'is_active' => $this->faker->boolean(),
20-
'recursive_browsing' => $this->faker->boolean(),
21-
'sef_key' => $this->faker->word(),
22-
'short_desc' => $this->faker->words(10),
23-
'description' => $this->faker->text(),
26+
'name' => [
27+
'en' => $englishName,
28+
'sl' => $slovenianName,
29+
],
30+
'parent_id' => null,
31+
'image' => self::generateCategoryImage($englishName),
32+
'sort' => fake()->randomNumber(),
33+
'is_active' => fake()->boolean(),
34+
'code' => fake()->optional()->bothify('CAT-####'),
35+
'recursive_browsing' => fake()->boolean(),
36+
'sef_key' => [
37+
'en' => Str::slug($englishName),
38+
'sl' => Str::slug($slovenianName),
39+
],
40+
'short_desc' => [
41+
'en' => $englishShortDesc,
42+
'sl' => $slovenianShortDesc,
43+
],
44+
'description' => [
45+
'en' => $englishDesc,
46+
'sl' => $slovenianDesc,
47+
],
2448
'created_at' => Carbon::now(),
2549
'updated_at' => Carbon::now(),
26-
27-
'site_id' => Site::inRandomOrder()->first()?->id ?? Site::factory()->create()->id,
50+
'site_id' => null,
2851
];
2952
}
53+
54+
private static function generateCategoryImage(string $name): ?string
55+
{
56+
$colors = ['3B82F6', '10B981', 'F59E0B', 'EF4444', '8B5CF6', '06B6D4', 'F97316', 'EC4899'];
57+
$backgrounds = ['E0F2FE', 'ECFDF5', 'FFFBEB', 'FEF2F2', 'F3E8FF', 'ECFEFF', 'FFF7ED', 'FDF2F8'];
58+
59+
$color = fake()->randomElement($colors);
60+
$background = fake()->randomElement($backgrounds);
61+
62+
return fake()->optional(0.8)->passthrough(
63+
'https://ui-avatars.com/api/?name='.urlencode($name).
64+
'&size=400&background='.$background.
65+
'&color='.$color.
66+
'&bold=true&format=png'
67+
);
68+
}
69+
70+
public function parent(): static
71+
{
72+
return $this->state(fn (array $attributes): array => [
73+
'parent_id' => null,
74+
]);
75+
}
76+
77+
public function child(?Category $parent = null): static
78+
{
79+
return $this->state(fn (array $attributes): array => [
80+
'parent_id' => $parent?->id ?? Category::factory()->parent()->create()->id,
81+
]);
82+
}
83+
84+
public function active(): static
85+
{
86+
return $this->state(fn (array $attributes): array => [
87+
'is_active' => true,
88+
]);
89+
}
90+
91+
public function inactive(): static
92+
{
93+
return $this->state(fn (array $attributes): array => [
94+
'is_active' => false,
95+
]);
96+
}
3097
}

database/factories/ProductFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Eclipse\Catalogue\Factories;
44

5+
use Eclipse\Catalogue\Models\Category;
56
use Eclipse\Catalogue\Models\Product;
67
use Illuminate\Database\Eloquent\Factories\Factory;
78
use Illuminate\Support\Carbon;
@@ -32,6 +33,7 @@ public function definition(): array
3233
'en' => $englishName,
3334
'sl' => $slovenianName,
3435
],
36+
'category_id' => Category::inRandomOrder()->first()?->id ?? Category::factory(),
3537
'short_description' => [
3638
'en' => $englishShortDesc,
3739
'sl' => $slovenianShortDesc,

database/migrations/2025_07_01_141618_create_categories_table.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,9 @@ public function up(): void
1515
->cascadeOnDelete()
1616
->cascadeOnUpdate();
1717
$table->string('name');
18-
$table->foreignId('parent_id')
19-
->nullable()
20-
->constrained('catalogue_categories', 'id')
21-
->cascadeOnDelete()
22-
->cascadeOnUpdate();
18+
$table->integer('parent_id')->default(-1);
2319
$table->string('image')->nullable();
24-
$table->unsignedInteger('sort')->nullable();
20+
$table->integer('sort')->default(0)->index();
2521
$table->boolean('is_active');
2622
$table->string('code')->nullable();
2723
$table->boolean('recursive_browsing')->nullable();
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::table('catalogue_products', function (Blueprint $table) {
12+
$table->foreignId('category_id')
13+
->nullable()
14+
->after('name')
15+
->constrained('catalogue_categories')
16+
->nullOnDelete()
17+
->cascadeOnUpdate();
18+
});
19+
}
20+
21+
public function down(): void
22+
{
23+
Schema::table('catalogue_products', function (Blueprint $table) {
24+
$table->dropForeign(['category_id']);
25+
$table->dropColumn('category_id');
26+
});
27+
}
28+
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Eclipse\Catalogue\Seeders;
4+
5+
use Eclipse\Catalogue\Models\Category;
6+
use Illuminate\Database\Seeder;
7+
8+
class CategorySeeder extends Seeder
9+
{
10+
public function run(): void
11+
{
12+
$tenantModel = config('eclipse-catalogue.tenancy.model');
13+
$tenantFK = config('eclipse-catalogue.tenancy.foreign_key');
14+
$tenants = $tenantModel::all();
15+
16+
foreach ($tenants as $tenant) {
17+
$parents = Category::factory()
18+
->parent()
19+
->active()
20+
->count(3)
21+
->create([$tenantFK => $tenant->id]);
22+
23+
foreach ($parents as $index => $parent) {
24+
$childrenCount = match ($index) {
25+
0 => 3,
26+
1 => 2,
27+
2 => 2,
28+
};
29+
30+
Category::factory()
31+
->child($parent)
32+
->active()
33+
->count($childrenCount)
34+
->create([$tenantFK => $tenant->id]);
35+
}
36+
}
37+
}
38+
}

resources/lang/en/categories.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
return [
4+
'title' => 'Categories',
5+
'navigation_group' => 'Catalogue',
6+
'sorting' => 'Sorting',
7+
8+
'form' => [
9+
'sections' => [
10+
'basic_information' => 'Basic Information',
11+
'content' => 'Content',
12+
'media_settings' => 'Media & Settings',
13+
'system_information' => 'System Information',
14+
],
15+
'fields' => [
16+
'parent_id' => 'Parent Category',
17+
'parent_id_placeholder' => 'Select parent category (optional)',
18+
'name' => 'Name',
19+
'name_placeholder' => 'Enter category name',
20+
'code' => 'Code',
21+
'code_placeholder' => 'Enter category code',
22+
'sef_key' => 'SEF Key',
23+
'sef_key_placeholder' => 'URL-friendly key (auto-generated if empty)',
24+
'sef_key_helper' => 'Leave empty to auto-generate from category name',
25+
'short_desc' => 'Short Description',
26+
'short_desc_placeholder' => 'Enter a brief description',
27+
'description' => 'Full Description',
28+
'description_placeholder' => 'Enter detailed category description',
29+
'image' => 'Category Image',
30+
'is_active' => 'Active',
31+
'is_active_helper' => 'Whether this category is visible',
32+
'recursive_browsing' => 'Recursive Browsing',
33+
'recursive_browsing_helper' => 'Allow browsing subcategories recursively',
34+
'created_at' => 'Created Date',
35+
'updated_at' => 'Last Modified Date',
36+
'not_yet_saved' => 'Not yet saved',
37+
],
38+
'errors' => [
39+
'sef_key' => 'The SEF key has already been taken.',
40+
'parent_id' => 'Cannot select this category as parent - it would create a circular reference',
41+
],
42+
],
43+
44+
'table' => [
45+
'columns' => [
46+
'image' => 'Image',
47+
'name' => 'Category',
48+
'sef_key' => 'SEF Key',
49+
'is_active' => 'Active',
50+
'code' => 'Code',
51+
'recursive_browsing' => 'Recursive',
52+
'description' => 'Long Desc.',
53+
'short_desc' => 'Short Description',
54+
],
55+
'tooltips' => [
56+
'recursive_browsing' => 'Include products from subcategories',
57+
'has_description' => 'Has description',
58+
'no_description' => 'No description',
59+
],
60+
],
61+
62+
'filters' => [
63+
'parent_category' => 'Parent Category',
64+
'category_placeholder' => 'All Categories',
65+
'is_active' => 'Status',
66+
'active' => 'Active',
67+
'inactive' => 'Inactive',
68+
'all_statuses' => 'All Statuses',
69+
'has_description' => 'Has Description',
70+
],
71+
72+
'actions' => [
73+
'create' => 'Create',
74+
'edit' => 'Edit',
75+
'delete' => 'Delete',
76+
'restore' => 'Restore',
77+
'force_delete' => 'Force Delete',
78+
'sorting' => 'Sorting',
79+
'bulk_actions' => 'Bulk Actions',
80+
],
81+
];

resources/lang/en/plugin-template.php

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/CatalogueServiceProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Eclipse\Catalogue;
44

5+
use Eclipse\Catalogue\Models\Category;
56
use Eclipse\Catalogue\Models\Product;
67
use Illuminate\Support\Facades\Config;
78
use Spatie\LaravelPackageTools\Package;
@@ -28,6 +29,7 @@ public function register()
2829

2930
$settings += [
3031
Product::class => Product::getTypesenseSettings(),
32+
Category::class => Category::getTypesenseSettings(),
3133
];
3234

3335
Config::set('scout.typesense.model-settings', $settings);

0 commit comments

Comments
 (0)