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:
@@ -5,33 +5,102 @@
|
||||
@section('page-subtitle', 'Manage your brand partners and contacts')
|
||||
|
||||
@section('content')
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-5">
|
||||
<div class="relative">
|
||||
<input type="text" placeholder="Search clients..." class="input-soft pl-9 text-sm"
|
||||
oninput="filterClients(this.value)" id="search-input">
|
||||
<span class="absolute left-3 top-2.5 text-gray-400 text-sm">🔍</span>
|
||||
|
||||
{{-- ── Filters bar ─────────────────────────────────────────── --}}
|
||||
<div class="flex flex-col gap-3 mb-5">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-3">
|
||||
{{-- Local text filter --}}
|
||||
<div class="relative flex-1 max-w-xs">
|
||||
<span class="absolute left-3 top-2.5 text-gray-400 text-sm">🔍</span>
|
||||
<input type="text" placeholder="Filter by name…" class="input-soft pl-9 text-sm"
|
||||
oninput="filterClients(this.value)" id="search-input">
|
||||
</div>
|
||||
|
||||
{{-- Category filter --}}
|
||||
<form method="GET" action="{{ route('clients.index') }}" class="flex flex-wrap gap-2 items-center" id="filter-form">
|
||||
<select name="category" class="input-soft text-sm py-2"
|
||||
onchange="document.getElementById('filter-form').submit()">
|
||||
<option value="">All Categories</option>
|
||||
@foreach($categories as $parent)
|
||||
<option value="{{ $parent->id }}"
|
||||
{{ request('category') == $parent->id ? 'selected' : '' }}>
|
||||
{{ $parent->icon }} {{ $parent->name }}
|
||||
</option>
|
||||
@foreach($parent->children as $child)
|
||||
<option value="{{ $child->id }}"
|
||||
{{ request('category') == $child->id ? 'selected' : '' }}>
|
||||
↳ {{ $child->icon }} {{ $child->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
<select name="status" class="input-soft text-sm py-2"
|
||||
onchange="document.getElementById('filter-form').submit()">
|
||||
<option value="">All Statuses</option>
|
||||
@foreach($statuses as $s)
|
||||
<option value="{{ $s->value }}" {{ request('status') === $s->value ? 'selected' : '' }}>
|
||||
{{ $s->label() }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
@if(request('category') || request('status'))
|
||||
<a href="{{ route('clients.index') }}" class="text-xs text-gray-400 hover:text-gray-600">✕ Clear</a>
|
||||
@endif
|
||||
</form>
|
||||
|
||||
<a href="{{ route('clients.create') }}" class="btn-sakura text-sm text-center shrink-0">+ Add Client</a>
|
||||
</div>
|
||||
|
||||
{{-- Category pills (top-level only) --}}
|
||||
<div class="flex gap-2 overflow-x-auto pb-1 -mx-4 px-4 lg:mx-0 lg:px-0">
|
||||
<a href="{{ route('clients.index') }}"
|
||||
class="shrink-0 text-xs px-3 py-1.5 rounded-full font-semibold transition-colors
|
||||
{{ ! request('category') ? 'bg-pink-400 text-white' : 'bg-white border border-pink-200 text-gray-600 hover:bg-pink-50' }}">
|
||||
All
|
||||
</a>
|
||||
@foreach($categories as $cat)
|
||||
<a href="{{ route('clients.index', ['category' => $cat->id, 'status' => request('status')]) }}"
|
||||
class="shrink-0 text-xs px-3 py-1.5 rounded-full font-semibold transition-colors whitespace-nowrap
|
||||
{{ request('category') == $cat->id ? 'bg-pink-400 text-white' : 'bg-white border border-pink-200 text-gray-600 hover:bg-pink-50' }}"
|
||||
style="{{ request('category') == $cat->id ? '' : 'border-color:' . $cat->color }}">
|
||||
{{ $cat->icon }} {{ $cat->name }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
<a href="{{ route('clients.create') }}" class="btn-sakura text-sm text-center">+ Add Client</a>
|
||||
</div>
|
||||
|
||||
{{-- ── Client grid ─────────────────────────────────────────── --}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4" id="clients-grid">
|
||||
@forelse($clients as $client)
|
||||
<div class="cat-card p-4 flex flex-col gap-3 hover:shadow-md transition-shadow duration-200"
|
||||
data-name="{{ strtolower($client->name) }}">
|
||||
data-name="{{ strtolower($client->name) }} {{ strtolower($client->company ?? '') }}">
|
||||
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-11 h-11 rounded-2xl flex items-center justify-center text-white font-bold text-base shadow-sm shrink-0"
|
||||
style="background: linear-gradient(135deg, #FFB7C5, #E8D5F5);">
|
||||
style="background: linear-gradient(135deg, {{ $client->category?->color ?? '#FFB7C5' }}, #E8D5F5);">
|
||||
{{ $client->initials }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<h3 class="font-bold text-gray-800 text-sm truncate">{{ $client->name }}</h3>
|
||||
<span class="badge {{ $client->status->badgeClass() }} text-xs shrink-0">{{ $client->status->label() }}</span>
|
||||
<span class="badge {{ $client->status->badgeClass() }} text-xs shrink-0">
|
||||
{{ $client->status->label() }}
|
||||
</span>
|
||||
</div>
|
||||
@if($client->company)
|
||||
<div class="text-xs text-gray-400 truncate">{{ $client->company }}</div>
|
||||
@endif
|
||||
@if($client->category)
|
||||
<div class="text-xs mt-0.5" style="color: {{ $client->category->color === '#FDFD96' ? '#8B8000' : $client->category->color }}">
|
||||
{{ $client->category->icon }} {{ $client->category->name }}
|
||||
@if($client->category->parent)
|
||||
<span class="text-gray-300 mx-0.5">·</span>
|
||||
{{ $client->category->parent->name }}
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,18 +131,28 @@
|
||||
|
||||
<div class="flex gap-2 pt-2 border-t border-pink-50">
|
||||
<a href="{{ route('clients.show', $client) }}" class="btn-sakura text-xs py-1.5 flex-1 text-center">View</a>
|
||||
<a href="{{ route('clients.edit', $client) }}" class="flex-1 text-center text-xs py-1.5 px-3 rounded-full border border-pink-200 text-gray-600 hover:bg-pink-50">Edit</a>
|
||||
<form action="{{ route('clients.destroy', $client) }}" method="POST" onsubmit="return confirm('Remove this client?')">
|
||||
<a href="{{ route('clients.edit', $client) }}"
|
||||
class="flex-1 text-center text-xs py-1.5 px-3 rounded-full border border-pink-200 text-gray-600 hover:bg-pink-50">
|
||||
Edit
|
||||
</a>
|
||||
<form action="{{ route('clients.destroy', $client) }}" method="POST"
|
||||
onsubmit="return confirm('Remove this client?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="text-xs py-1.5 px-3 rounded-full border border-red-100 text-red-400 hover:bg-red-50">Del</button>
|
||||
<button class="text-xs py-1.5 px-3 rounded-full border border-red-100 text-red-400 hover:bg-red-50">
|
||||
Del
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="col-span-full text-center py-16 text-gray-400">
|
||||
<span class="text-5xl block mb-3">🐱</span>
|
||||
<p class="text-base font-medium">No clients yet!</p>
|
||||
<a href="{{ route('clients.create') }}" class="btn-sakura mt-4 inline-block">Add your first client</a>
|
||||
<p class="text-base font-medium">No clients found!</p>
|
||||
@if(request('category') || request('status'))
|
||||
<a href="{{ route('clients.index') }}" class="text-sm text-pink-500 mt-2 block">Clear filters</a>
|
||||
@else
|
||||
<a href="{{ route('clients.create') }}" class="btn-sakura mt-4 inline-block">Add your first client</a>
|
||||
@endif
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user