initial commit

This commit is contained in:
I-Fun
2026-04-01 08:04:56 +07:00
commit e09c8a5fa9
111 changed files with 18022 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
@extends('layouts.app')
@section('title', 'New Invoice')
@section('page-title', 'Create Invoice 🧾')
@section('page-subtitle', 'Fill in the invoice details')
@section('content')
<div class="max-w-3xl mx-auto">
<div class="cat-card p-4 lg:p-8" x-data="invoiceForm()">
<form action="{{ route('invoices.store') }}" method="POST">
@csrf
{{-- 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 <span class="text-pink-400">*</span></label>
<input type="text" name="title" value="{{ old('title') }}" class="input-soft" required
placeholder="e.g. Instagram Collaboration - Brand X">
</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 }}" {{ request('client') == $client->id ? 'selected' : '' }}>
{{ $client->name }}
</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Currency</label>
<select name="currency" class="input-soft">
<option value="IDR" selected>IDR (Rupiah)</option>
<option value="USD">USD (Dollar)</option>
<option value="SGD">SGD</option>
</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', today()->toDateString()) }}"
class="input-soft" required>
</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', today()->addDays(14)->toDateString()) }}"
class="input-soft" required>
</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', 11) }}" 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">
{{-- Description full width --}}
<input type="text" :name="`items[${index}][description]`"
x-model="item.description"
class="input-soft text-sm mb-2 bg-white"
placeholder="Service description" required>
{{-- Qty / Price / Total in a row --}}
<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"
placeholder="0">
</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>
</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</span>
<span class="font-semibold">Rp <span x-text="formatNum(subtotal)"></span></span>
</div>
<div class="flex justify-between text-sm py-1">
<span class="text-gray-500">Tax (<span x-text="taxRate"></span>%)</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">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-3 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') }}"
class="input-soft text-sm" placeholder="BCA, Mandiri...">
</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') }}"
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') }}"
class="input-soft text-sm">
</div>
</div>
<div class="mb-5">
<label class="block text-sm font-semibold text-gray-700 mb-1">Notes</label>
<textarea name="notes" rows="2" class="input-soft resize-none"
placeholder="Payment terms, etc.">{{ old('notes') }}</textarea>
</div>
<div class="flex flex-col sm:flex-row gap-3">
<button type="submit" class="btn-sakura flex-1 py-3">🧾 Create Invoice</button>
<a href="{{ route('invoices.index') }}"
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 invoiceForm() {
return {
items: [{ description: '', quantity: 1, unit_price: 0 }],
taxRate: 11,
get subtotal() { return this.items.reduce((s, i) => s + (i.quantity * i.unit_price), 0); },
get taxAmount() { return Math.round(this.subtotal * this.taxRate / 100); },
get total() { return this.subtotal + this.taxAmount; },
addItem() { this.items.push({ description: '', quantity: 1, unit_price: 0 }); },
removeItem(i) { if (this.items.length > 1) this.items.splice(i, 1); },
recalc() {},
formatNum(n) { return Number(n).toLocaleString('id-ID'); },
};
}
</script>
@endpush
@endsection