Files
Kol-Manager/resources/views/invoices/edit.blade.php

224 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('layouts.app')
@section('title', 'Edit Invoice')
@section('page-title', 'Edit Invoice 🧾')
@section('page-subtitle', $invoice->invoice_number)
@section('content')
<div class="max-w-3xl mx-auto">
<div class="cat-card p-4 lg:p-8" x-data="invoiceEditForm()">
<form action="{{ route('invoices.update', $invoice) }}" method="POST" enctype="multipart/form-data">
@csrf @method('PUT')
{{-- Header fields --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-5">
<div class="sm:col-span-2">
<label class="block text-sm font-semibold text-gray-700 mb-1">Invoice Title</label>
<input type="text" name="title" value="{{ old('title', $invoice->title) }}" class="input-soft" required>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Client</label>
<select name="client_id" class="input-soft">
<option value="">— No client —</option>
@foreach($clients as $client)
<option value="{{ $client->id }}" {{ old('client_id', $invoice->client_id) == $client->id ? 'selected' : '' }}>{{ $client->name }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Status</label>
<select name="status" class="input-soft">
@foreach($statuses as $s)
<option value="{{ $s->value }}" {{ old('status', $invoice->status->value) === $s->value ? 'selected' : '' }}>{{ $s->label() }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Issue Date</label>
<input type="date" name="issue_date" value="{{ old('issue_date', $invoice->issue_date->toDateString()) }}" class="input-soft">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Due Date</label>
<input type="date" name="due_date" value="{{ old('due_date', $invoice->due_date->toDateString()) }}" class="input-soft">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Paid Date</label>
<input type="date" name="paid_date" value="{{ old('paid_date', $invoice->paid_date?->toDateString()) }}" class="input-soft">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Tax Rate (%)</label>
<input type="number" name="tax_rate" x-model="taxRate" @input="recalc()"
value="{{ old('tax_rate', $invoice->tax_rate) }}" class="input-soft" step="0.01">
</div>
</div>
{{-- Line Items --}}
<div class="mb-5">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-bold text-gray-700">Line Items</h3>
<button type="button" @click="addItem()" class="btn-sakura text-xs py-1.5 px-4">+ Add Item</button>
</div>
<div class="space-y-2">
<template x-for="(item, index) in items" :key="index">
<div class="bg-pink-50 rounded-2xl p-3">
<input type="text" :name="`items[${index}][description]`"
x-model="item.description"
class="input-soft text-sm mb-2 bg-white"
placeholder="Service description" required>
<div class="flex gap-2 items-center">
<div class="w-16 shrink-0">
<label class="text-xs text-gray-400 block mb-0.5">Qty</label>
<input type="number" :name="`items[${index}][quantity]`"
x-model.number="item.quantity" @input="recalc()"
class="input-soft text-sm text-center bg-white px-2" min="1">
</div>
<div class="flex-1">
<label class="text-xs text-gray-400 block mb-0.5">Unit Price</label>
<input type="number" :name="`items[${index}][unit_price]`"
x-model.number="item.unit_price" @input="recalc()"
class="input-soft text-sm bg-white" min="0" step="1000">
</div>
<div class="w-24 shrink-0 text-right">
<label class="text-xs text-gray-400 block mb-0.5">Total</label>
<div class="text-sm font-bold text-gray-700 pt-2">
Rp <span x-text="formatNum(item.quantity * item.unit_price)"></span>
</div>
</div>
<button type="button" @click="removeItem(index)"
class="text-red-400 hover:text-red-600 text-lg shrink-0 pt-4">✕</button>
</div>
{{-- Taxable checkbox --}}
<div class="mt-2 flex items-center gap-2">
<input type="hidden" :name="`items[${index}][is_taxable]`" value="0">
<input type="checkbox" :name="`items[${index}][is_taxable]`"
:id="`taxable_${index}`"
x-model="item.is_taxable" @change="recalc()"
value="1"
class="w-3.5 h-3.5 accent-pink-400">
<label :for="`taxable_${index}`" class="text-xs text-gray-500 cursor-pointer select-none">
Taxable
</label>
</div>
</div>
</template>
</div>
</div>
{{-- Totals --}}
<div class="bg-pink-50 rounded-2xl p-4 mb-5">
<div class="flex justify-between text-sm py-1">
<span class="text-gray-500">Subtotal (all items)</span>
<span class="font-semibold">Rp <span x-text="formatNum(subtotal)"></span></span>
</div>
<div class="flex justify-between text-sm py-1 text-amber-600">
<span>Tax (<span x-text="taxRate"></span>% — paid by client)</span>
<span class="font-semibold">Rp <span x-text="formatNum(taxAmount)"></span></span>
</div>
<div class="flex justify-between text-base font-bold py-2 mt-1 border-t border-pink-200">
<span class="text-gray-700">Grand Total</span>
<span class="text-pink-600">Rp <span x-text="formatNum(total)"></span></span>
</div>
</div>
{{-- Bank Info --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-5">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Bank Name</label>
<input type="text" name="bank_name" value="{{ old('bank_name', $invoice->bank_name) }}"
class="input-soft text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Branch Name</label>
<input type="text" name="branch_name" value="{{ old('branch_name', $invoice->branch_name) }}"
class="input-soft text-sm" placeholder="e.g. KCP Sudirman">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Account Number</label>
<input type="text" name="bank_account" value="{{ old('bank_account', $invoice->bank_account) }}"
class="input-soft text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Account Holder</label>
<input type="text" name="bank_holder" value="{{ old('bank_holder', $invoice->bank_holder) }}"
class="input-soft text-sm">
</div>
</div>
<div class="mb-5">
<label class="block text-sm font-semibold text-gray-700 mb-1">Notes / Payment Terms</label>
<textarea name="notes" rows="2" class="input-soft resize-none">{{ old('notes', $invoice->notes) }}</textarea>
</div>
<div class="mb-5" x-data="{
removeSign() {
if (!confirm('Remove the signature from this invoice?')) return;
$refs.removeSignInput.value = '1';
this.$el.closest('form').submit();
}
}">
<label class="block text-sm font-semibold text-gray-700 mb-1">Custom Signature Image (Optional)</label>
<p class="text-xs text-gray-400 mb-3">Upload a specific signature for this invoice. Leave blank to keep the current one.</p>
@if($invoice->signature_image)
<div class="flex items-center gap-4 mb-3 p-3 bg-white rounded-xl border border-pink-100">
<img src="{{ '/storage/' . $invoice->signature_image }}" alt="Signature" class="h-16 object-contain">
<div>
<input type="hidden" name="remove_signature" value="0" x-ref="removeSignInput">
<button type="button" @click="removeSign()" class="text-xs px-3 py-1.5 rounded-full border border-red-200 text-red-400 hover:bg-red-50">
🗑️ Remove
</button>
</div>
</div>
@endif
<input type="file" name="signature_image" accept="image/*" class="w-full text-sm text-gray-600
file:mr-3 file:py-1.5 file:px-4
file:rounded-full file:border-0
file:text-xs file:font-semibold
file:bg-pink-50 file:text-pink-600
hover:file:bg-pink-100 cursor-pointer">
</div>
<div class="flex flex-col sm:flex-row gap-3">
<button type="submit" class="btn-sakura flex-1 py-3">💾 Save Changes</button>
<a href="{{ route('invoices.show', $invoice) }}"
class="flex-1 py-3 text-center rounded-full border border-pink-200 text-gray-600 hover:bg-pink-50 font-semibold text-sm">
Cancel
</a>
</div>
</form>
</div>
</div>
@push('scripts')
<script>
function invoiceEditForm() {
@php
$itemsJson = $invoice->items->map(function($i) {
return ['description' => $i->description, 'quantity' => $i->quantity, 'unit_price' => (float)$i->unit_price, 'is_taxable' => (bool)$i->is_taxable];
});
@endphp
return {
items: @json($itemsJson),
taxRate: {{ $invoice->tax_rate }},
get subtotal() {
return this.items.reduce((s, i) => s + (i.quantity * i.unit_price), 0);
},
get taxAmount() {
const taxable = this.items
.filter(i => i.is_taxable)
.reduce((s, i) => s + (i.quantity * i.unit_price), 0);
return Math.round(taxable * this.taxRate / 100);
},
get total() { return this.subtotal; },
addItem() { this.items.push({ description: '', quantity: 1, unit_price: 0, is_taxable: true }); },
removeItem(i) { if (this.items.length > 1) this.items.splice(i, 1); },
recalc() {},
formatNum(n) { return Number(n).toLocaleString('id-ID'); },
};
}
</script>
@endpush
@endsection