Aplikasi Keuangan yang mengatur lalu lintas transaksi antara bank dan aplikasi internal Kampus
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

273 lines
13 KiB

@use(App\Domains\Billing\Enums\Invoicing\InvoiceType)
@use(App\Domains\System\Support\ValueObjects\Money)
<div class="row g-3 g-lg-4">
<!-- Invoice Header & Title Section -->
<div class="col-12">
<!-- Main title input card -->
<div class="card card-body shadow-sm border-0">
<div class="d-flex align-items-center gap-3">
<div class="avatar avatar-md bg-primary-subtle text-primary rounded-3 d-flex align-items-center justify-content-center p-2">
<x-tabler-file-invoice width="24" height="24" />
</div>
<div class="flex-grow-1">
<x-form.input
:label="__('domains/billing/field.invoice.name')"
wire:model="form.name"
placeholder="Contoh: Tagihan UKT & Registrasi Ulang 2026/2027"
class="form-control-lg fw-semibold"
/>
</div>
</div>
</div>
</div>
<!-- Left Main Column: Customer Details & Invoice Items -->
<div class="col-12 col-lg-7 col-xl-8 d-flex flex-column gap-3 gap-lg-4">
<!-- Customer Details Card -->
<x-card
:title="__('domains/billing/section.invoice.customer_detail.title')"
:subtitle="__('domains/billing/section.invoice.customer_detail.subtitle')"
class="shadow-sm border-0"
>
<div class="row g-3">
@foreach($this->schema as $schema)
@php($model = $schema->isLive ? 'wire:model.live' : 'wire:model')
<div class="{{ count($this->schema) > 5 ? 'col-12 col-md-6' : 'col-12' }}">
<!-- Dynamic schema input component binding -->
<x-dynamic-component
:component="$schema->type->component()"
:attributes="new Illuminate\View\ComponentAttributeBag($schema->attributes)->merge([
$model => 'form.client_detail.'.$schema->key
])"
/>
</div>
@endforeach
</div>
<!-- Action button for student search if schema forbids manual items -->
@if($form->type?->schema()->isCannotAddItems())
<div class="d-flex pt-3 justify-content-end border-top mt-3 border-subtle">
<x-button
type="button"
theme="primary"
icon="tabler-file-search"
wire:click="searchStudentAdmission"
:icon-property="['width' => 20, 'height' => 20]"
:label="__('domains/billing/button.check_student_admission')"
class="px-3"
/>
</div>
@endif
</x-card>
<!-- Invoice Items Table Card -->
<x-card
:title="__('domains/billing/section.invoice.items.title')"
:subtitle="__('domains/billing/section.invoice.items.subtitle')"
class="shadow-sm border-0"
>
<x-slot:actions>
@if($form->type?->schema()->isCanAddItems())
<!-- Button to add dynamic line items -->
<x-button
type="button"
theme="outline-primary"
icon-only
icon="tabler-plus"
:icon-property="['width' => 20, 'height' => 20]"
class="btn-sm rounded-circle"
/>
@endif
</x-slot:actions>
<div class="table-responsive rounded-3 border border-subtle">
<table class="table table-hover align-middle mb-0 text-nowrap small">
<thead class="bg-body-tertiary">
<tr class="text-uppercase fs-7 fw-semibold text-body-secondary">
<th class="ps-3 py-3" style="width: 50px;">#</th>
<th class="py-3">@lang('domains/billing/field.invoice_item.code')</th>
<th class="py-3">@lang('domains/billing/field.invoice_item.name')</th>
<th class="text-center py-3">@lang('domains/billing/field.invoice_item.quantity')</th>
<th class="text-end py-3">@lang('domains/billing/field.invoice_item.gross_price')</th>
<th class="text-end py-3">@lang('domains/billing/field.invoice_item.discounted_price')</th>
<th class="text-end pe-3 py-3">@lang('domains/billing/field.invoice_item.net_price')</th>
</tr>
</thead>
<tbody>
@forelse($this->form->items ?? [] as $index => $item)
<tr>
<td class="ps-3 py-2 fw-semibold text-body-secondary">{{ $index + 1 }}</td>
<td class="py-2 font-monospace text-body-secondary">
{{ $item['code'] ?? '-' }}
</td>
<td class="py-2 text-body fw-bold">{{ $item['name'] ?? '-' }}</td>
<td class="text-center py-2 font-monospace fw-semibold">{{ $item['quantity'] ?? 1 }}</td>
<td class="text-end py-2 font-monospace text-body-secondary">{{ $item['gross_amount'] ?? new Money(0) }}</td>
<td class="text-end py-2 font-monospace text-danger">{{ $item['discount_amount'] ?? new Money(0) }}</td>
<td class="text-end pe-3 py-2 font-monospace fw-bold text-success">
{{ $item['net_amount'] ?? new Money(0) }}
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center py-5 text-body-secondary">
<!-- Empty state display -->
<div class="avatar avatar-lg mx-auto mb-2">
<span class="avatar-initial rounded-circle bg-body-tertiary text-body-secondary">
<x-tabler-receipt width="28" height="28" />
</span>
</div>
<p class="small mb-0 text-body-secondary fw-medium">{{ __('ui/common.no_data') }}</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</x-card>
</div>
<!-- Right Column: Sticky Invoice Summary Side Card -->
<div class="col-12 col-lg-5 col-xl-4">
<div class="sticky-top" style="top: 90px; z-index: 10;">
<x-card
:title="__('domains/billing/section.invoice.summary.title')"
:subtitle="__('domains/billing/section.invoice.summary.subtitle')"
class="shadow-sm border-0 border-top border-3 border-primary"
>
<div class="p-3 rounded-3 bg-body-tertiary border border-subtle">
<!-- Status Indicator Badge -->
<div class="d-flex align-items-center justify-content-between mb-3">
<span class="text-body-secondary fw-bold text-uppercase fs-7 tracking-wider">
@lang('domains/billing/field.invoice.status')
</span>
<span class="badge bg-primary-subtle text-primary border border-primary-subtle rounded-pill px-2 py-1 fw-semibold">
@lang('domains/billing/enum.invoice_status.draft')
</span>
</div>
<div class="d-flex flex-column gap-2">
<!-- Gross Amount -->
<div class="d-flex align-items-center justify-content-between p-2 rounded bg-body border border-subtle">
<small class="text-body-secondary fw-semibold fs-7">
@lang('domains/billing/field.invoice.gross_amount')
</small>
<span class="fw-bold text-body font-monospace">
{{ $form->gross_amount }}
</span>
</div>
<!-- Discount Amount -->
<div class="d-flex align-items-center justify-content-between p-2 rounded bg-body border border-subtle">
<small class="text-body-secondary fw-semibold fs-7">
@lang('domains/billing/field.invoice.discount_amount')
</small>
<span class="fw-bold text-danger font-monospace">
-{{ $form->discount_amount }}
</span>
</div>
<hr class="my-1 border-subtle opacity-50">
<!-- Net Amount -->
<div class="d-flex align-items-center justify-content-between p-3 rounded bg-primary text-white shadow-xs">
<small class="fw-bold text-uppercase fs-7 opacity-90">
@lang('domains/billing/field.invoice.net_amount')
</small>
<span class="fw-bold font-monospace fs-5">
{{ $form->net_amount }}
</span>
</div>
</div>
<!-- Action Buttons -->
<div class="mt-3 d-flex flex-column gap-2">
<!-- Issue Invoice Button -->
<x-button
type="button"
theme="primary"
size="md"
icon="tabler-file-invoice"
:label="__('domains/billing/button.issue_invoice')"
wire:click="createInvoice"
wire:loading.attr="disabled"
wire:target="createInvoice"
class="w-100 fw-bold shadow-xs"
/>
<!-- Save Draft Button -->
<x-button
type="button"
theme="secondary"
size="md"
icon="tabler-device-floppy"
:label="__('domains/billing/button.save_invoice_draft')"
wire:click="saveDraft"
wire:loading.attr="disabled"
wire:target="save"
class="w-100 fw-bold shadow-xs"
/>
<!-- Cancel / Re-select Type Button -->
<x-button
type="button"
theme="outline-secondary"
size="sm"
x-on:click="$js.selectInvoiceType"
:label="__('ui/button.cancel')"
class="w-100 mt-1"
/>
</div>
</div>
</x-card>
</div>
</div>
</div>
@script
<script>
// SweetAlert initialization function for invoice type and client selection
$js.selectInvoiceType = init = () => {
Swal.fire({
title: '{{ __('domains/billing/section.invoice.select_invoice_type') }}',
html: `
<div class="text-start mb-3">
<x-form.select x-select2="{dropdownParent: '.swal2-container'}" id="swal-client-id" :label="__('domains/billing/field.invoice.client_name')" :options="$this->clients" />
</div>
<div class="text-start">
<x-form.select x-select2="{dropdownParent: '.swal2-container'}" id="swal-invoice-type" :label="__('domains/billing/field.invoice.type')" :options="InvoiceType::options()" />
</div>
`,
focusConfirm: false,
showCancelButton: false,
allowOutsideClick: false,
allowEscapeKey: false,
confirmButtonText: 'Lanjutkan',
customClass: {
confirmButton: 'btn btn-primary px-4',
popup: 'rounded-4 shadow-lg border-0 bg-body text-body'
},
buttonsStyling: false,
preConfirm: () => {
const clientId = document.getElementById('swal-client-id').value;
const invoiceType = document.getElementById('swal-invoice-type').value;
if (!clientId) {
Swal.showValidationMessage('{{ __('validation.required', ['attribute' => __('domains/billing/field.invoice.client_name')]) }}');
return false;
}
if (!invoiceType) {
Swal.showValidationMessage('{{ __('validation.required', ['attribute' => __('domains/billing/field.invoice.type')]) }}');
return false;
}
$wire.set('form.type', invoiceType);
$wire.set('form.client_id', clientId);
}
});
}
init()
</script>
@endscript