feat: Add notification services for Email, Telegram, and WhatsApp
- Implement EmailNotifier for sending batch and test notifications via email. - Implement TelegramNotifier for sending batch and test notifications via Telegram. - Implement WahaNotifier for sending batch and test notifications via WhatsApp using WAHA API. - Create migration for client_categories table and update clients table to include category_id. - Create migration for notification_settings table to manage notification configurations. - Add ClientCategorySeeder to populate default client categories. - Create manifest.json for PWA support. - Implement service worker (sw.js) for caching static assets and offline support. - Add edit event view for modifying scheduled events. - Create search results view for displaying search queries. - Develop notification settings view for configuring notification channels and triggers.
This commit is contained in:
143
app/Http/Controllers/SearchController.php
Normal file
143
app/Http/Controllers/SearchController.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\ContentBrief;
|
||||
use App\Models\Event;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\RateCard;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$q = trim($request->get('q', ''));
|
||||
$results = $q ? $this->search($q) : [];
|
||||
|
||||
return view('search.index', compact('q', 'results'));
|
||||
}
|
||||
|
||||
/** Quick AJAX endpoint — returns grouped results for the dropdown */
|
||||
public function quick(Request $request): JsonResponse
|
||||
{
|
||||
$q = trim($request->get('q', ''));
|
||||
|
||||
if (strlen($q) < 2) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
return response()->json($this->search($q, limit: 5));
|
||||
}
|
||||
|
||||
private function search(string $q, int $limit = 20): array
|
||||
{
|
||||
$groups = [];
|
||||
$like = "%{$q}%";
|
||||
|
||||
// ── Clients ──────────────────────────────────────────────
|
||||
$clients = Client::where('name', 'LIKE', $like)
|
||||
->orWhere('email', 'LIKE', $like)
|
||||
->orWhere('company', 'LIKE', $like)
|
||||
->orWhere('ig_handle', 'LIKE', $like)
|
||||
->orWhere('tiktok_handle', 'LIKE', $like)
|
||||
->limit($limit)->get();
|
||||
|
||||
if ($clients->isNotEmpty()) {
|
||||
$groups[] = [
|
||||
'type' => 'clients',
|
||||
'label' => '👥 Clients',
|
||||
'items' => $clients->map(fn ($c) => [
|
||||
'icon' => '👥',
|
||||
'title' => $c->name,
|
||||
'subtitle' => collect([$c->company, $c->email])->filter()->implode(' · '),
|
||||
'url' => route('clients.show', $c),
|
||||
])->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
// ── Events ───────────────────────────────────────────────
|
||||
$events = Event::with('client')
|
||||
->where('title', 'LIKE', $like)
|
||||
->orWhere('description', 'LIKE', $like)
|
||||
->orWhere('location', 'LIKE', $like)
|
||||
->limit($limit)->get();
|
||||
|
||||
if ($events->isNotEmpty()) {
|
||||
$groups[] = [
|
||||
'type' => 'events',
|
||||
'label' => '📅 Events',
|
||||
'items' => $events->map(fn ($e) => [
|
||||
'icon' => '📅',
|
||||
'title' => $e->title,
|
||||
'subtitle' => $e->start_time->format('d M Y') . ($e->client ? ' · ' . $e->client->name : ''),
|
||||
'url' => route('schedule.index'),
|
||||
])->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
// ── Invoices ─────────────────────────────────────────────
|
||||
$invoices = Invoice::with('client')
|
||||
->where('invoice_number', 'LIKE', $like)
|
||||
->orWhere('title', 'LIKE', $like)
|
||||
->limit($limit)->get();
|
||||
|
||||
if ($invoices->isNotEmpty()) {
|
||||
$groups[] = [
|
||||
'type' => 'invoices',
|
||||
'label' => '🧾 Invoices',
|
||||
'items' => $invoices->map(fn ($i) => [
|
||||
'icon' => '🧾',
|
||||
'title' => $i->invoice_number . ($i->title ? ' — ' . $i->title : ''),
|
||||
'subtitle' => ($i->client?->name ?? '') . ' · ' . ($i->formatted_total ?? ''),
|
||||
'url' => route('invoices.show', $i),
|
||||
])->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
// ── Content Briefs ───────────────────────────────────────
|
||||
$content = ContentBrief::with('client')
|
||||
->where('title', 'LIKE', $like)
|
||||
->orWhere('description', 'LIKE', $like)
|
||||
->orWhere('platform', 'LIKE', $like)
|
||||
->limit($limit)->get();
|
||||
|
||||
if ($content->isNotEmpty()) {
|
||||
$groups[] = [
|
||||
'type' => 'content',
|
||||
'label' => '🎬 Content',
|
||||
'items' => $content->map(fn ($c) => [
|
||||
'icon' => $c->platform_icon ?? '🎬',
|
||||
'title' => $c->title,
|
||||
'subtitle' => ucfirst($c->platform) . ($c->client ? ' · ' . $c->client->name : ''),
|
||||
'url' => route('content.show', $c),
|
||||
])->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
// ── Rate Cards ───────────────────────────────────────────
|
||||
$rateCards = RateCard::where('name', 'LIKE', $like)
|
||||
->orWhere('description', 'LIKE', $like)
|
||||
->limit($limit)->get();
|
||||
|
||||
if ($rateCards->isNotEmpty()) {
|
||||
$groups[] = [
|
||||
'type' => 'rate-cards',
|
||||
'label' => '💰 Rate Cards',
|
||||
'items' => $rateCards->map(fn ($r) => [
|
||||
'icon' => '💰',
|
||||
'title' => $r->name,
|
||||
'subtitle' => $r->description ?? '',
|
||||
'url' => route('rate-cards.edit', $r),
|
||||
])->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user