From 5748b163b63e9f7a5c1b583bcc9f45179aa34b8a Mon Sep 17 00:00:00 2001 From: I-Fun Date: Thu, 2 Apr 2026 07:24:43 +0700 Subject: [PATCH] feat: Add accent color to invoice settings and update related views; enhance event creation with brand contract option --- .dockerignore | 1 - Dockerfile | 1 + app/Http/Controllers/EventController.php | 88 +++++++++++++------ .../Controllers/InvoiceSettingsController.php | 1 + app/Models/InvoiceSettings.php | 2 + config/filesystems.php | 7 ++ ...0_add_accent_color_to_invoice_settings.php | 22 +++++ docker/entrypoint.sh | 4 + docker/supervisord.conf | 9 +- resources/views/files/index.blade.php | 63 ++++++++++++- resources/views/invoices/pdf.blade.php | 11 ++- resources/views/invoices/settings.blade.php | 16 ++++ resources/views/invoices/show.blade.php | 13 ++- resources/views/schedule/edit.blade.php | 52 ++++++++--- resources/views/schedule/index.blade.php | 74 +++++++++++++--- 15 files changed, 302 insertions(+), 62 deletions(-) create mode 100644 database/migrations/2026_04_01_200000_add_accent_color_to_invoice_settings.php diff --git a/.dockerignore b/.dockerignore index a2cc395..be30cbf 100644 --- a/.dockerignore +++ b/.dockerignore @@ -44,7 +44,6 @@ Thumbs.db # ── Docker config itself ───────────────────── Dockerfile docker-compose*.yml -docker/ # ── Claude / project meta ──────────────────── .claude diff --git a/Dockerfile b/Dockerfile index 2a4b704..8b9af39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,7 @@ RUN apk add --no-cache \ nginx \ supervisor \ sqlite \ + sqlite-dev \ curl \ git \ unzip \ diff --git a/app/Http/Controllers/EventController.php b/app/Http/Controllers/EventController.php index a7c9b0b..09fdf96 100644 --- a/app/Http/Controllers/EventController.php +++ b/app/Http/Controllers/EventController.php @@ -5,12 +5,14 @@ declare(strict_types=1); namespace App\Http\Controllers; use App\Enums\EventStatus; +use App\Models\BrandContract; use App\Models\Client; use App\Models\ClientCategory; use App\Models\Event; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Arr; use Illuminate\View\View; class EventController extends Controller @@ -51,24 +53,41 @@ class EventController extends Controller public function store(Request $request): RedirectResponse { $validated = $request->validate([ - 'title' => 'required|string|max:255', - 'description' => 'nullable|string', - 'client_id' => 'nullable|exists:clients,id', - 'category_id' => 'nullable|exists:client_categories,id', - 'event_type' => 'required|in:task,meeting,deadline,posting', - 'start_time' => 'required|date', - 'end_time' => 'nullable|date|after:start_time', - 'location' => 'nullable|string|max:255', - 'is_priority' => 'boolean', - 'is_all_day' => 'boolean', - 'color' => 'nullable|string|max:20', - 'notes' => 'nullable|string', + 'title' => 'required|string|max:255', + 'description' => 'nullable|string', + 'client_id' => 'nullable|exists:clients,id', + 'category_id' => 'nullable|exists:client_categories,id', + 'event_type' => 'required|in:task,meeting,deadline,posting', + 'start_time' => 'required|date', + 'end_time' => 'nullable|date|after:start_time', + 'location' => 'nullable|string|max:255', + 'is_priority' => 'boolean', + 'is_all_day' => 'boolean', + 'color' => 'nullable|string|max:20', + 'notes' => 'nullable|string', + 'create_brand_contract' => 'nullable|boolean', + 'contract_title' => 'nullable|string|max:255', + 'contract_start_date' => 'nullable|date', + 'contract_end_date' => 'nullable|date|after_or_equal:contract_start_date', ]); $validated['is_priority'] = $request->boolean('is_priority'); $validated['is_all_day'] = $request->boolean('is_all_day'); - Event::create($validated); + $event = Event::create(Arr::except($validated, [ + 'create_brand_contract', 'contract_title', 'contract_start_date', 'contract_end_date', + ])); + + if ($request->boolean('create_brand_contract') && $event->client_id) { + BrandContract::create([ + 'title' => $request->input('contract_title') ?: $event->title, + 'client_id' => $event->client_id, + 'category_id' => $event->category_id, + 'start_date' => $request->input('contract_start_date') ?? $event->start_time->toDateString(), + 'end_date' => $request->input('contract_end_date') ?? ($event->end_time?->toDateString() ?? $event->start_time->toDateString()), + 'is_active' => true, + ]); + } return redirect()->route('schedule.index') ->with('success', '🐾 Event scheduled!'); @@ -86,22 +105,39 @@ class EventController extends Controller public function update(Request $request, Event $event): RedirectResponse { $validated = $request->validate([ - 'title' => 'required|string|max:255', - 'description' => 'nullable|string', - 'client_id' => 'nullable|exists:clients,id', - 'category_id' => 'nullable|exists:client_categories,id', - 'event_type' => 'required|in:task,meeting,deadline,posting', - 'start_time' => 'required|date', - 'end_time' => 'nullable|date', - 'location' => 'nullable|string|max:255', - 'is_priority' => 'boolean', - 'status' => 'required|in:pending,in_progress,completed,cancelled', - 'color' => 'nullable|string|max:20', - 'notes' => 'nullable|string', + 'title' => 'required|string|max:255', + 'description' => 'nullable|string', + 'client_id' => 'nullable|exists:clients,id', + 'category_id' => 'nullable|exists:client_categories,id', + 'event_type' => 'required|in:task,meeting,deadline,posting', + 'start_time' => 'required|date', + 'end_time' => 'nullable|date', + 'location' => 'nullable|string|max:255', + 'is_priority' => 'boolean', + 'status' => 'required|in:pending,in_progress,completed,cancelled', + 'color' => 'nullable|string|max:20', + 'notes' => 'nullable|string', + 'create_brand_contract' => 'nullable|boolean', + 'contract_title' => 'nullable|string|max:255', + 'contract_start_date' => 'nullable|date', + 'contract_end_date' => 'nullable|date|after_or_equal:contract_start_date', ]); $validated['is_priority'] = $request->boolean('is_priority'); - $event->update($validated); + $event->update(Arr::except($validated, [ + 'create_brand_contract', 'contract_title', 'contract_start_date', 'contract_end_date', + ])); + + if ($request->boolean('create_brand_contract') && $event->client_id) { + BrandContract::create([ + 'title' => $request->input('contract_title') ?: $event->title, + 'client_id' => $event->client_id, + 'category_id' => $event->category_id, + 'start_date' => $request->input('contract_start_date') ?? $event->start_time->toDateString(), + 'end_date' => $request->input('contract_end_date') ?? ($event->end_time?->toDateString() ?? $event->start_time->toDateString()), + 'is_active' => true, + ]); + } return redirect()->route('schedule.index') ->with('success', '🐾 Event updated!'); diff --git a/app/Http/Controllers/InvoiceSettingsController.php b/app/Http/Controllers/InvoiceSettingsController.php index 01e3e03..251187f 100644 --- a/app/Http/Controllers/InvoiceSettingsController.php +++ b/app/Http/Controllers/InvoiceSettingsController.php @@ -26,6 +26,7 @@ class InvoiceSettingsController extends Controller 'header_name' => 'nullable|string|max:100', 'header_title' => 'nullable|string|max:150', 'header_logo' => 'nullable|image|max:2048', + 'accent_color' => 'nullable|string|max:20', 'default_tax_rate' => 'nullable|numeric|min:0|max:100', 'default_currency' => 'nullable|string|max:10', 'default_service_description'=> 'nullable|string|max:255', diff --git a/app/Models/InvoiceSettings.php b/app/Models/InvoiceSettings.php index cf81fac..c9fc2af 100644 --- a/app/Models/InvoiceSettings.php +++ b/app/Models/InvoiceSettings.php @@ -16,6 +16,7 @@ class InvoiceSettings extends Model 'header_name', 'header_title', 'header_logo', + 'accent_color', 'default_tax_rate', 'default_currency', 'default_service_description', @@ -37,6 +38,7 @@ class InvoiceSettings extends Model return static::firstOrCreate([], [ 'invoice_prefix' => 'INV', 'invoice_next_number' => 1, + 'accent_color' => '#FFB7C5', 'default_tax_rate' => 11, 'default_currency' => 'IDR', ]); diff --git a/config/filesystems.php b/config/filesystems.php index 37d8fca..e3ebbd5 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -38,6 +38,13 @@ return [ 'report' => false, ], + 'private' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => false, + 'throw' => false, + ], + 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), diff --git a/database/migrations/2026_04_01_200000_add_accent_color_to_invoice_settings.php b/database/migrations/2026_04_01_200000_add_accent_color_to_invoice_settings.php new file mode 100644 index 0000000..2dbfed4 --- /dev/null +++ b/database/migrations/2026_04_01_200000_add_accent_color_to_invoice_settings.php @@ -0,0 +1,22 @@ +string('accent_color', 20)->default('#FFB7C5')->after('header_logo'); + }); + } + + public function down(): void + { + Schema::table('invoice_settings', function (Blueprint $table) { + $table->dropColumn('accent_color'); + }); + } +}; diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 922c367..15fe6b5 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -45,6 +45,10 @@ chmod -R 775 \ echo "[migrate] Running database migrations…" php artisan migrate --force --no-interaction +# ── 5b. Create public storage symlink ──────── +echo "[storage] Creating public storage symlink…" +php artisan storage:link --quiet 2>/dev/null || true + # ── 6. Cache config / routes / views ──────── echo "[optimize] Caching configuration…" php artisan config:cache --quiet diff --git a/docker/supervisord.conf b/docker/supervisord.conf index 457465b..0b088d0 100644 --- a/docker/supervisord.conf +++ b/docker/supervisord.conf @@ -38,11 +38,10 @@ stderr_logfile_maxbytes=0 ; ── Laravel Queue Worker ────────────────────────────────────────────────── [program:queue] -command=php /var/www/html/artisan queue:work \ - --sleep=3 \ - --tries=3 \ - --max-time=3600 \ - --queue=default +command=php /var/www/html/artisan queue:work --sleep=3 --tries=3 --max-time=3600 --queue=default + + +queue=default directory=/var/www/html user=www-data autostart=true diff --git a/resources/views/files/index.blade.php b/resources/views/files/index.blade.php index 88164ee..54fe180 100644 --- a/resources/views/files/index.blade.php +++ b/resources/views/files/index.blade.php @@ -6,8 +6,30 @@ @section('content') +{{-- ── File Detail Popup Modal ──────────────────────────────── --}} + + {{-- ── Upload Form (collapsible) ────────────────────────────── --}} -
{{-- Preview / Icon --}} @if($file->is_image) -
+
{{ $file->name }}
@else -
+
{{ $file->icon }}
@endif @@ -219,11 +243,42 @@
📁

No files yet.

-
@endforelse
{{ $files->links() }}
+@push('scripts') + +@endpush + @endsection diff --git a/resources/views/invoices/pdf.blade.php b/resources/views/invoices/pdf.blade.php index 7c2b518..36017a1 100644 --- a/resources/views/invoices/pdf.blade.php +++ b/resources/views/invoices/pdf.blade.php @@ -6,7 +6,7 @@ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Helvetica', Arial, sans-serif; color: #374151; font-size: 13px; padding: 40px; } .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; } - .logo { font-size: 22px; font-weight: 900; color: #FFB7C5; } + .logo { font-size: 22px; font-weight: 900; color: {{ $invoiceSettings->accent_color ?? '#FFB7C5' }}; } .logo-sub { font-size: 11px; color: #9CA3AF; margin-top: 2px; } .inv-number { font-size: 22px; font-weight: 700; color: #1F2937; text-align: right; } .badge { display: inline-block; padding: 3px 12px; border-radius: 20px; font-size: 11px; font-weight: 700; background: #FDE68A; color: #92400E; } @@ -27,7 +27,7 @@ .totals { display: flex; justify-content: flex-end; margin-bottom: 24px; } .totals-box { width: 260px; } .totals-row { display: flex; justify-content: space-between; padding: 4px 0; font-size: 12px; } - .totals-row.total { border-top: 2px solid #FFB7C5; margin-top: 6px; padding-top: 8px; font-size: 15px; font-weight: 800; color: #FFB7C5; } + .totals-row.total { border-top: 2px solid {{ $invoiceSettings->accent_color ?? '#FFB7C5' }}; margin-top: 6px; padding-top: 8px; font-size: 15px; font-weight: 800; color: {{ $invoiceSettings->accent_color ?? '#FFB7C5' }}; } .bank-box { background: #FFF5F7; border-radius: 12px; padding: 14px 16px; margin-bottom: 16px; } .bank-title { font-weight: 700; font-size: 12px; color: #374151; margin-bottom: 4px; } .bank-detail { font-size: 12px; color: #6B7280; } @@ -91,7 +91,12 @@ @foreach($invoice->items as $item) - {{ $item->description }} + + {{ $item->description }} + @if(!$item->is_taxable) + Non-Taxable + @endif + {{ $item->quantity }} Rp {{ number_format($item->unit_price, 0, ',', '.') }} Rp {{ number_format($item->total, 0, ',', '.') }} diff --git a/resources/views/invoices/settings.blade.php b/resources/views/invoices/settings.blade.php index 1c74b76..150f0af 100644 --- a/resources/views/invoices/settings.blade.php +++ b/resources/views/invoices/settings.blade.php @@ -96,6 +96,22 @@ value="{{ old('header_title', $settings->header_title) }}" class="input-soft text-sm" placeholder="Content Creator · KOL">
+
+ +
+ +
+ @foreach(['#FFB7C5','#B5EAD7','#FDFD96','#E8D5F5','#FFD5B8','#AEC6CF','#FF8FAD','#A8D8EA'] as $preset) + + @endforeach +
+
+

Applied to your invoice header name and totals.

+
diff --git a/resources/views/invoices/show.blade.php b/resources/views/invoices/show.blade.php index 5ac4f7e..78a868e 100644 --- a/resources/views/invoices/show.blade.php +++ b/resources/views/invoices/show.blade.php @@ -24,7 +24,8 @@ alt="Logo" class="h-14 w-14 object-cover rounded-2xl border border-pink-100 shrink-0"> @endif
-
+
{{ $invoiceSettings->header_name ?: '🐱 KOL Manager' }}
@@ -74,7 +75,12 @@ @foreach($invoice->items as $item) - {{ $item->description }} + + {{ $item->description }} + @if(!$item->is_taxable) + Non-Taxable + @endif + {{ $item->quantity }} Rp {{ number_format($item->unit_price, 0, ',', '.') }} Rp {{ number_format($item->total, 0, ',', '.') }} @@ -95,7 +101,8 @@ Rp {{ number_format($invoice->tax_amount, 0, ',', '.') }}
@endif -
+
Total (you receive){{ $invoice->formatted_total }}
diff --git a/resources/views/schedule/edit.blade.php b/resources/views/schedule/edit.blade.php index 2a26fe8..18c5295 100644 --- a/resources/views/schedule/edit.blade.php +++ b/resources/views/schedule/edit.blade.php @@ -131,17 +131,20 @@
{{-- Color --}} -
+
-
- @foreach(['#FFB7C5','#B5EAD7','#FDFD96','#E8D5F5','#FFD5B8','#AEC6CF'] as $hex) - - @endforeach + +
+ +
+ @foreach(['#FFB7C5','#B5EAD7','#FDFD96','#E8D5F5','#FFD5B8','#AEC6CF'] as $hex) + + @endforeach +
@@ -159,6 +162,35 @@ placeholder="Additional notes…">{{ old('notes', $event->notes) }}
+ {{-- Active Contract checkbox --}} +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
-
+
-
- @foreach(['#FFB7C5','#B5EAD7','#FDFD96','#E8D5F5','#FFD5B8','#AEC6CF'] as $hex) - - @endforeach + +
+ +
+ @foreach(['#FFB7C5','#B5EAD7','#FDFD96','#E8D5F5','#FFD5B8','#AEC6CF'] as $hex) + + @endforeach +
@@ -105,6 +109,33 @@
+ + {{-- Active Contract checkbox --}} +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+