refactor: improve UI responsiveness, add currency formatting to invoice inputs, update address formatting, and refine Docker setup instructions.

This commit is contained in:
I-Fun
2026-04-05 22:37:51 +07:00
parent 1c2c10d253
commit 59a2724057
11 changed files with 70 additions and 47 deletions

View File

@@ -1,6 +1,14 @@
git clone this repository # On local machine
#git clone this repository
composer install composer install
#php artisan key:generate php artisan key:generate
touch database/database.sqlite && php artisan migrate touch database/database.sqlite && php artisan migrate
npm install npm install
npm run build npm run build
# On Docker
cp .env.example .env.docker
docker compose build
docker compose up -d

View File

@@ -57,14 +57,19 @@ class Client extends Model
public function getFullAddressAttribute(): string public function getFullAddressAttribute(): string
{ {
$line3_parts = array_filter([$this->city, $this->state]);
$country_part = array_filter([$this->postal_code, $this->country]);
$country_str = implode(' - ', $country_part);
if ($country_str) {
$line3_parts[] = $country_str;
}
$line3 = implode(', ', $line3_parts);
return collect([ return collect([
$this->address_line1, $this->address_line1,
$this->address_line2, $this->address_line2,
$this->city, $line3
$this->state, ])->filter()->implode("\n");
$this->postal_code,
$this->country,
])->filter()->implode(', ');
} }
public function getInitialsAttribute(): string public function getInitialsAttribute(): string

View File

@@ -133,7 +133,7 @@
@if($client->full_address) @if($client->full_address)
<div class="flex gap-2 items-start"> <div class="flex gap-2 items-start">
<span class="text-gray-400 shrink-0">📍</span> <span class="text-gray-400 shrink-0">📍</span>
<span class="text-gray-600">{{ $client->full_address }}</span> <span class="text-gray-600">{!! nl2br(e($client->full_address)) !!}</span>
</div> </div>
@endif @endif
@if($client->notes) @if($client->notes)

View File

@@ -8,19 +8,6 @@
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-5"> <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-5">
<div class="flex gap-2 flex-wrap"> <div class="flex gap-2 flex-wrap">
<a href="{{ route('content.create') }}" class="btn-sakura text-sm shrink-0">+ New Brief</a> <a href="{{ route('content.create') }}" class="btn-sakura text-sm shrink-0">+ New Brief</a>
@foreach(['instagram', 'tiktok', 'youtube'] as $p)
<a href="{{ request()->fullUrlWithQuery(['platform' => $p]) }}"
class="px-3 py-1.5 rounded-full text-xs font-semibold transition-colors
{{ request('platform') === $p ? 'text-white' : 'bg-white text-gray-500 hover:bg-pink-50 border border-pink-100' }}"
style="{{ request('platform') === $p ? 'background:#FFB7C5' : '' }}">
{{ ['instagram' => '📸', 'tiktok' => '🎵', 'youtube' => '▶️'][$p] }} {{ ucfirst($p) }}
</a>
@endforeach
@if(request('platform'))
<a href="{{ route('content.index') }}"
class="px-3 py-1.5 rounded-full text-xs text-gray-400 hover:text-gray-600 border border-gray-200 bg-white">✕
Clear</a>
@endif
</div> </div>
</div> </div>
@@ -36,8 +23,19 @@
@php $status = $col['status']; @endphp @php $status = $col['status']; @endphp
{{-- Column shell — layout only, NOT the sort target --}} {{-- Column shell — layout only, NOT the sort target --}}
@php
$bgColors = [
'drafting' => '#f2f2f2',
'filming' => 'rgba(251, 189, 35, 0.2)',
'editing' => 'rgba(58, 191, 248, 0.2)',
'review' => 'rgba(240, 0, 184, 0.2)',
'posted' => 'rgba(54, 211, 153, 0.2)',
'archived' => 'rgba(42, 50, 60, 0.2)',
];
$bgStyle = isset($bgColors[$status->value]) ? 'background-color: ' . $bgColors[$status->value] . ';' : '';
@endphp
<div class="{{ $status->colorClass() }} flex flex-col w-full lg:flex-1 lg:min-w-36 rounded-2xl p-3" <div class="{{ $status->colorClass() }} flex flex-col w-full lg:flex-1 lg:min-w-36 rounded-2xl p-3"
data-status="{{ $status->value }}"> style="{{ $bgStyle }}" data-status="{{ $status->value }}">
{{-- Column header --}} {{-- Column header --}}
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">

View File

@@ -78,9 +78,16 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<label class="text-xs text-gray-400 block mb-0.5">Unit Price</label> <label class="text-xs text-gray-400 block mb-0.5">Unit Price</label>
<input type="number" :name="`items[${index}][unit_price]`" <input type="hidden" :name="`items[${index}][unit_price]`" :value="item.unit_price">
x-model.number="item.unit_price" @input="recalc()" <input type="text"
class="input-soft text-sm bg-white" min="0" step="1000" placeholder="0"> :value="item.unit_price ? formatNum(item.unit_price) : ''"
@input="
let raw = $event.target.value.replace(/\D/g, '');
item.unit_price = raw ? parseInt(raw, 10) : 0;
$event.target.value = item.unit_price ? formatNum(item.unit_price) : '';
recalc();
"
class="input-soft text-sm bg-white" placeholder="0">
</div> </div>
<div class="w-24 shrink-0 text-right"> <div class="w-24 shrink-0 text-right">
<label class="text-xs text-gray-400 block mb-0.5">Total</label> <label class="text-xs text-gray-400 block mb-0.5">Total</label>

View File

@@ -75,9 +75,16 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<label class="text-xs text-gray-400 block mb-0.5">Unit Price</label> <label class="text-xs text-gray-400 block mb-0.5">Unit Price</label>
<input type="number" :name="`items[${index}][unit_price]`" <input type="hidden" :name="`items[${index}][unit_price]`" :value="item.unit_price">
x-model.number="item.unit_price" @input="recalc()" <input type="text"
class="input-soft text-sm bg-white" min="0" step="1000"> :value="item.unit_price ? formatNum(item.unit_price) : ''"
@input="
let raw = $event.target.value.replace(/\D/g, '');
item.unit_price = raw ? parseInt(raw, 10) : 0;
$event.target.value = item.unit_price ? formatNum(item.unit_price) : '';
recalc();
"
class="input-soft text-sm bg-white" placeholder="0">
</div> </div>
<div class="w-24 shrink-0 text-right"> <div class="w-24 shrink-0 text-right">
<label class="text-xs text-gray-400 block mb-0.5">Total</label> <label class="text-xs text-gray-400 block mb-0.5">Total</label>

View File

@@ -271,7 +271,7 @@
@if($invoice->client->email) @if($invoice->client->email)
<div class="billing-detail">{{ $invoice->client->email }}</div> @endif <div class="billing-detail">{{ $invoice->client->email }}</div> @endif
@if($invoice->client->full_address) @if($invoice->client->full_address)
<div class="billing-detail">{{ $invoice->client->full_address }}</div> @endif <div class="billing-detail">{!! nl2br(e($invoice->client->full_address)) !!}</div> @endif
@else @else
<div class="billing-detail">—</div> <div class="billing-detail">—</div>
@endif @endif

View File

@@ -53,7 +53,7 @@
@if($invoice->client->email) @if($invoice->client->email)
<div class="text-sm text-gray-500">{{ $invoice->client->email }}</div> @endif <div class="text-sm text-gray-500">{{ $invoice->client->email }}</div> @endif
@if($invoice->client->full_address) @if($invoice->client->full_address)
<div class="text-sm text-gray-400">{{ $invoice->client->full_address }}</div> @endif <div class="text-sm text-gray-400">{!! nl2br(e($invoice->client->full_address)) !!}</div> @endif
@else @else
<div class="text-gray-500 text-sm">No client</div> <div class="text-gray-500 text-sm">No client</div>
@endif @endif

View File

@@ -6,18 +6,18 @@
@section('content') @section('content')
<div class="flex flex-wrap items-center justify-between gap-3 mb-5"> <div class="flex flex-wrap items-center justify-between gap-3 mb-5">
<div class="flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2 w-full">
<a href="{{ route('rate-cards.create') }}" class="btn-sakura text-sm shrink-0">+ Add Service</a> <a href="{{ route('rate-cards.create') }}" class="btn-sakura text-xs sm:text-sm shrink-0 px-3 py-1.5 sm:px-4 sm:py-2 flex items-center gap-1">+ Add Service</a>
<a href="{{ route('rate-cards.public') }}" target="_blank" <a href="{{ route('rate-cards.public') }}" target="_blank"
class="btn-sakura text-sm px-3 py-2 rounded-full border border-pink-200 text-pink-500 hover:bg-pink-50 flex items-center gap-1"> class="bg-white text-xs sm:text-sm px-3 py-1.5 sm:px-4 sm:py-2 rounded-full border border-pink-200 text-pink-600 hover:bg-pink-50 flex items-center gap-1 shadow-sm shrink-0">
🔗 Public Page 🔗 Public Page
</a> </a>
<a href="{{ route('rate-cards.pdf') }}" <a href="{{ route('rate-cards.pdf') }}"
class="btn-sakura text-sm px-3 py-2 rounded-full border border-blue-200 text-blue-500 hover:bg-blue-50 flex items-center gap-1"> class="bg-white text-xs sm:text-sm px-3 py-1.5 sm:px-4 sm:py-2 rounded-full border border-blue-200 text-blue-600 hover:bg-blue-50 flex items-center gap-1 shadow-sm shrink-0">
📄 PDF 📄 PDF
</a> </a>
<a href="{{ route('rate-cards.profile') }}" <a href="{{ route('rate-cards.profile') }}"
class="btn-sakura text-sm px-3 py-2 rounded-full border border-purple-200 text-purple-500 hover:bg-purple-50 flex items-center gap-1"> class="bg-white text-xs sm:text-sm px-3 py-1.5 sm:px-4 sm:py-2 rounded-full border border-purple-200 text-purple-600 hover:bg-purple-50 flex items-center gap-1 shadow-sm shrink-0">
🎨 Profile 🎨 Profile
</a> </a>
</div> </div>

View File

@@ -282,8 +282,6 @@
</div> </div>
</div> </div>
</div>
{{-- Past Brands --}} {{-- Past Brands --}}
<div class="cat-card overflow-hidden"> <div class="cat-card overflow-hidden">
<div class="flex items-center gap-3 px-5 py-4 border-b border-pink-50"> <div class="flex items-center gap-3 px-5 py-4 border-b border-pink-50">
@@ -356,18 +354,18 @@
</div> </div>
{{-- Actions --}} {{-- Actions --}}
<div class="flex flex-wrap items-center gap-3"> <div class="flex flex-wrap items-center gap-2 w-full">
<button type="submit" class="btn-sakura px-6 py-2.5">💾 Save Profile</button> <button type="submit" class="btn-sakura text-xs sm:text-sm shrink-0 px-4 py-2 sm:px-6 sm:py-2.5">💾 Save Profile</button>
<a href="{{ route('rate-cards.public') }}" target="_blank" <a href="{{ route('rate-cards.public') }}" target="_blank"
class="btn-sakura text-sm px-4 py-2.5 rounded-full border border-pink-200 text-pink-500 hover:bg-pink-50"> class="bg-white text-xs sm:text-sm px-3 py-1.5 sm:px-4 sm:py-2 rounded-full border border-pink-200 text-pink-600 hover:bg-pink-50 shadow-sm shrink-0">
👁️ Preview 👁️ Preview
</a> </a>
<a href="{{ route('rate-cards.pdf') }}" <a href="{{ route('rate-cards.pdf') }}"
class="btn-sakura text-sm px-4 py-2.5 rounded-full border border-blue-200 text-blue-500 hover:bg-blue-50"> class="bg-white text-xs sm:text-sm px-3 py-1.5 sm:px-4 sm:py-2 rounded-full border border-blue-200 text-blue-600 hover:bg-blue-50 shadow-sm shrink-0">
📄 Download PDF 📄 Download PDF
</a> </a>
<a href="{{ route('rate-cards.index') }}" <a href="{{ route('rate-cards.index') }}"
class="btn-sakura text-sm px-4 py-2.5 rounded-full border border-gray-200 text-gray-500 hover:bg-gray-50"> class="bg-white text-xs sm:text-sm px-3 py-1.5 sm:px-4 sm:py-2 rounded-full border border-gray-200 text-gray-600 hover:bg-gray-50 shadow-sm shrink-0">
← Back ← Back
</a> </a>
</div> </div>

View File

@@ -91,7 +91,7 @@ document.addEventListener('DOMContentLoaded', function () {
headerToolbar: { headerToolbar: {
left: 'prev,next today', left: 'prev,next today',
center: 'title', center: 'title',
right: isMobile ? 'listWeek,timeGridDay' : 'dayGridMonth,timeGridWeek,listWeek', right: isMobile ? 'dayGridMonth,listWeek,timeGridDay' : 'dayGridMonth,timeGridWeek,listWeek',
}, },
editable: !isMobile, editable: !isMobile,
selectable: true, selectable: true,
@@ -99,7 +99,7 @@ document.addEventListener('DOMContentLoaded', function () {
height: 'auto', height: 'auto',
windowResize(view) { windowResize(view) {
const mob = window.innerWidth < 640; const mob = window.innerWidth < 640;
if (mob && calendar.view.type !== 'listWeek' && calendar.view.type !== 'timeGridDay') { if (mob && calendar.view.type !== 'listWeek' && calendar.view.type !== 'timeGridDay' && calendar.view.type !== 'dayGridMonth') {
calendar.changeView('listWeek'); calendar.changeView('listWeek');
} }
}, },