feat(membership): membuat curd dengan modal

-membuat action edit dengan modal
-menghapus sidebar yang tidak diperlukan
This commit is contained in:
Yoga Pangestu 2025-09-25 19:24:41 +07:00
parent 73059387c5
commit 28c69dbf23
9 changed files with 367 additions and 6 deletions

View File

@ -0,0 +1,59 @@
<?php
namespace App\Livewire\Datatable;
use App\Models\Membership;
use App\Traits\Datatable\WithAppendColumn;
use App\Traits\Datatable\WithConfiguration;
use App\Traits\Datatable\WithPrependColumn;
use Illuminate\Database\Eloquent\Builder;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
class MembershipsTable extends DataTableComponent
{
use WithAppendColumn, WithConfiguration, WithPrependColumn;
protected $model = Membership::class;
public function columns(): array
{
return [
Column::make('Nama', 'name')->searchable(),
Column::make('Min. Pembelian', 'min_purchase')
->format(fn ($value) => currency($value, 'Rp'))
->searchable()
->sortable(),
Column::make('Diskon', 'discount_percentage')
->format(fn ($value) => percentage($value))
->searchable()
->sortable(),
Column::make('Aksi')
->label(function ($row) {
$actions = '';
$actions .= view('components.datatables.edit-modal', [
'id' => $row->id,
'method' => 'update',
'modalTitle' => 'Ubah Membership',
])->render();
$actions .= view('components.datatables.delete', [
'id' => $row->id,
'deleteRoute' => route('studio.loyalty.membership.delete', $row->id),
])->render();
return $actions;
})
->html(),
];
}
public function builder(): Builder
{
return Membership::select('id', 'name', 'discount_percentage', 'min_purchase');
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Livewire\Forms;
use App\Models\Membership;
use Livewire\Form;
class MembershipForm extends Form
{
public ?Membership $membership = null;
public string $name = '';
public string $min_purchase = '';
public string $discount_percentage = '';
public ?string $terms_conditions = null;
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:50'],
'min_purchase' => ['required', 'regex:/^[0-9.]+$/', 'max:99999999999'],
'discount_percentage' => ['required', 'numeric', 'between:0,100'],
'terms_conditions' => ['nullable', 'string'],
];
}
public function validationAttributes(): array
{
return [
'name' => 'nama',
'min_purchase' => 'min. pembelian',
'discount_percentage' => 'persentase diskon',
'terms_conditions' => 'syarat dan ketentuan',
];
}
public function setMembership(Membership $membership)
{
$this->membership = $membership;
$this->name = $membership->name;
$this->min_purchase = currency($membership->min_purchase);
$this->terms_conditions = $membership->terms_conditions;
$this->discount_percentage = $membership->discount_percentage;
}
public function store()
{
$this->validate();
Membership::create([
'name' => $this->name,
'min_purchase' => replaceCurrency($this->min_purchase),
'discount_percentage' => $this->discount_percentage,
'terms_conditions' => $this->terms_conditions,
]);
}
public function update()
{
$this->validate();
$this->membership->update([
'name' => $this->name,
'min_purchase' => replaceCurrency($this->min_purchase),
'discount_percentage' => $this->discount_percentage,
'terms_conditions' => $this->terms_conditions,
]);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace App\Livewire\Studio\Loyalty;
use App\Livewire\Forms\MembershipForm;
use App\Models\Membership as MembershipModel;
use App\Traits\WithCloseModal;
use App\Traits\WithConfirmation;
use App\Traits\WithUpdatedData;
use Flux\Flux;
use Livewire\Attributes\On;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Membership')]
class Membership extends Component
{
use WithCloseModal, WithConfirmation, WithUpdatedData;
public MembershipForm $form;
public string $method = 'create';
public string $modalTitle = '';
#[On('modal:open')]
public function openModal(string $method, string $modalTitle, ?string $id = null)
{
$this->resetValidation();
$this->resetErrorBag();
$this->method = $method;
$this->modalTitle = $modalTitle;
if ($id) {
$this->form->setMembership(MembershipModel::findOrFail($id));
}
}
public function create()
{
$this->form->store();
$this->dispatch('refreshDatatable');
Flux::toast(
heading: 'Berhasil',
text: 'Membership berhasil ditambahkan.',
variant: 'success',
duration: 3000
);
Flux::modals()->close();
}
public function update()
{
$this->form->update();
$this->dispatch('refreshDatatable');
Flux::toast(
heading: 'Berhasil',
text: 'Membership berhasil diperbarui.',
variant: 'success',
duration: 3000
);
Flux::modals()->close();
}
public function delete(MembershipModel $membership)
{
$membership->delete();
$this->dispatch('refreshDatatable');
Flux::toast(
heading: 'Berhasil',
text: 'Membership berhasil dihapus.',
variant: 'success',
);
Flux::modals()->close();
}
public function render()
{
return view('livewire.studio.loyalty.memberships', [
'pageTitle' => 'Membership',
]);
}
}

22
app/Models/Membership.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Membership extends Model
{
use HasFactory, SoftDeletes;
protected $guarded = ['id'];
protected function casts(): array
{
return [
'min_purchase' => 'int',
'discount_percent' => 'decimal:2',
];
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('memberships', function (Blueprint $table) {
$table->id();
$table->string('name', 50);
$table->text('terms_conditions')->nullable();
$table->unsignedInteger('min_purchase');
$table->decimal('discount_percentage', 5, 2);
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('memberships');
}
};

View File

@ -0,0 +1,7 @@
<flux:modal.trigger name="form-modal">
<flux:tooltip content="Ubah">
<flux:button href="javascript:void(0)" variant="primary" color="yellow" icon="pencil-square" size="sm"
wire:click="$dispatch('modal:open', {method: '{{ $method }}', 'modalTitle': '{{ $modalTitle }}', 'id': '{{ $id }}'})">
</flux:button>
</flux:tooltip>
</flux:modal.trigger>

View File

@ -24,11 +24,14 @@ class="px-2 hidden dark:flex" />
</div>
</div>
<flux:navlist.item icon="inbox" badge="12" href="#">Inbox</flux:navlist.item>
<flux:navlist.group expandable heading="Favorites" class="hidden lg:grid">
<flux:navlist.item href="#">Marketing site</flux:navlist.item>
<flux:navlist.item href="#">Android app</flux:navlist.item>
<flux:navlist.item href="#">Brand guidelines</flux:navlist.item>
</flux:navlist.group>
<div class="mt-3 mb-1">
<div class="text-zinc-500 dark:text-gray-300 text-sm/6">Loyalty
</div>
<div class="grid gap-2">
<flux:navlist.item icon="star" href="{{ route('studio.loyalty.membership.index') }}"
:current="request()->routeIs('studio.loyalty.membership.*')" wire:navigate.hover>Membership
</flux:navlist.item>
</div>
</div>
</flux:navlist>
</flux:sidebar>

View File

@ -0,0 +1,64 @@
<flux:main>
<div class="flex justify-between items-center">
<div>
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
</div>
<div>
<flux:modal.trigger name="form-modal">
<flux:button href="javascript:void(0)" variant="primary" class="text-sm"
wire:click="$dispatch('modal:open', {method: 'create', 'modalTitle': 'Tambah Membership'})">Tambah
</flux:button>
</flux:modal.trigger>
</div>
</div>
<div class="mt-6">
<livewire:datatable.memberships-table />
</div>
@include('components.confirmation.delete')
<flux:modal name="form-modal" class="w-[95%] max-w-sm md:max-w-xl mx-auto" @close="closeModal('form-modal')">
<div class="p-4 space-y-6">
<flux:heading size="lg">{{ $modalTitle }}</flux:heading>
<flux:input label="Nama" placeholder="Masukkan nama membership" wire:model.live.debounce.500ms="form.name"
autofocus autocomplete="off" clearable />
<flux:field>
<flux:label>Min. Belanja</flux:label>
<flux:input.group>
<flux:input.group.prefix>Rp</flux:input.group.prefix>
<flux:input placeholder="Masukkan minimal belanja" x-mask:dynamic="$money($input, ',')"
wire:model.live.debounce.500ms="form.min_purchase" autocomplete="off" clearable />
</flux:input.group>
<flux:error name="form.min_purchase" />
</flux:field>
<flux:field>
<flux:label>Persentase Diskon</flux:label>
<flux:input.group>
<flux:input placeholder="Masukkan persentase diskon"
wire:model.live.debounce.500ms="form.discount_percentage" autocomplete="off" clearable />
<flux:input.group.suffix>%</flux:input.group.suffix>
</flux:input.group>
<flux:error name="form.discount_percentage" />
</flux:field>
<flux:editor label="Syarat dan Ketentuan" wire:model="form.terms_conditions"
placeholder="Masukkan syarat dan ketentuan" auotocomplete="off"
class="**:data-[slot=content]:min-h-[100px]!" />
<div class="flex">
<flux:spacer />
<flux:button variant="primary" class="sm:w-auto cursor-pointer" wire:click="{{ $method }}">
Simpan
</flux:button>
</div>
</div>
</flux:modal>
</flux:main>

View File

@ -3,6 +3,7 @@
use App\Livewire\Auth\Login;
use App\Livewire\Auth\Logout;
use App\Livewire\Studio\Dashboard\Overview;
use App\Livewire\Studio\Loyalty\Membership as MembershipComponent;
use App\Livewire\Studio\Master\Outlet\Create as OutletCreate;
use App\Livewire\Studio\Master\Outlet\Edit as OutletEdit;
use App\Livewire\Studio\Master\Outlet\Index as OutletIndex;
@ -48,4 +49,11 @@
Route::get('users/{user}/edit', UserEdit::class)->name('edit');
Route::get('users/{user}/delete', UserCreate::class)->name('delete');
});
Route::prefix('loyalty')
->as('studio.loyalty.membership.')
->group(function () {
Route::get('memberships', MembershipComponent::class)->name('index');
Route::delete('memberships/{membership}/delete', MembershipComponent::class)->name('delete');
});
});