diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index 4ad3b9b29..ed454bb77 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -102,6 +102,36 @@ public function loadServices() : asset($default_logo), ] + (array) $service; })->all(); + + // Extract unique categories from services + $categories = collect($services) + ->pluck('category') + ->filter() + ->unique() + ->map(function ($category) { + // Handle multiple categories separated by comma + if (str_contains($category, ',')) { + return collect(explode(',', $category))->map(fn ($cat) => trim($cat)); + } + + return [$category]; + }) + ->flatten() + ->unique() + ->map(function ($category) { + // Format common acronyms to uppercase + $acronyms = ['ai', 'api', 'ci', 'cd', 'cms', 'crm', 'erp', 'iot', 'vpn', 'vps', 'dns', 'ssl', 'tls', 'ssh', 'ftp', 'http', 'https', 'smtp', 'imap', 'pop3', 'sql', 'nosql', 'json', 'xml', 'yaml', 'csv', 'pdf', 'sms', 'mfa', '2fa', 'oauth', 'saml', 'jwt', 'rest', 'soap', 'grpc', 'graphql', 'websocket', 'webrtc', 'p2p', 'b2b', 'b2c', 'seo', 'sem', 'ppc', 'roi', 'kpi', 'ui', 'ux', 'ide', 'sdk', 'api', 'cli', 'gui', 'cdn', 'ddos', 'dos', 'xss', 'csrf', 'sqli', 'rce', 'lfi', 'rfi', 'ssrf', 'xxe', 'idor', 'owasp', 'gdpr', 'hipaa', 'pci', 'dss', 'iso', 'nist', 'cve', 'cwe', 'cvss']; + $lower = strtolower($category); + + if (in_array($lower, $acronyms)) { + return strtoupper($category); + } + + return $category; + }) + ->sort(SORT_NATURAL | SORT_FLAG_CASE) + ->values() + ->all(); $gitBasedApplications = [ [ 'id' => 'public', @@ -202,6 +232,7 @@ public function loadServices() return [ 'services' => $services, + 'categories' => $categories, 'gitBasedApplications' => $gitBasedApplications, 'dockerBasedApplications' => $dockerBasedApplications, 'databases' => $databases, diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index f8925c1ce..277ca99af 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -13,9 +13,55 @@
@if ($current_step === 'type')
- +
+ + +
+ +
+ Filter by category + + + +
+ +
+ + + + +
+ +
+
+ +
+
+
+ All Categories +
+ +
+
+
+
Loading...
@@ -140,6 +186,8 @@ function sortFn(a, b) { function searchResources() { return { search: '', + selectedCategory: '', + categories: [], loading: false, isSticky: false, selecting: false, @@ -156,11 +204,13 @@ function searchResources() { this.loading = true; const { services, + categories, gitBasedApplications, dockerBasedApplications, databases } = await this.$wire.loadServices(); this.services = services; + this.categories = categories || []; this.gitBasedApplications = gitBasedApplications; this.dockerBasedApplications = dockerBasedApplications; this.databases = databases; @@ -171,15 +221,30 @@ function searchResources() { }, filterAndSort(items, isSort = true) { const searchLower = this.search.trim().toLowerCase(); + let filtered = Object.values(items); - if (searchLower === '') { - return isSort ? Object.values(items).sort(sortFn) : Object.values(items); + // Filter by category if selected + if (this.selectedCategory !== '') { + const selectedCategoryLower = this.selectedCategory.toLowerCase(); + filtered = filtered.filter(item => { + if (!item.category) return false; + // Handle comma-separated categories + const categories = item.category.includes(',') + ? item.category.split(',').map(c => c.trim().toLowerCase()) + : [item.category.toLowerCase()]; + return categories.includes(selectedCategoryLower); + }); } - const filtered = Object.values(items).filter(item => { - return (item.name?.toLowerCase().includes(searchLower) || - item.description?.toLowerCase().includes(searchLower) || - item.slogan?.toLowerCase().includes(searchLower)) - }) + + // Filter by search term + if (searchLower !== '') { + filtered = filtered.filter(item => { + return (item.name?.toLowerCase().includes(searchLower) || + item.description?.toLowerCase().includes(searchLower) || + item.slogan?.toLowerCase().includes(searchLower)) + }); + } + return isSort ? filtered.sort(sortFn) : filtered; }, get filteredGitBasedApplications() {