feat: Implement invoice creation functionality and integrate DataTables for invoice listing with dynamic column rendering.

This commit is contained in:
pbao9
2026-02-15 00:55:17 +07:00
parent 691c7e3765
commit a7614c91cd
18 changed files with 404 additions and 324 deletions
@@ -4,10 +4,13 @@ namespace App\Http\Controllers\admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\InvoiceRequest;
use App\Models\Customer;
use App\Models\Payment;
use App\Repositories\Invoice\InvoiceRepositoryInterface;
use App\Repositories\Order\OrderRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
class InvoiceController extends Controller
{
@@ -31,6 +34,46 @@ class InvoiceController extends Controller
return view('administrator.pages.invoice.index', compact('title', 'invoices'));
}
public function create()
{
$title = 'Add new Invoice';
$customer = Customer::select('id', 'fullname')->where('status', 1)->get();
$payment = Payment::select('id', 'name')->where('status', 1)->get();
return view('administrator.pages.invoice.add', compact('title', 'customer', 'payment'));
}
public function store(Request $request)
{
$validated = $request->validate([
'customer_id' => 'required|numeric',
'payment_id' => 'required|numeric',
'sub_total' => 'required|numeric',
'total' => 'required|numeric',
]);
$invoiceData = [
'date' => time(),
'due_date' => time() + (7 * 24 * 60 * 60),
'sub_total' => $validated['sub_total'],
'tax' => $validated['sub_total'] * 0.1,
'total' => $validated['total'],
'payment_id' => $validated['payment_id'],
'customer_id' => $validated['customer_id'],
'status' => 0,
];
DB::beginTransaction();
try {
$invoice = $this->invoiceRepository->create($invoiceData);
DB::commit();
return redirect()->route('administrator.pages.invoice.edit', ['id' => $invoice->id])->with('success', 'Invoice created successfully');
} catch (\Exception $error) {
DB::rollBack();
return back()->withErrors($error->getMessage());
}
}
public function edit(int $id = 0)
{
$invoice = $this->invoiceRepository->findWithRelationships($id, ['payment', 'invoiceItem', 'customer']);
@@ -96,4 +139,40 @@ class InvoiceController extends Controller
return redirect()->route('administrator.pages.invoice.index')->with('success', 'Invoice deleted successfully');
}
public function datatable(Request $request)
{
$query = $this->invoiceRepository->filter(
$request->all(),
['payment', 'customer'],
[],
['*'],
true
);
return DataTables::eloquent($query)
->addColumn('invoice_num', function ($d) {
return view('administrator.pages.invoice.datatable.invoice_num', ['row' => $d])->render();
})
->addColumn('date', function ($d) {
return view('administrator.pages.invoice.datatable.date', ['row' => $d])->render();
})
->addColumn('customer', function ($d) {
return view('administrator.pages.invoice.datatable.customer', ['row' => $d])->render();
})
->addColumn('payment', function ($d) {
return view('administrator.pages.invoice.datatable.payment', ['row' => $d])->render();
})
->addColumn('total', function ($d) {
return view('administrator.pages.invoice.datatable.total', ['row' => $d])->render();
})
->addColumn('status', function ($d) {
return view('administrator.pages.invoice.datatable.status', ['row' => $d])->render();
})
->addColumn('action', function ($d) {
return view('administrator.pages.invoice.datatable.action', ['row' => $d])->render();
})
->rawColumns(['invoice_num', 'date', 'customer', 'payment', 'total', 'status', 'action'])
->make(true);
}
}
@@ -16,8 +16,12 @@ class InvoiceCachedRepository extends BaseCachedRepository implements InvoiceRep
{
return $this->invoiceRepository->getModel();
}
public function filter(array $data = [], array $relationships = [], array $withCount = [], array $select = ['*'])
public function filter(array $data = [], array $relationships = [], array $withCount = [], array $select = ['*'], bool $returnQuery = false)
{
if ($returnQuery) {
return $this->invoiceRepository->filter($data, $relationships, $withCount, $select, $returnQuery);
}
$cacheKey = $this->getCacheKey(md5(json_encode([
'filterItem' => [
'data' => $data,
@@ -12,10 +12,13 @@ class InvoiceRepository extends BaseRepository implements InvoiceRepositoryInter
{
return Invoice::class;
}
public function filter(array $data = [], array $relationships = [], array $withCount = [], array $select = ['*'])
public function filter(array $data = [], array $relationships = [], array $withCount = [], array $select = ['*'], bool $returnQuery = false)
{
$model = $this->model->with($relationships)->select($select);
if (isset($data['client']) && ! empty($data['client'])) {
$model = $model->where("customer_id", $data['client']);
}
if (isset($data['invoice_type'])) {
$model = $model->where("invoice_type", $data['invoice_type']);
}
@@ -86,6 +89,11 @@ class InvoiceRepository extends BaseRepository implements InvoiceRepositoryInter
if (count($withCount) > 0) {
$model = $model->withCount($withCount);
}
if ($returnQuery) {
return $model;
}
if (isset($data['limit']) && $data['limit']) {
return $model->latest('id')->take($data['limit'])->get();
}
@@ -5,7 +5,7 @@ namespace App\Repositories\Invoice;
use App\Repositories\BaseRepositoryInterface;
interface InvoiceRepositoryInterface extends BaseRepositoryInterface{
public function filter(array $data = [],array $relationships = [],array $withCount = [],array $select = ['*']);
public function filter(array $data = [],array $relationships = [],array $withCount = [],array $select = ['*'], bool $returnQuery = false);
public function get_billing_year(int $year = 0);
public function get_billing_month(int $month = 0);
public function get_billing_day(int|string $day = '');
+1 -125
View File
@@ -1,125 +1 @@
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-Thin.ttf') format('truetype');
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-ThinItalic.ttf') format('truetype');
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-ExtraLight.ttf') format('truetype');
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-ExtraLightItalic.ttf') format('truetype');
font-weight: 200;
font-style: italic;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-Light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-LightItalic.ttf') format('truetype');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-Italic.ttf') format('truetype');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-Medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-MediumItalic.ttf') format('truetype');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-SemiBold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-SemiBoldItalic.ttf') format('truetype');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-Bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-BoldItalic.ttf') format('truetype');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-ExtraBold.ttf') format('truetype');
font-weight: 800;
font-style: normal;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-ExtraBoldItalic.ttf') format('truetype');
font-weight: 800;
font-style: italic;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-Black.ttf') format('truetype');
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: 'Be Vietnam Pro';
src: url('/fonts/bevietnampro/BeVietnamPro-BlackItalic.ttf') format('truetype');
font-weight: 900;
font-style: italic;
}
@import url('https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&display=swap');
+1 -1
View File
@@ -233,7 +233,7 @@ $component-hover-bg: rgba($secondary, .2); // custom variable
// Font, line-height, and color for body text, headings, and more.
// Font family
$font-family-sans-serif: "Be Vietnam Pro", Helvetica, sans-serif !default;
$font-family-sans-serif: "Inter Tight", Helvetica, sans-serif !default;
$font-size-base: 0.875rem !default; // 14px
$font-size-lg: 1rem !default; // 16px
@@ -1,14 +1,21 @@
@php
$dtColumns = collect($columns)->map(function ($c) {
$dtColumns = collect($columns)->map(function ($c, $index) {
return [
'data' => $c['key'],
'name' => $c['key'],
'orderable' => $c['orderable'] ?? true,
'searchable' => $c['searchable'] ?? true,
'visible' => $c['visible'] ?? true,
];
})->values();
@endphp
<div class="d-flex justify-content-end align-items-center mb-3">
<button class="btn btn-outline-secondary btn-sm" type="button" data-bs-toggle="offcanvas" data-bs-target="#{{ $id }}-column-offcanvas">
<i class="ti ti-columns"></i> Columns
</button>
</div>
<div class="table-responsive">
<table id="{{ $id }}" class="table table-striped w-100">
<thead>
@@ -21,15 +28,41 @@
</table>
</div>
<div class="offcanvas offcanvas-end" tabindex="-1" id="{{ $id }}-column-offcanvas">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Column Visibility</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
@foreach($columns as $index => $col)
@if((!isset($col['visibility']) || $col['visibility'] !== false) && $col['key'] !== 'checkbox')
<div class="form-check form-switch mb-2">
<input type="checkbox" class="form-check-input column-toggle"
data-column-index="{{ $index }}"
id="{{ $id }}-col-{{ $index }}"
{{ !isset($col['visible']) || $col['visible'] !== false ? 'checked' : '' }}>
<label class="form-check-label" for="{{ $id }}-col-{{ $index }}">{!! $col['label'] !!}</label>
</div>
@endif
@endforeach
</div>
</div>
@push('custom-scripts')
<script>
document.addEventListener('DOMContentLoaded', function () {
$('#{{ $id }}').DataTable({
var table = $('#{{ $id }}').DataTable({
processing: true,
serverSide: true,
ajax: '{{ $ajax }}',
columns: @json($dtColumns),
});
$('#{{ $id }}-column-offcanvas').on('change', '.column-toggle', function () {
var columnIdx = $(this).data('column-index');
var column = table.column(columnIdx);
column.visible($(this).is(':checked'));
});
});
</script>
@endpush
@@ -0,0 +1,142 @@
@extends('administrator.layout.master')
@section('breadcrumb')
@php
$breadcrumbItems = [
['label' => 'Invoice'],
['label' => 'Invoice Management', 'url' => route('administrator.pages.invoice.index')],
['label' => 'Add New Invoice', 'active' => true],
];
@endphp
<x-admin-breadcrumb :items="$breadcrumbItems" />
@endsection
@section('content')
@if(auth()->guard('admins')->user()->can("add invoice"))
<form action="{{ route('administrator.pages.invoice.store') }}" method="post">
@csrf
<div class="row">
<div class="col-md-8">
<div class="card card-outline card-primary rounded-4 shadow-none">
<div class="card-header bg-white rounded-top-4">
<h5 class="card-title m-0">Invoice Information</h5>
</div>
<div class="card-body">
<div class="form-group mb-3">
<label for="customer_id">Client User</label>
<select class="form-control select2" name="customer_id" id="customer_id" data-placeholder="Select Customer">
<option value=""></option>
@if($customer->count())
@foreach($customer->all() as $item)
<option value="{{ $item->id }}">
{{ $item->firstname . ' ' . $item->lastname }}
</option>
@endforeach
@endif
</select>
</div>
<div class="form-group mb-3">
<label for="payment_id">Payment Method</label>
<select class="form-control select2" name="payment_id" id="payment_id" data-placeholder="Select Payment">
<option value=""></option>
@if($payment->count())
@foreach($payment->all() as $item)
<option value="{{ $item->id }}">{{ $item->name }}</option>
@endforeach
@endif
</select>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label for="sub_total">Sub Total</label>
<input type="number" class="form-control" name="sub_total" id="sub_total" value="0" min="0">
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label for="total">Total</label>
<input type="number" class="form-control" name="total" id="total" value="0" min="0">
</div>
</div>
</div>
<div class="form-group mb-3">
<label for="description">Description</label>
<textarea class="form-control" name="description" id="description" rows="3"></textarea>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card card-outline card-primary rounded-4 shadow-none">
<div class="card-header bg-white rounded-top-4">
<h5 class="card-title m-0">Invoice Summary</h5>
</div>
<div class="card-body">
<table class="table table-borderless">
<tbody>
<tr>
<td>Sub Total</td>
<td class="text-end" id="summary_sub_total">0đ</td>
</tr>
<tr>
<td>VAT (10%)</td>
<td class="text-end" id="summary_tax">0đ</td>
</tr>
<tr class="border-top">
<td><strong>Total</strong></td>
<td class="text-end" id="summary_total"><strong>0đ</strong></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<x-fixed-action-bar>
<x-button-crm type="button" variant="secondary" size="sm" icon="arrow-left"
route="administrator.pages.invoice.index">Cancel</x-button-crm>
<x-button-crm type="submit" variant="primary" size="sm" icon="check-circle">Create Invoice</x-button-crm>
</x-fixed-action-bar>
</form>
@else
<div class="alert alert-danger alert-dismissible">
<h5 class="mb-0"><i class="icon fas fa-ban"></i> Access Denied - You are not assigned to any Invoice
management department</h5>
</div>
@endif
@endsection
@push('custom-scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const subTotalInput = document.getElementById('sub_total');
const totalInput = document.getElementById('total');
const summarySubTotal = document.getElementById('summary_sub_total');
const summaryTax = document.getElementById('summary_tax');
const summaryTotal = document.getElementById('summary_total');
function formatCurrency(amount) {
return new Intl.NumberFormat('vi-VN').format(amount) + 'đ';
}
function calculateTotal() {
const subTotal = parseFloat(subTotalInput.value) || 0;
const tax = subTotal * 0.1;
const total = subTotal + tax;
summarySubTotal.textContent = formatCurrency(subTotal);
summaryTax.textContent = formatCurrency(tax);
summaryTotal.innerHTML = '<strong>' + formatCurrency(total) + '</strong>';
totalInput.value = total;
}
subTotalInput.addEventListener('input', calculateTotal);
});
</script>
@endpush
@@ -0,0 +1,27 @@
<div class="dt-action-dropdown dropdown">
<button class="btn btn-sm btn-light d-flex align-items-center gap-1" type="button" data-bs-toggle="dropdown"
aria-expanded="false">
<i data-lucide="more-vertical" width="14" height="14"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="{{ route('administrator.pages.invoice.edit', ['id' => $row->id]) }}">
<i data-lucide="pencil" width="14" height="14" class="me-2"></i>
View
</a>
</li>
@if(auth()->guard('admins')->user()->can("delete invoice"))
<li>
<hr class="dropdown-divider">
<form method="post" class="d-inline" action="{{ route('administrator.pages.invoice.destroy', ['id' => $row->id]) }}">
@csrf
@method('delete')
<button type="submit" class="dropdown-item text-danger">
<i data-lucide="trash-2" width="14" height="14" class="me-2 text-danger"></i>
Delete
</button>
</form>
</li>
@endif
</ul>
</div>
@@ -0,0 +1 @@
{{ $row->customer->firstname . ' ' . $row->customer->lastname }}
@@ -0,0 +1 @@
@time($row->date)
@@ -0,0 +1,3 @@
<a href="{{ route('administrator.pages.invoice.edit', ['id' => $row->id]) }}" title="View Invoice">
{{ $row->id }}
</a>
@@ -0,0 +1 @@
{{ $row->payment->name ?? 'VN Pay' }}
@@ -0,0 +1 @@
{!! $row->getStatus() !!}
@@ -0,0 +1 @@
{{ number_format($row->total, 0, ',', '.') }}đ
@@ -1,184 +1,42 @@
@extends("administrator.layout.master")
@section("content")
@extends('administrator.layout.master')
@push('plugin-styles')
@foreach (config('assets.datatable.css') as $datatable)
<link href="{{ asset($datatable) }}" rel="stylesheet" />
@endforeach
@endpush
@section('breadcrumb')
@php
$breadcrumbItems = [
['label' => 'Invoice'],
['label' => 'Invoice Management'],
['label' => 'Invoice List', 'active' => true],
];
@endphp
<x-admin-breadcrumb :items="$breadcrumbItems" />
@endsection
@section('content')
@if(auth()->guard('admins')->user()->can("manager invoice"))
<div class="card card-primary card-outline">
<div class="card-header">
<form action="{{ route('administrator.pages.invoice.index') }}" method="get">
<div class="row form-group mb-0">
<div class="col-md-3">
{{-- Customer --}}
<x-form.customer :model="request()->get('client') ?? null" />
</div>
<div class="col-md-3">
<div class="form-group">
<label for="invoice_id">
Invoice ID </label>
<input type="number" name="invoice_id" id="invoice_id" class="form-control"
value="{{ request()->invoice_id ?? "" }}">
</div>
</div>
<div class="col-md-3">
{{-- Payment --}}
<x-form.payment :model="request()->get('payment_method') ?? null" />
</div>
<div class="col-md-3">
<div class="form-group">
<label for="inputStatus">
Status</label>
<select name="status" class="form-control" id="inputStatus">
<option value="full"
@selected(request()->status === "full")>
Any
</option>
<option value="active"
@selected(request()->status === "active")>
Active
</option>
<option value="unpaid"
@selected(request()->status === "unpaid")>
Pending
</option>
<option value="cancel"
@selected(request()->status === "cancel")>
Cancel
</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="date">Invoice Date:</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="far fa-calendar-alt"></i>
</span>
</div>
<input type="text" class="form-control reservation float-right"
name="date"
value="{{ request()->date ?? "" }}"
id="date">
</div>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="due_date">Due Date:</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="far fa-calendar-alt"></i>
</span>
</div>
<input type="text" class="form-control reservation float-right"
name="due_date"
value="{{ request()->due_date ?? "" }}"
id="due_date">
</div>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="date_paid">Date Paid:</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="far fa-calendar-alt"></i>
</span>
</div>
<input type="text" class="form-control reservation float-right"
name="date_paid"
value="{{ request()->date_paid ?? "" }}"
id="date_paid">
</div>
</div>
</div>
<div class="col-md-3">
<label class="clear-search hidden">
</label>
<button type="submit" class="form-control btn btn-block bg-gradient-primary">
<i class="fas fa-search"></i> Search
</button>
</div>
</div>
</form>
<span>{{ $invoices->count() }} Records Found</span>
<div class="card card-primary card-outline rounded-4 shadow-none">
<div class="card-header bg-white d-flex justify-content-end gap-2 rounded-top-4">
<x-button-c-r-m route="administrator.pages.invoice.create" variant="primary" size="sm" icon="plus">
Add New Invoice
</x-button-c-r-m>
</div>
<div class="card-body">
<table class="table table-invoice table-bordered">
<thead>
<tr>
<th style="width: 10px">
<label for=""></label><input type="checkbox" name="check" class="check-all" id="">
</th>
<th>Invoice #</th>
<th>Client Name</th>
<th>Date</th>
<th>Payment Method</th>
<th>Total</th>
<th>Status</th>
<th style="width: 40px"></th>
<th style="width: 40px"></th>
</tr>
</thead>
<tbody>
@if($invoices->count())
@foreach($invoices->all() as $item)
<tr>
<td class="text-center">
<input type="checkbox" name="id[]" id="" value="{{ $item->id }}">
</td>
<td class="text-center">
<a href="{{ route("administrator.pages.invoice.edit",['id' => $item->id]) }}"
title="View Order"> {{ $item->id }}</a>
</td>
<td class="text-center">
@isset($item->customer->firstname)
{{ $item->customer->firstname . ' ' . $item->customer->lastname }}
@endisset
</td>
<td class="text-center">
@time($item->date)
</td>
<td class="text-center">
{{ $item->payment->name ?? 'VN Pay' }}
</td>
<td class="text-center">
{{ number_format($item->total,0,',','.') }}đ
</td>
<td class="text-center">
{!! $item->getStatus() !!}
</td>
<td class="text-center">
<a class="btn btn-warning btn-sm"
href="{{ route('administrator.pages.invoice.edit',['id' => $item->id ]) }}">
<i class="fas fa-search"></i>
</a>
</td>
<td class="text-center">
@if(auth()->guard('admins')->user()->can("delete invoice"))
<form method="post" class="d-inline"
action="{{ route('administrator.pages.invoice.destroy',['id' => $item->id]) }}">
@csrf
@method('delete')
<button type="submit" data-toggle="tooltip" data-placement="top" title=""
class="btn btn-sm btn-danger" data-original-title="Xoá">
<i class="fas fa-trash"></i>
</button>
</form>
@endif
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>
<div class="card-footer">
{{ $invoices->withQueryString()->links() }}
<div class="card-body p-0">
@php
$invoiceColumns = [
['key' => 'invoice_num', 'label' => 'Invoice #'],
['key' => 'date', 'label' => 'Date'],
['key' => 'customer', 'label' => 'Client Name'],
['key' => 'payment', 'label' => 'Payment Method'],
['key' => 'total', 'label' => 'Total'],
['key' => 'status', 'label' => 'Status'],
['key' => 'action', 'label' => 'Action'],
];
@endphp
<x-datatable id="invoiceTable" ajax="{{ route('administrator.pages.invoice.datatable') }}" :columns="$invoiceColumns" />
</div>
</div>
@else
@@ -188,3 +46,8 @@
</div>
@endif
@endsection
@push('plugin-scripts')
@foreach (config('assets.datatable.js') as $datatable)
<script src="{{ asset($datatable) }}"></script>
@endforeach
@endpush
+51 -14
View File
@@ -2,32 +2,34 @@
@php
$dtColumns = collect($columns)
->map(function ($c) {
->map(function ($c, $index) {
return [
'data' => $c['key'],
'name' => $c['key'],
'orderable' => $c['orderable'] ?? true,
'searchable' => $c['searchable'] ?? true,
'className' => $c['className'] ?? '',
'visible' => $c['visible'] ?? true,
];
})
->values();
@endphp
@if ($bulkDeleteUrl)
<div class="d-flex justify-content-start align-items-center mb-3" id="{{ $id }}-bulk-actions"
style="display: none !important;">
<div class="bg-danger-subtle text-danger rounded-3 d-flex align-items-center gap-3 px-4 py-2">
<div class="d-flex align-items-center gap-2">
<i data-lucide="alert-circle" width="16" height="16"></i>
<span class="fw-medium"><span id="{{ $id }}-selected-count">0</span> items selected</span>
</div>
<div class="vr bg-danger opacity-25"></div>
<x-button-c-r-m type="button" variant="danger" size="sm" icon="trash-2" id="{{ $id }}-bulk-delete-opener-btn"
class="shadow-sm">
Delete Selected
</x-button-c-r-m>
<div class="d-flex justify-content-start align-items-center mb-3" id="{{ $id }}-bulk-actions"
style="display: none !important;">
<div class="bg-danger-subtle text-danger rounded-3 d-flex align-items-center gap-3 px-4 py-2">
<div class="d-flex align-items-center gap-2">
<i data-lucide="alert-circle" width="16" height="16"></i>
<span class="fw-medium"><span id="{{ $id }}-selected-count">0</span> items selected</span>
</div>
<div class="vr bg-danger opacity-25"></div>
<x-button-c-r-m type="button" variant="danger" size="sm" icon="trash-2" id="{{ $id }}-bulk-delete-opener-btn"
class="shadow-sm">
Delete Selected
</x-button-c-r-m>
</div>
</div>
<div class="modal fade" id="{{ $id }}-bulk-delete-modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
@@ -56,6 +58,7 @@ $dtColumns = collect($columns)
</div>
</div>
@endif
<div class="data-table-wrapper position-relative shadow-none">
<div class="loading-process" id="{{ $id }}-loading" style="display: none;">
<div class="loading-bar"></div>
@@ -110,6 +113,27 @@ $dtColumns = collect($columns)
</tbody>
</table>
</div>
<div class="offcanvas offcanvas-end" tabindex="-1" id="{{ $id }}-column-offcanvas">
<div class="offcanvas-header border-bottom">
<h5 class="offcanvas-title">Column Visibility</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
@foreach($columns as $index => $col)
@if((!isset($col['visibility']) || $col['visibility'] !== false) && $col['key'] !== 'checkbox')
<div class="form-check form-switch mb-2">
<input type="checkbox" class="form-check-input column-toggle"
data-column-index="{{ $index }}"
id="{{ $id }}-col-{{ $index }}"
{{ !isset($col['visible']) || $col['visible'] !== false ? 'checked' : '' }}>
<label class="form-check-label" for="{{ $id }}-col-{{ $index }}">{{ $col['label'] }}</label>
</div>
@endif
@endforeach
</div>
</div>
@push('custom-scripts')
<script>
document.addEventListener('DOMContentLoaded', function () {
@@ -139,7 +163,7 @@ $dtColumns = collect($columns)
zeroRecords: '<div class="d-flex flex-column align-items-center justify-content-center py-5"><div class="text-secondary mb-3"><i data-lucide="inbox" width="28" height="28" stroke-width="1.5"></i></div><h6 class="text-secondary fw-medium">No matching records found</h6></div>',
emptyTable: '<div class="d-flex flex-column align-items-center justify-content-center py-5"><div class="text-secondary mb-3"><i data-lucide="inbox" width="28" height="28" stroke-width="1.5"></i></div><h6 class="text-secondary fw-medium">No data available</h6></div>'
},
dom: "<'row px-3 py-2 align-items-center'<'col-sm-12 col-md-6 d-inline-flex align-items-center gap-2'<'dt-filter-toggle'>f><'col-sm-12 col-md-6 d-inline-flex align-items-center justify-content-end gap-2'l>>" +
dom: "<'row px-3 py-2 align-items-center'<'col-sm-12 col-md-6 d-inline-flex align-items-center gap-2'<'dt-filter-toggle'><'dt-column-toggle'>f><'col-sm-12 col-md-6 d-inline-flex align-items-center justify-content-end gap-2'l>>" +
"<'row p-0'<'col-sm-12'tr>>" +
"<'row px-3 pb-3 pt-3 align-items-center'<'col-sm-12 col-md-5 text-muted small d-inline-flex align-items-center'i><'col-sm-12 col-md-7 d-inline-flex align-items-center justify-content-end gap-2'p>>",
initComplete: function () {
@@ -152,6 +176,13 @@ $dtColumns = collect($columns)
'</button>'
);
wrapper.find('.dt-column-toggle').html(
'<button type="button" class="btn btn-sm btn-outline-secondary d-flex align-items-center gap-1" data-bs-toggle="offcanvas" data-bs-target="#{{ $id }}-column-offcanvas">' +
'<i data-lucide="columns" width="14" height="14"></i>' +
'<span>Columns</span>' +
'</button>'
);
$('#{{ $id }}-filter-toggle-btn').on('click', function () {
var filterRow = $('#{{ $id }}-filter-row');
var btn = $(this);
@@ -207,6 +238,12 @@ $dtColumns = collect($columns)
}
});
$('#{{ $id }}-column-offcanvas').on('change', '.column-toggle', function () {
var columnIdx = $(this).data('column-index');
var column = table.column(columnIdx);
column.visible($(this).is(':checked'));
});
$('#{{ $id }} tbody').on('dblclick', 'tr', function () {
const editLink = $(this).find('a.btn-outline-primary, a[href*="edit"]').first();
if (editLink.length && editLink.attr('href')) {
+3
View File
@@ -473,6 +473,9 @@ Route::prefix('')->group(function () {
//Invoice
Route::controller(InvoiceController::class)->group(function () {
Route::get('list-all-invoice', 'index')->name('administrator.pages.invoice.index');
Route::get('add-new-invoice', 'create')->name('administrator.pages.invoice.create');
Route::post('add-new-invoice', 'store')->name('administrator.pages.invoice.store');
Route::get('invoice-datatable', 'datatable')->name('administrator.pages.invoice.datatable');
Route::get('view-invoice-item/{id}', 'edit')->name('administrator.pages.invoice.edit');
Route::put('view-invoice-item/{id}', 'update')->name('administrator.pages.invoice.update');
Route::delete('remove-invoice/{id}', 'destroy')->name('administrator.pages.invoice.destroy');