Files
Kol-Manager/resources/views/clients/index.blade.php
I-Fun 1b90f730a6 feat: add notes management feature with CRUD operations
- Created Note model and controller for handling notes.
- Added routes for notes CRUD operations.
- Implemented notes index view with search and pinning functionality.
- Created migration for notes table with necessary fields.
- Developed event creation form with client and contract options.
2026-04-02 23:24:38 +07:00

188 lines
9.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('layouts.app')
@section('title', 'Clients')
@section('page-title', 'Clients / CRM 👥')
@section('page-subtitle', 'Manage your brand partners and contacts')
@section('content')
{{-- ── Toolbar + collapsible filter ───────────────────────── --}}
<div x-data="{ open: {{ request('category') || request('status') ? 'true' : 'false' }} }">
<div class="flex items-center justify-between gap-2 mb-3">
<div class="flex items-center gap-2">
<a href="{{ route('clients.create') }}" class="btn-sakura text-sm">+ Add</a>
@if(request('category') || request('status'))
<a href="{{ route('clients.index') }}"
class="text-xs px-3 py-2 rounded-full border border-gray-200 text-gray-500 hover:bg-gray-50 flex items-center gap-1">
✕ Clear filters
</a>
@endif
</div>
<button @click="open = !open"
class="flex items-center gap-1.5 text-xs px-3 py-2 rounded-full border transition-colors"
:class="open ? 'border-pink-300 bg-pink-50 text-pink-600' : 'border-gray-200 text-gray-500 hover:bg-gray-50'">
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 4h18M7 10h10M11 16h2"/>
</svg>
Filter
@if(request('category') || request('status'))
<span class="w-2 h-2 rounded-full bg-pink-400 inline-block ml-0.5"></span>
@endif
</button>
</div>
{{-- ── Filters panel (collapsible) ────────────────────────── --}}
<div x-show="open" x-transition.duration.150ms>
<form method="GET" action="{{ route('clients.index') }}" id="filter-form" class="cat-card p-3 mb-4">
{{-- Row 1: search + dropdowns --}}
<div class="flex flex-col sm:flex-row sm:items-center gap-2">
{{-- Search (fills remaining space on desktop) --}}
<div class="relative flex-1 min-w-0">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none">🔍</span>
<input type="text" placeholder="Filter by name…" class="input-soft pl-9 text-sm py-2 w-full"
oninput="filterClients(this.value)" id="search-input">
</div>
{{-- Category dropdown --}}
<select name="category" class="input-soft text-sm py-2 sm:w-48 shrink-0"
onchange="this.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' : '' }}>
&nbsp;&nbsp;↳ {{ $child->name }}
</option>
@endforeach
@endforeach
</select>
{{-- Status dropdown --}}
<select name="status" class="input-soft text-sm py-2 sm:w-36 shrink-0"
onchange="this.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>
</div>
</form>
</div>
</div>{{-- end x-data --}}
{{-- ── 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">
@if($client->avatar)
<div class="w-11 h-11 rounded-2xl overflow-hidden shrink-0 shadow-sm ring-2 ring-pink-100">
<img src="{{ \Illuminate\Support\Facades\Storage::url($client->avatar) }}"
alt="{{ $client->name }}"
class="w-full h-full object-cover">
</div>
@else
<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>
@endif
<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>
{{-- Share button disabled --}}
<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">
✕
</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