feat: Implement soft deletes for customer, invoice, order, and transaction models, and add customer edit and trash management to the admin panel.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
<x-button-crm type="button" variant="primary" size="sm" route="customer.create" icon="plus-circle">Add Customer</x-button-crm>
|
||||
<x-button-crm type="button" variant="warning" size="sm" route="customer.trash" icon="trash-2">Trash</x-button-crm>
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="d-flex gap-2">
|
||||
<x-button-crm type="button" variant="outline-success" size="sm" route="customer.restore" :params="['id' => $row->id]" icon="rotate-ccw" />
|
||||
<x-button-crm type="button" variant="outline-danger" size="sm" icon="trash-2"
|
||||
data-bs-toggle="modal" data-bs-target="#forceDeleteModal-{{ $row->id }}" />
|
||||
</div>
|
||||
|
||||
@include('administrator.pages.customer.datatable.modal', [
|
||||
'deleteRoute' => 'customer.forceDelete',
|
||||
'itemLabel' => 'Customer',
|
||||
'itemId' => $row->id,
|
||||
'itemName' => $row->fullname,
|
||||
'modalId' => 'forceDeleteModal-' . $row->id
|
||||
])
|
||||
@@ -0,0 +1,47 @@
|
||||
<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('customer.edit', ['id' => $row->id]) }}">
|
||||
<i data-lucide="pencil" width="14" height="14" class="me-2"></i>
|
||||
Edit
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
<li>
|
||||
<button type="button" class="dropdown-item text-danger" data-bs-toggle="modal"
|
||||
data-bs-target="#deleteModal">
|
||||
<i data-lucide="trash-2" width="14" height="14" class="me-2 text-danger"></i>
|
||||
Delete
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<form action="{{ route('customer.destroy', ['id' => $row->id]) }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<input type="hidden" name="id" value="{{ $row->id }}">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Xoá khách hàng</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Bạn có chắc chắn muốn xoá khách hàng này?
|
||||
<strong>{{ $row->fullname }}</strong>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Huỷ</button>
|
||||
<button type="submit" class="btn btn-danger" id="confirmDeleteBtn">Xoá</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,36 @@
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="{{ $modalId ?? 'deleteModal' }}" tabindex="-1"
|
||||
aria-labelledby="deleteModalLabel-{{ $modalId ?? 'deleteModal' }}" aria-hidden="true" data-bs-backdrop="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||
<div class="modal-content shadow-lg border-0">
|
||||
<div class="modal-header bg-danger text-white py-2">
|
||||
<h6 class="modal-title d-flex align-items-center" id="deleteModalLabel-{{ $modalId ?? 'deleteModal' }}">
|
||||
<i data-lucide="trash-2" class="me-2" style="width: 16px; height: 16px;"></i>
|
||||
Xác nhận xóa
|
||||
</h6>
|
||||
<button type="button" class="btn-close btn-close-white shadow-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body py-4">
|
||||
<div class="text-center">
|
||||
<div class="mb-3">
|
||||
<i data-lucide="alert-triangle" class="text-warning" style="width: 54px; height: 54px;"></i>
|
||||
</div>
|
||||
<h6 class="fw-bold mb-2">Bạn có chắc chắn không?</h6>
|
||||
<p class="text-muted small mb-1">Xóa {{ $itemLabel }}:</p>
|
||||
<p class="fw-bold mb-3 text-dark">{{ $itemName ?? '' }}</p>
|
||||
<div class="px-3 py-2 bg-danger-subtle rounded-3">
|
||||
<p class="text-danger small mb-0 fw-medium">Hành động này không thể hoàn tác!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light border-0 py-2 d-flex justify-content-center gap-2">
|
||||
<x-button-c-r-m type="button" variant="secondary" size="sm" icon="x" data-bs-dismiss="modal">Hủy bỏ</x-button-c-r-m>
|
||||
<form action="{{ route($deleteRoute, ['id' => $itemId ?? 0]) }}" method="POST" class="d-inline m-0">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<x-button-c-r-m type="submit" variant="danger" size="sm" icon="trash-2">Xác nhận xóa</x-button-c-r-m>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
@php
|
||||
$status = App\Enums\Status::tryFrom($row->status);
|
||||
@endphp
|
||||
|
||||
@if($status)
|
||||
<x-admin-badge :variant="$status->value">
|
||||
{{ $status->label() }}
|
||||
</x-admin-badge>
|
||||
@else
|
||||
<x-admin-badge variant="secondary">
|
||||
Unknown
|
||||
</x-admin-badge>
|
||||
@endif
|
||||
@@ -0,0 +1,116 @@
|
||||
@extends("administrator.layout.master")
|
||||
@section("content")
|
||||
@if(auth()->guard('admins')->user()->can("edit customer"))
|
||||
<div class="card card-outline card-primary">
|
||||
<form action="{{ route('customer.update', ['id' => $customer->id]) }}" method="post">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{{--Fullname--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="fullname">Fullname</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="fullname" name="fullname" placeholder="Fullname"
|
||||
value="{{ old('fullname', $customer->fullname) }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{{--Company--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="company">Company</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="company" name="company" placeholder="Company"
|
||||
value="{{ old('company', $customer->company) }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{{--Email--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="email">Email Address</label>
|
||||
<div class="col-md-8">
|
||||
<input type="email" id="email" name="email" placeholder="Email Address"
|
||||
value="{{ old('email', $customer->email) }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{{--Phone--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="phone">Phone Number</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="phone" name="phone" placeholder="(+84) 123. 456. 789)"
|
||||
value="{{ old('phone', $customer->phone) }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{{--Password--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="password">Password (leave blank to keep current)</label>
|
||||
<div class="col-md-8">
|
||||
<input type="password" id="password" name="password" placeholder="******"
|
||||
value="" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{{--Address--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="address">Address</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="address" name="address" placeholder="Address"
|
||||
value="{{ old('address', $customer->address) }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{{--City--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="city">City</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="city" name="city" placeholder="City"
|
||||
value="{{ old('city', $customer->city) }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{{--State--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="state">State</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="state" name="state" placeholder="State"
|
||||
value="{{ old('state', $customer->state) }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{{--Postcode--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="postcode">Postcode</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="postcode" name="postcode" placeholder="Postcode"
|
||||
value="{{ old('postcode', $customer->postcode) }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{{--Country--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="country">Country</label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="country" name="country" placeholder="Country"
|
||||
value="{{ old('country', $customer->country) }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
{{--Marketing Emails--}}
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4" for="email_marketing">Marketing Emails Opt-in</label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="email_marketing" id="email_marketing" @checked(old('email_marketing', $customer->email_marketing) == 1)>
|
||||
Send Client Marketing Emails
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-outline-primary">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
@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 Customer
|
||||
management department</h5>
|
||||
</div>
|
||||
@endif
|
||||
@endsection
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
@section('breadcrumb')
|
||||
@php
|
||||
$breadcrumbItems = [
|
||||
['label' => 'Customers', 'active' => true],
|
||||
];
|
||||
$breadcrumbItems = [
|
||||
['label' => 'Customers', 'active' => true],
|
||||
];
|
||||
@endphp
|
||||
<x-admin-breadcrumb :items="$breadcrumbItems" />
|
||||
@endsection
|
||||
@@ -18,19 +18,20 @@
|
||||
@if (auth()->guard('admins')->user()->can('manager customer'))
|
||||
<div class="card card-primary card-outline rounded-3 border-primary-subtle">
|
||||
<div class="card-header d-flex justify-content-end gap-2">
|
||||
@include('administrator.pages.block.customer.btn')
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<x-datatable id="customerTable" ajax="{{ route('customer.datatable') }}" :columns="[
|
||||
['key' => 'fullname', 'label' => 'Fullname'],
|
||||
['key' => 'company', 'label' => 'Company'],
|
||||
['key' => 'email', 'label' => 'Email'],
|
||||
['key' => 'hosting_count', 'label' => 'Hosting'],
|
||||
['key' => 'vps_count', 'label' => 'VPS'],
|
||||
['key' => 'domain_count', 'label' => 'Domain'],
|
||||
['key' => 'created_at', 'label' => 'Created'],
|
||||
['key' => 'status', 'label' => 'Status'],
|
||||
['key' => 'action', 'label' => 'Action'],
|
||||
]" />
|
||||
['key' => 'fullname', 'label' => 'Fullname'],
|
||||
['key' => 'company', 'label' => 'Company'],
|
||||
['key' => 'email', 'label' => 'Email'],
|
||||
['key' => 'hosting_count', 'label' => 'Hosting'],
|
||||
['key' => 'vps_count', 'label' => 'VPS'],
|
||||
['key' => 'domain_count', 'label' => 'Domain'],
|
||||
['key' => 'created_at', 'label' => 'Created'],
|
||||
['key' => 'status', 'label' => 'Status'],
|
||||
['key' => 'action', 'label' => 'Action'],
|
||||
]" />
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
@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' => 'Customers', 'route' => route('customer.index')],
|
||||
['label' => 'Trash', 'active' => true],
|
||||
];
|
||||
@endphp
|
||||
<x-admin-breadcrumb :items="$breadcrumbItems" />
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
@if (auth()->guard('admins')->user()->can('manager customer'))
|
||||
<div class="card card-primary card-outline rounded-3 border-primary-subtle">
|
||||
<div class="card-header d-flex justify-content-end gap-2">
|
||||
<x-button-crm type="button" variant="secondary" size="sm" route="customer.index" icon="arrow-left">Back to List</x-button-crm>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<x-datatable id="customerTrashTable" ajax="{{ route('customer.trash.datatable') }}" :columns="[
|
||||
['key' => 'fullname', 'label' => 'Fullname'],
|
||||
['key' => 'company', 'label' => 'Company'],
|
||||
['key' => 'email', 'label' => 'Email'],
|
||||
['key' => 'deleted_at', 'label' => 'Deleted At'],
|
||||
['key' => 'action', 'label' => 'Action', 'searchable' => false],
|
||||
]" />
|
||||
</div>
|
||||
</div>
|
||||
@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 Customer
|
||||
management department</h5>
|
||||
</div>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@push('plugin-scripts')
|
||||
@foreach (config('assets.datatable.js') as $datatable)
|
||||
<script src="{{ asset($datatable) }}"></script>
|
||||
@endforeach
|
||||
@endpush
|
||||
Reference in New Issue
Block a user