diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index cfa94fe..73943ab 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -94,13 +94,11 @@ class ClientController extends Controller public function show(Client $client): View { - $client->load([ - 'category', - 'events' => fn ($q) => $q->latest()->limit(5), - 'invoices' => fn ($q) => $q->latest()->limit(5), - 'contentBriefs'=> fn ($q) => $q->latest()->limit(5), - 'mediaFiles' => fn ($q) => $q->latest()->limit(10), - ]); + $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')); } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 785953f..bf90314 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -28,10 +28,9 @@ class InvoiceController extends Controller public function create(): View { - $clients = Client::where('status', 'active')->orderBy('name')->get(); - $statuses = InvoiceStatus::cases(); - $invoiceSettings = InvoiceSettings::instance(); - return view('invoices.create', compact('clients', 'statuses', 'invoiceSettings')); + $clients = Client::where('status', 'active')->orderBy('name')->get(); + $statuses = InvoiceStatus::cases(); + return view('invoices.create', compact('clients', 'statuses')); } public function store(Request $request): RedirectResponse @@ -84,17 +83,15 @@ class InvoiceController extends Controller public function show(Invoice $invoice): View { $invoice->load('client', 'items'); - $invoiceSettings = InvoiceSettings::instance(); - return view('invoices.show', compact('invoice', 'invoiceSettings')); + return view('invoices.show', compact('invoice')); } public function edit(Invoice $invoice): View { - $clients = Client::where('status', 'active')->orderBy('name')->get(); - $statuses = InvoiceStatus::cases(); - $invoiceSettings = InvoiceSettings::instance(); + $clients = Client::where('status', 'active')->orderBy('name')->get(); + $statuses = InvoiceStatus::cases(); $invoice->load('client', 'items'); - return view('invoices.edit', compact('invoice', 'clients', 'statuses', 'invoiceSettings')); + return view('invoices.edit', compact('invoice', 'clients', 'statuses')); } public function update(Request $request, Invoice $invoice): RedirectResponse diff --git a/app/Http/Controllers/RateCardController.php b/app/Http/Controllers/RateCardController.php index 321ca18..2ce27ca 100644 --- a/app/Http/Controllers/RateCardController.php +++ b/app/Http/Controllers/RateCardController.php @@ -17,8 +17,7 @@ class RateCardController extends Controller public function index(): View { $rateCards = RateCard::orderBy('sort_order')->orderBy('platform')->get(); - $profile = RateCardProfile::getSingleton(); - return view('rate-cards.index', compact('rateCards', 'profile')); + return view('rate-cards.index', compact('rateCards')); } public function create(): View @@ -88,23 +87,21 @@ class RateCardController extends Controller public function public(): View { - $profile = RateCardProfile::getSingleton(); $rateCards = RateCard::where('is_active', true) ->orderBy('sort_order') ->orderBy('platform') ->get() ->groupBy('platform'); - return view('rate-cards.public', compact('rateCards', 'profile')); + return view('rate-cards.public', compact('rateCards')); } // ── Profile Settings ───────────────────────────────────────── public function profilePage(): View { - $profile = RateCardProfile::getSingleton(); $mediaFiles = \App\Models\MediaFile::latest()->get(); - return view('rate-cards.profile', compact('profile', 'mediaFiles')); + return view('rate-cards.profile', compact('mediaFiles')); } public function profileUpdate(Request $request): RedirectResponse @@ -143,7 +140,7 @@ class RateCardController extends Controller } $validated['show_prices'] = $request->has('show_prices'); - $contentShowcase = $request->input('content_showcase', []); + $contentShowcase = $validated['content_showcase'] ?? []; foreach ($contentShowcase as &$item) { $item['show_on_public'] = filter_var($item['show_on_public'] ?? true, FILTER_VALIDATE_BOOLEAN); $item['show_on_pdf'] = filter_var($item['show_on_pdf'] ?? true, FILTER_VALIDATE_BOOLEAN); @@ -206,6 +203,7 @@ class RateCardController extends Controller public function deleteBrandLogo(Request $request) { + $request->validate(['path' => 'required|string']); $path = $request->input('path'); $profile = RateCardProfile::getSingleton(); $brands = $profile->past_brands ?? []; diff --git a/app/Models/InvoiceSettings.php b/app/Models/InvoiceSettings.php index d516f5e..c0c5d9d 100644 --- a/app/Models/InvoiceSettings.php +++ b/app/Models/InvoiceSettings.php @@ -35,25 +35,39 @@ class InvoiceSettings extends Model 'default_tax_rate' => 'decimal:2', ]; + protected static ?self $instance = null; + /** Always return the single settings row, creating it if absent. */ public static function instance(): self { - return static::firstOrCreate([], [ + if (self::$instance) { + return self::$instance; + } + + self::$instance = static::firstOrCreate([], [ 'invoice_prefix' => 'INV', 'invoice_next_number' => 1, 'accent_color' => '#FFB7C5', 'default_tax_rate' => 2.5, 'default_currency' => 'IDR', ]); + + return self::$instance; + } + + protected static function booted() + { + static::saved(fn ($model) => self::$instance = $model); + static::deleted(fn () => self::$instance = null); } - /** Generate the next invoice number and increment the counter. */ public static function generateNumber(): string { $settings = static::instance(); $prefix = rtrim($settings->invoice_prefix, '-'); $number = str_pad((string) $settings->invoice_next_number, 3, '0', STR_PAD_LEFT); $settings->increment('invoice_next_number'); + \Illuminate\Support\Facades\Cache::forget('invoice_settings'); return $prefix . '-' . $number; } } diff --git a/app/Models/RateCardProfile.php b/app/Models/RateCardProfile.php index 1e368ae..bc1f9be 100644 --- a/app/Models/RateCardProfile.php +++ b/app/Models/RateCardProfile.php @@ -26,16 +26,30 @@ class RateCardProfile extends Model 'content_showcase' => 'array', ]; + protected static ?self $instance = null; + /** Get the singleton profile, creating defaults if needed */ public static function getSingleton(): self { - return static::firstOrCreate([], [ + if (self::$instance) { + return self::$instance; + } + + self::$instance = static::firstOrCreate([], [ 'owner_name' => config('app.name', 'Cathy Page'), 'bg_color' => '#FFF8F0', 'accent_color' => '#FFB7C5', 'text_color' => '#374151', 'show_prices' => true, ]); + + return self::$instance; + } + + protected static function booted() + { + static::saved(fn ($model) => self::$instance = $model); + static::deleted(fn () => self::$instance = null); } public function getWhatsappUrlAttribute(): ?string diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8a9a502..4789468 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -16,6 +16,13 @@ class AppServiceProvider extends ServiceProvider { // Inject alert badge count into all views View::composer('*', function (\Illuminate\View\View $view): void { + if (!$view->offsetExists('profile')) { + $view->with('profile', \App\Models\RateCardProfile::getSingleton()); + } + if (!$view->offsetExists('invoiceSettings')) { + $view->with('invoiceSettings', \App\Models\InvoiceSettings::instance()); + } + try { $alertCount = Event::upcoming(24)->count(); } catch (\Throwable) { diff --git a/resources/views/rate-cards/pdf.blade.php b/resources/views/rate-cards/pdf.blade.php index 27f3479..08a0354 100644 --- a/resources/views/rate-cards/pdf.blade.php +++ b/resources/views/rate-cards/pdf.blade.php @@ -315,23 +315,23 @@ $file = !$isUrl ? \App\Models\MediaFile::find($item['file_id']) : null; @endphp @if($file || $isUrl) -
-
+
+
@if(!empty($item['thumbnail_path']) && file_exists(storage_path('app/public/' . $item['thumbnail_path']))) - +
@if(!$isUrl && $file->is_video)
-
▶
+
▶
@endif
@elseif(!$isUrl && $file->is_image && file_exists(storage_path('app/private/' . $file->path))) - +
@else - + {{ $isUrl ? '🌐' : ($file->is_video ? '🎬' : '📁') }} {{ $isUrl ? 'Open Link' : 'View File' }} diff --git a/resources/views/rate-cards/public.blade.php b/resources/views/rate-cards/public.blade.php index b6c5e4e..79972ab 100644 --- a/resources/views/rate-cards/public.blade.php +++ b/resources/views/rate-cards/public.blade.php @@ -111,6 +111,63 @@ gap: 14px; } + .carousel-wrapper { + position: relative; + } + + .carousel-track { + display: flex; + overflow-x: auto; + scroll-snap-type: x mandatory; + gap: 14px; + padding-bottom: 10px; + scrollbar-width: none; + } + + .carousel-track::-webkit-scrollbar { + display: none; + } + + .carousel-slide { + flex: 0 0 220px; + scroll-snap-align: start; + } + + .carousel-btn { + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 40px; + height: 40px; + border-radius: 50%; + background: white; + border: 1px solid rgba(0,0,0,0.05); + box-shadow: 0 4px 10px rgba(0,0,0,0.1); + color: + {{ $profile->accent_color }} + ; + font-size: 20px; + font-weight: 900; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + z-index: 10; + } + + .carousel-btn:hover { + background: #fdfdfd; + box-shadow: 0 6px 14px rgba(0,0,0,0.15); + } + + .carousel-btn.prev { + left: -20px; + } + + .carousel-btn.next { + right: -20px; + } + .card { background: white; border-radius: 24px; @@ -123,6 +180,17 @@ 22; } + .popup-modal { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.8); + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + } + .service { font-weight: 700; color: #1F2937; @@ -337,22 +405,32 @@ @if(!empty($publicShowcase))
Content Showcase
-
+ @php $isCarousel = count($publicShowcase) >= 3; @endphp + @if($isCarousel) +