- 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.
172 lines
8.7 KiB
PHP
172 lines
8.7 KiB
PHP
@extends('layouts.app')
|
||
|
||
@section('title', 'Clients')
|
||
@section('page-title', 'Clients / CRM 👥')
|
||
@section('page-subtitle', 'Manage your brand partners and contacts')
|
||
|
||
@section('content')
|
||
|
||
{{-- ── 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>
|
||
</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) }} {{ 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, {{ $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>
|
||
</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>
|
||
|
||
<div class="flex flex-wrap gap-1.5">
|
||
@if($client->ig_handle)
|
||
<span class="text-xs bg-pink-50 text-pink-600 px-2 py-0.5 rounded-full">📸 {{ $client->ig_handle }}</span>
|
||
@endif
|
||
@if($client->tiktok_handle)
|
||
<span class="text-xs bg-purple-50 text-purple-600 px-2 py-0.5 rounded-full">🎵 {{ $client->tiktok_handle }}</span>
|
||
@endif
|
||
@if($client->youtube_handle)
|
||
<span class="text-xs bg-red-50 text-red-600 px-2 py-0.5 rounded-full">▶️ {{ $client->youtube_handle }}</span>
|
||
@endif
|
||
</div>
|
||
|
||
<div class="flex gap-3 text-xs text-gray-500">
|
||
<span>📅 {{ $client->events_count }}</span>
|
||
<span>🧾 {{ $client->invoices_count }}</span>
|
||
<span>🎬 {{ $client->content_briefs_count }}</span>
|
||
</div>
|
||
|
||
@if($client->email || $client->city)
|
||
<div class="text-xs text-gray-400 space-y-0.5">
|
||
@if($client->email) <div class="truncate">✉️ {{ $client->email }}</div> @endif
|
||
@if($client->city) <div>📍 {{ $client->city }}{{ $client->country ? ', '.$client->country : '' }}</div> @endif
|
||
</div>
|
||
@endif
|
||
|
||
<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?')">
|
||
@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>
|
||
</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 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>
|
||
|
||
{{ $clients->links() }}
|
||
|
||
@push('scripts')
|
||
<script>
|
||
function filterClients(query) {
|
||
document.querySelectorAll('#clients-grid > div[data-name]').forEach(card => {
|
||
card.style.display = card.dataset.name.includes(query.toLowerCase()) ? '' : 'none';
|
||
});
|
||
}
|
||
</script>
|
||
@endpush
|
||
@endsection
|