whereNull('parent_id') ->orderBy('sort_order') ->get(); } private function flatCategories(): \Illuminate\Support\Collection { $flat = collect(); foreach ($this->categoryTree() as $parent) { $flat->push($parent); foreach ($parent->children as $child) { $flat->push($child); } } return $flat; } public function index(Request $request): View { $query = Client::with('category') ->withCount(['invoices', 'events', 'contentBriefs', 'mediaFiles']) ->latest(); if ($request->filled('category')) { $query->where('category_id', $request->category); } if ($request->filled('status')) { $query->where('status', $request->status); } $clients = $query->paginate(12)->withQueryString(); $categories = $this->categoryTree(); $statuses = ClientStatus::cases(); return view('clients.index', compact('clients', 'categories', 'statuses')); } public function create(): View { $statuses = ClientStatus::cases(); $categories = $this->flatCategories(); $categoryTree = $this->categoryTree(); return view('clients.create', compact('statuses', 'categories', 'categoryTree')); } public function store(Request $request): RedirectResponse { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'nullable|email|unique:clients,email', 'phone' => 'nullable|string|max:50', 'company' => 'nullable|string|max:255', 'ig_handle' => 'nullable|string|max:100', 'tiktok_handle' => 'nullable|string|max:100', 'youtube_handle' => 'nullable|string|max:100', 'address_line1' => 'nullable|string|max:255', 'address_line2' => 'nullable|string|max:255', 'city' => 'nullable|string|max:100', 'state' => 'nullable|string|max:100', 'postal_code' => 'nullable|string|max:20', 'country' => 'nullable|string|max:100', 'status' => 'required|in:active,inactive,prospect', 'category_id' => 'nullable|exists:client_categories,id', 'notes' => 'nullable|string', ]); Client::create($validated); return redirect()->route('clients.index') ->with('success', '🐱 Client added successfully!'); } public function show(Client $client): View { $client->load('category'); $client->setRelation('events', $client->events()->latest()->limit(5)->get()); $client->setRelation('invoices', $client->invoices()->latest()->limit(5)->get()); $client->setRelation('contentBriefs', $client->contentBriefs()->latest()->limit(5)->get()); $client->setRelation('mediaFiles', $client->mediaFiles()->latest()->limit(10)->get()); return view('clients.show', compact('client')); } public function edit(Client $client): View { $statuses = ClientStatus::cases(); $categories = $this->flatCategories(); $categoryTree = $this->categoryTree(); return view('clients.edit', compact('client', 'statuses', 'categories', 'categoryTree')); } public function update(Request $request, Client $client): RedirectResponse { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'nullable|email|unique:clients,email,' . $client->id, 'phone' => 'nullable|string|max:50', 'company' => 'nullable|string|max:255', 'ig_handle' => 'nullable|string|max:100', 'tiktok_handle' => 'nullable|string|max:100', 'youtube_handle' => 'nullable|string|max:100', 'address_line1' => 'nullable|string|max:255', 'address_line2' => 'nullable|string|max:255', 'city' => 'nullable|string|max:100', 'state' => 'nullable|string|max:100', 'postal_code' => 'nullable|string|max:20', 'country' => 'nullable|string|max:100', 'status' => 'required|in:active,inactive,prospect', 'category_id' => 'nullable|exists:client_categories,id', 'notes' => 'nullable|string', ]); $client->update($validated); return redirect()->route('clients.show', $client) ->with('success', '🐱 Client updated successfully!'); } public function destroy(Client $client): RedirectResponse { $client->delete(); return redirect()->route('clients.index') ->with('success', '🐱 Client removed.'); } public function quickStore(Request $request): JsonResponse { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'nullable|email|unique:clients,email', 'phone' => 'nullable|string|max:50', 'company' => 'nullable|string|max:255', ]); $client = Client::create(array_merge($validated, ['status' => 'active'])); return response()->json(['id' => $client->id, 'name' => $client->name]); } public function uploadAvatar(Request $request, Client $client): JsonResponse { $request->validate(['avatar' => 'required|image|max:2048']); if ($client->avatar) { Storage::disk('public')->delete($client->avatar); } $path = $request->file('avatar')->store('client-avatars', 'public'); $client->update(['avatar' => $path]); return response()->json(['url' => asset('storage/' . $path)]); } public function deleteAvatar(Client $client): JsonResponse { if ($client->avatar) { Storage::disk('public')->delete($client->avatar); $client->update(['avatar' => null]); } return response()->json(['ok' => true]); } }