234 lines
12 KiB
PHP
234 lines
12 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', $client->name)
|
|
@section('page-title', $client->name)
|
|
@section('page-subtitle', $client->company ?? $client->email ?? 'Client Profile')
|
|
|
|
@section('content')
|
|
<div class="max-w-5xl mx-auto">
|
|
|
|
{{-- Profile Header --}}
|
|
<div class="cat-card p-4 mb-4"
|
|
x-data="{
|
|
avatarUrl: '{{ $client->avatar ? asset('storage/' . $client->avatar) : '' }}',
|
|
uploading: false,
|
|
async pickAvatar(input) {
|
|
if (!input.files.length) return;
|
|
this.uploading = true;
|
|
const fd = new FormData();
|
|
fd.append('avatar', input.files[0]);
|
|
fd.append('_token', document.querySelector('meta[name=csrf-token]').content);
|
|
try {
|
|
const res = await fetch('{{ route('clients.upload-avatar', $client->id, false) }}', {
|
|
method: 'POST',
|
|
body: fd,
|
|
headers: { 'Accept': 'application/json' }
|
|
});
|
|
const data = await res.json();
|
|
if (res.ok) {
|
|
this.avatarUrl = data.url + '?t=' + Date.now();
|
|
} else {
|
|
alert(data.message || 'Upload failed');
|
|
}
|
|
} catch (err) {
|
|
console.error('Avatar upload error:', err);
|
|
alert('Network Error. Could not upload.');
|
|
}
|
|
this.uploading = false;
|
|
input.value = '';
|
|
},
|
|
async removeAvatar() {
|
|
if (!confirm('Remove profile picture?')) return;
|
|
const fd = new FormData();
|
|
fd.append('_token', document.querySelector('meta[name=csrf-token]').content);
|
|
fd.append('_method', 'DELETE');
|
|
try {
|
|
await fetch('{{ route('clients.delete-avatar', $client->id, false) }}', {
|
|
method: 'POST',
|
|
body: fd,
|
|
headers: { 'Accept': 'application/json' }
|
|
});
|
|
this.avatarUrl = '';
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
}
|
|
}">
|
|
<div class="flex items-start gap-4">
|
|
{{-- Avatar with upload overlay --}}
|
|
<div class="relative shrink-0 group">
|
|
<template x-if="avatarUrl">
|
|
<img :src="avatarUrl" alt="{{ $client->name }}"
|
|
class="w-16 h-16 rounded-3xl object-cover shadow border border-pink-100">
|
|
</template>
|
|
<template x-if="!avatarUrl">
|
|
<div class="w-16 h-16 rounded-3xl flex items-center justify-center text-white font-bold text-xl shadow"
|
|
style="background: linear-gradient(135deg, #FFB7C5, #E8D5F5);">
|
|
{{ $client->initials }}
|
|
</div>
|
|
</template>
|
|
{{-- Upload overlay --}}
|
|
<label class="absolute inset-0 rounded-3xl bg-black/30 flex items-center justify-center
|
|
opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
|
|
:class="{ 'opacity-100': uploading }"
|
|
title="Change photo">
|
|
<span x-show="!uploading" class="text-white text-lg">📷</span>
|
|
<span x-show="uploading" class="text-white text-xs">…</span>
|
|
<input type="file" accept="image/*" class="hidden" @change="pickAvatar($event.target)">
|
|
</label>
|
|
|
|
{{-- Remove Photo Button (Top Left) --}}
|
|
<button x-show="avatarUrl" @click.prevent="removeAvatar()"
|
|
class="absolute -top-2 -left-2 bg-red-100 text-red-500 rounded-full w-6 h-6 flex items-center justify-center hover:bg-red-200 shadow text-xs z-10 border border-white"
|
|
title="Remove Photo">
|
|
✕
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<h2 class="text-lg font-bold text-gray-800 leading-tight">{{ $client->name }}</h2>
|
|
@if($client->company) <p class="text-sm text-gray-500">{{ $client->company }}</p> @endif
|
|
<div class="flex gap-2 mt-2 flex-wrap">
|
|
<span class="badge {{ $client->status->badgeClass() }}">{{ $client->status->label() }}</span>
|
|
@if($client->ig_handle)
|
|
<span class="text-xs bg-pink-50 text-pink-600 px-2 py-1 rounded-full">📸 {{ $client->ig_handle }}</span>
|
|
@endif
|
|
@if($client->tiktok_handle)
|
|
<span class="text-xs bg-purple-50 text-purple-600 px-2 py-1 rounded-full">🎵 {{ $client->tiktok_handle }}</span>
|
|
@endif
|
|
@if($client->youtube_handle)
|
|
<span class="text-xs bg-red-50 text-red-600 px-2 py-1 rounded-full">▶️ {{ $client->youtube_handle }}</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-2 mt-3 flex-wrap">
|
|
<a href="{{ route('clients.edit', $client) }}" class="btn-sakura text-sm px-4 py-2">Edit</a>
|
|
<a href="{{ route('invoices.create') }}?client={{ $client->id }}"
|
|
class="text-sm px-4 py-2 rounded-full border border-pink-200 text-gray-600 hover:bg-pink-50 font-semibold">
|
|
🧾 New Invoice
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
|
|
{{-- Contact Info --}}
|
|
<div class="cat-card p-4">
|
|
<h3 class="font-bold text-gray-700 mb-3 text-sm flex items-center gap-2">📋 Contact Info</h3>
|
|
<div class="space-y-2 text-sm">
|
|
@if($client->email)
|
|
<div class="flex gap-2 items-start">
|
|
<span class="text-gray-400 shrink-0">✉️</span>
|
|
<a href="mailto:{{ $client->email }}" class="text-pink-500 hover:underline break-all">{{ $client->email }}</a>
|
|
</div>
|
|
@endif
|
|
@if($client->phone)
|
|
<div class="flex gap-2 items-start">
|
|
<span class="text-gray-400 shrink-0">📞</span>
|
|
<span>{{ $client->phone }}</span>
|
|
</div>
|
|
@endif
|
|
@if($client->full_address)
|
|
<div class="flex gap-2 items-start">
|
|
<span class="text-gray-400 shrink-0">📍</span>
|
|
<span class="text-gray-600">{!! nl2br(e($client->full_address)) !!}</span>
|
|
</div>
|
|
@endif
|
|
@if($client->notes)
|
|
<div class="mt-3 pt-3 border-t border-pink-50">
|
|
<p class="text-xs font-semibold text-gray-500 mb-1">Notes</p>
|
|
<p class="text-gray-600 text-xs">{{ $client->notes }}</p>
|
|
</div>
|
|
@endif
|
|
@if(!$client->email && !$client->phone && !$client->full_address && !$client->notes)
|
|
<p class="text-xs text-gray-400">No contact info yet.</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Recent Events --}}
|
|
<div class="cat-card p-4">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h3 class="font-bold text-gray-700 text-sm flex items-center gap-2">📅 Events</h3>
|
|
<a href="{{ route('schedule.index') }}" class="text-xs text-pink-500">All →</a>
|
|
</div>
|
|
@forelse($client->events as $event)
|
|
<div class="py-2 border-b border-pink-50 last:border-0 text-sm">
|
|
<div class="font-medium text-gray-700 truncate">{{ $event->title }}</div>
|
|
<div class="text-xs text-gray-400">{{ $event->start_time->format('d M Y, H:i') }}</div>
|
|
</div>
|
|
@empty
|
|
<p class="text-xs text-gray-400">No events yet.</p>
|
|
@endforelse
|
|
</div>
|
|
|
|
{{-- Recent Invoices --}}
|
|
<div class="cat-card p-4">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h3 class="font-bold text-gray-700 text-sm flex items-center gap-2">🧾 Invoices</h3>
|
|
<a href="{{ route('invoices.index') }}" class="text-xs text-pink-500">All →</a>
|
|
</div>
|
|
@forelse($client->invoices as $invoice)
|
|
<div class="py-2 border-b border-pink-50 last:border-0 text-sm flex justify-between gap-2">
|
|
<div class="min-w-0">
|
|
<div class="font-medium text-gray-700 truncate">{{ $invoice->invoice_number }}</div>
|
|
<div class="text-xs text-gray-400">Due {{ $invoice->due_date->format('d M Y') }}</div>
|
|
</div>
|
|
<div class="text-right shrink-0">
|
|
<div class="font-bold text-gray-700 text-xs">{{ $invoice->formatted_total }}</div>
|
|
<span class="badge {{ $invoice->status->badgeClass() }} badge-xs">{{ $invoice->status->label() }}</span>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<p class="text-xs text-gray-400">No invoices yet.</p>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Content Briefs --}}
|
|
@if($client->contentBriefs->isNotEmpty())
|
|
<div class="cat-card p-4 mt-4">
|
|
<h3 class="font-bold text-gray-700 mb-3 text-sm flex items-center gap-2">🎬 Content Briefs</h3>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
@foreach($client->contentBriefs as $brief)
|
|
<a href="{{ route('content.show', $brief) }}"
|
|
class="bg-pink-50 rounded-2xl p-3 hover:bg-pink-100 transition-colors block">
|
|
<div class="text-xs font-bold text-pink-600 flex items-center gap-1">
|
|
{{ $brief->platform_icon }} {{ ucfirst($brief->platform) }}
|
|
</div>
|
|
<div class="font-semibold text-gray-700 text-sm truncate mt-1">{{ $brief->title }}</div>
|
|
<div class="text-xs text-gray-400 mt-0.5">{{ $brief->status->label() }}</div>
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Uploaded Files --}}
|
|
@if($client->mediaFiles->isNotEmpty())
|
|
<div class="cat-card p-4 mt-4">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h3 class="font-bold text-gray-700 text-sm flex items-center gap-2">📁 Files</h3>
|
|
<a href="{{ route('files.index') }}?client_id={{ $client->id }}" class="text-xs text-pink-500">All Files →</a>
|
|
</div>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
@foreach($client->mediaFiles as $file)
|
|
<a href="{{ route('files.serve', $file) }}" target="_blank"
|
|
class="bg-blue-50 rounded-2xl p-3 hover:bg-blue-100 transition-colors flex items-center gap-3">
|
|
<div class="shrink-0 text-xl border border-blue-200 rounded-lg w-10 h-10 flex items-center justify-center bg-white">
|
|
📄
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<div class="font-semibold text-gray-700 text-sm truncate" title="{{ $file->name }}">{{ $file->name }}</div>
|
|
<div class="text-xs text-gray-400 mt-0.5">{{ number_format($file->size / 1024, 1) }} KB</div>
|
|
</div>
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endsection
|