feat(faq): implementasi CRUD
This commit is contained in:
parent
182cb1fda0
commit
4f09ce32b2
95
app/Livewire/Datatable/Studio/Manage/FaqsTable.php
Normal file
95
app/Livewire/Datatable/Studio/Manage/FaqsTable.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Datatable\Studio\Manage;
|
||||
|
||||
use App\Models\Faq;
|
||||
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 FaqsTable extends DataTableComponent
|
||||
{
|
||||
use WithConfiguration, WithPrependColumn;
|
||||
|
||||
protected $model = Faq::class;
|
||||
|
||||
public function columns(): array
|
||||
{
|
||||
return [
|
||||
Column::make('Pertanyaan', 'question')->searchable(),
|
||||
|
||||
Column::make('Aksi')
|
||||
->label(function ($row) {
|
||||
$actions = '';
|
||||
$maxSortOrder = Faq::max('sort_order');
|
||||
|
||||
if (auth()->user()->can('update faq')) {
|
||||
if ($row->sort_order > 1) {
|
||||
$actions .= view('components.datatables.sort-button', [
|
||||
'id' => $row->hash,
|
||||
'module' => 'faq',
|
||||
'direction' => 'up',
|
||||
'tooltip' => 'Naik',
|
||||
'color' => 'cyan',
|
||||
'icon' => 'arrow-up',
|
||||
])->render();
|
||||
|
||||
$actions .= view('components.datatables.sort-button', [
|
||||
'id' => $row->hash,
|
||||
'module' => 'faq',
|
||||
'direction' => 'first',
|
||||
'tooltip' => 'Pertama',
|
||||
'color' => 'emerald',
|
||||
'icon' => 'bars-arrow-up',
|
||||
])->render();
|
||||
}
|
||||
|
||||
if ($row->sort_order < $maxSortOrder) {
|
||||
$actions .= view('components.datatables.sort-button', [
|
||||
'id' => $row->hash,
|
||||
'module' => 'faq',
|
||||
'direction' => 'down',
|
||||
'tooltip' => 'Turun',
|
||||
'color' => 'orange',
|
||||
'icon' => 'arrow-down',
|
||||
])->render();
|
||||
|
||||
$actions .= view('components.datatables.sort-button', [
|
||||
'id' => $row->hash,
|
||||
'module' => 'faq',
|
||||
'direction' => 'last',
|
||||
'tooltip' => 'Terakhir',
|
||||
'color' => 'rose',
|
||||
'icon' => 'bars-arrow-down',
|
||||
])->render();
|
||||
}
|
||||
}
|
||||
|
||||
if (auth()->user()->can('update faq')) {
|
||||
$actions .= view('components.datatables.edit-modal', [
|
||||
'id' => $row->hash,
|
||||
'method' => 'update',
|
||||
'modalTitle' => 'Ubah Kategori',
|
||||
])->render();
|
||||
}
|
||||
|
||||
if (auth()->user()->can('delete faq')) {
|
||||
$actions .= view('components.datatables.delete', [
|
||||
'id' => $row->hash,
|
||||
'deleteRoute' => route('studio.manage.faq.delete', $row->hash),
|
||||
])->render();
|
||||
}
|
||||
|
||||
return $actions;
|
||||
})
|
||||
->html(),
|
||||
];
|
||||
}
|
||||
|
||||
public function builder(): Builder
|
||||
{
|
||||
return Faq::select('id', 'question', 'sort_order')->orderBy('sort_order');
|
||||
}
|
||||
}
|
||||
109
app/Livewire/Forms/Studio/Manage/FaqForm.php
Normal file
109
app/Livewire/Forms/Studio/Manage/FaqForm.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Forms\Studio\Manage;
|
||||
|
||||
use App\Models\Faq;
|
||||
use Livewire\Form;
|
||||
|
||||
class FaqForm extends Form
|
||||
{
|
||||
public ?Faq $faq = null;
|
||||
|
||||
public string $question = '';
|
||||
|
||||
public string $answer = '';
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'question' => ['required', 'string', 'max:100'],
|
||||
'answer' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function validationAttributes(): array
|
||||
{
|
||||
return [
|
||||
'question' => 'pertanyaan',
|
||||
'answer' => 'jawaban',
|
||||
];
|
||||
}
|
||||
|
||||
public function setFaq(Faq $faq)
|
||||
{
|
||||
$this->faq = $faq;
|
||||
|
||||
$this->question = $faq->question;
|
||||
$this->answer = $faq->answer;
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
Faq::create([
|
||||
'question' => $this->question,
|
||||
'answer' => $this->answer,
|
||||
'sort_order' => Faq::count() + 1,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$this->faq->update([
|
||||
'question' => $this->question,
|
||||
'answer' => $this->answer,
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$this->faq->delete();
|
||||
|
||||
Faq::where('sort_order', '>', $this->faq->sort_order)
|
||||
->orderBy('sort_order')
|
||||
->get()
|
||||
->each(function ($cat) {
|
||||
$cat->update(['sort_order' => $cat->sort_order - 1]);
|
||||
});
|
||||
}
|
||||
|
||||
public function sortOrder(string $direction)
|
||||
{
|
||||
if (in_array($direction, ['up', 'down'])) {
|
||||
$swap = null;
|
||||
|
||||
if ($direction === 'up') {
|
||||
$swap = Faq::where('sort_order', '<', $this->faq->sort_order)
|
||||
->orderByDesc('sort_order')
|
||||
->first();
|
||||
} else {
|
||||
$swap = Faq::where('sort_order', '>', $this->faq->sort_order)
|
||||
->orderBy('sort_order')
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($swap) {
|
||||
$temp = $this->faq->sort_order;
|
||||
$this->faq->update(['sort_order' => $swap->sort_order]);
|
||||
$swap->update(['sort_order' => $temp]);
|
||||
}
|
||||
} elseif ($direction === 'first') {
|
||||
$this->faq->update(['sort_order' => Faq::min('sort_order') - 1]);
|
||||
$this->normalizeSortOrder();
|
||||
} elseif ($direction === 'last') {
|
||||
$this->faq->update(['sort_order' => Faq::max('sort_order') + 1]);
|
||||
$this->normalizeSortOrder();
|
||||
}
|
||||
}
|
||||
|
||||
protected function normalizeSortOrder()
|
||||
{
|
||||
$categories = Faq::orderBy('sort_order')->get();
|
||||
foreach ($categories as $index => $faq) {
|
||||
$faq->update(['sort_order' => $index + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
99
app/Livewire/Studio/Manage/Faq.php
Normal file
99
app/Livewire/Studio/Manage/Faq.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Studio\Manage;
|
||||
|
||||
use App\Livewire\Forms\Studio\Manage\FaqForm;
|
||||
use App\Models\Faq as FaqModel;
|
||||
use App\Traits\WithAuthorization;
|
||||
use App\Traits\WithCloseModal;
|
||||
use App\Traits\WithConfirmation;
|
||||
use App\Traits\WithToast;
|
||||
use App\Traits\WithUpdatedData;
|
||||
use Flux\Flux;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Component;
|
||||
|
||||
#[Title('FAQs')]
|
||||
class Faq extends Component
|
||||
{
|
||||
use WithAuthorization, WithCloseModal, WithConfirmation, WithToast, WithUpdatedData;
|
||||
|
||||
public FaqForm $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->setFaq(FaqModel::byHashOrFail($id));
|
||||
}
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$this->canOrAbort('create faq');
|
||||
|
||||
$this->form->store();
|
||||
|
||||
$this->dispatch('refreshDatatable');
|
||||
|
||||
$this->toast('FAQs berhasil ditambahkan.');
|
||||
|
||||
Flux::modals()->close();
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$this->canOrAbort('update faq');
|
||||
|
||||
$this->form->update();
|
||||
|
||||
$this->dispatch('refreshDatatable');
|
||||
|
||||
$this->toast('FAQs berhasil diperbarui.');
|
||||
|
||||
Flux::modals()->close();
|
||||
}
|
||||
|
||||
public function delete(FaqModel $faq)
|
||||
{
|
||||
$this->form->faq = $faq;
|
||||
|
||||
$this->form->delete();
|
||||
|
||||
$this->dispatch('refreshDatatable');
|
||||
|
||||
$this->toast('FAQs berhasil dihapus.');
|
||||
|
||||
Flux::modals()->close();
|
||||
}
|
||||
|
||||
#[On('fn:sortOrder')]
|
||||
public function sortOrder(FaqModel $faq, string $direction)
|
||||
{
|
||||
$this->canOrAbort('update faq');
|
||||
|
||||
$this->form->faq = $faq;
|
||||
|
||||
$this->form->sortOrder($direction);
|
||||
|
||||
$this->dispatch('refreshDatatable');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.studio.manage.faqs', [
|
||||
'pageTitle' => 'FAQs',
|
||||
]);
|
||||
}
|
||||
}
|
||||
21
app/Models/Faq.php
Normal file
21
app/Models/Faq.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Veelasky\LaravelHashId\Eloquent\HashableId;
|
||||
|
||||
class Faq extends Model
|
||||
{
|
||||
use HashableId, SoftDeletes;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'sort_order' => 'int',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -158,6 +158,13 @@ public function boot(): void
|
||||
'match' => 'studio.manage.article.*',
|
||||
'can' => 'view article',
|
||||
],
|
||||
[
|
||||
'label' => 'FAQs',
|
||||
'icon' => 'question-mark-circle',
|
||||
'route' => 'studio.manage.faq.index',
|
||||
'match' => 'studio.manage.faq.*',
|
||||
'can' => 'view faq',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
|
||||
32
database/migrations/2025_11_06_084400_create_faqs_table.php
Normal file
32
database/migrations/2025_11_06_084400_create_faqs_table.php
Normal 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('faqs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('question', 100);
|
||||
$table->text('answer');
|
||||
$table->unsignedInteger('sort_order');
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('faqs');
|
||||
}
|
||||
};
|
||||
@ -106,14 +106,19 @@ public function run(): void
|
||||
'delete order',
|
||||
'show order',
|
||||
|
||||
'view purchase',
|
||||
'create purchase',
|
||||
'delete purchase',
|
||||
|
||||
'view article',
|
||||
'create article',
|
||||
'update article',
|
||||
'delete article',
|
||||
|
||||
'view purchase',
|
||||
'create purchase',
|
||||
'delete purchase',
|
||||
'view faq',
|
||||
'create faq',
|
||||
'update faq',
|
||||
'delete faq',
|
||||
|
||||
'view application settings',
|
||||
|
||||
|
||||
47
resources/views/livewire/studio/manage/faqs.blade.php
Normal file
47
resources/views/livewire/studio/manage/faqs.blade.php
Normal file
@ -0,0 +1,47 @@
|
||||
<flux:main>
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
|
||||
</div>
|
||||
@can('create faq')
|
||||
<div>
|
||||
<flux:modal.trigger name="form-modal">
|
||||
<flux:button variant="primary" class="text-sm"
|
||||
wire:click="$dispatch('modal:open', {method: 'create', 'modalTitle': 'Tambah FAQs'})">Tambah
|
||||
</flux:button>
|
||||
</flux:modal.trigger>
|
||||
</div>
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<livewire:datatable.studio.manage.faqs-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:field>
|
||||
<flux:label>Pertanyaan <span class="text-red-500 ms-1">*</span></flux:label>
|
||||
<flux:input placeholder="Bagaimana Cara Jadi Member?" wire:model.live.debounce.500ms="form.question"
|
||||
autofocus autocomplete="off" />
|
||||
<flux:error name="form.question" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>Jawaban <span class="text-red-500 ms-1">*</span></flux:label>
|
||||
<flux:editor wire:model="form.answer" placeholder="1. Silakan Anda mendaftar terlebih dahulu."
|
||||
auotocomplete="off" class="**:data-[slot=content]:min-h-[100px]!" />
|
||||
</flux:field>
|
||||
|
||||
<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>
|
||||
@ -26,6 +26,7 @@
|
||||
use App\Livewire\Studio\Manage\Article\Create as ArticleCreate;
|
||||
use App\Livewire\Studio\Manage\Article\Edit as ArticleEdit;
|
||||
use App\Livewire\Studio\Manage\Article\Index as ArticleIndex;
|
||||
use App\Livewire\Studio\Manage\Faq as FaqComponent;
|
||||
use App\Livewire\Studio\Manage\Order\Create as OrderCreate;
|
||||
use App\Livewire\Studio\Manage\Order\Index as OrderIndex;
|
||||
use App\Livewire\Studio\Manage\Order\Show as OrderShow;
|
||||
@ -166,6 +167,11 @@
|
||||
Route::get('/{article}/edit', ArticleEdit::class)->name('edit')->middleware('can:update article');
|
||||
Route::delete('/{article}/delete', ArticleCreate::class)->name('delete')->middleware('can:delete article');
|
||||
});
|
||||
|
||||
Route::prefix('faqs')->name('faq.')->group(function () {
|
||||
Route::get('/', FaqComponent::class)->name('index')->middleware('can:view faq');
|
||||
Route::delete('/{faq}/delete', FaqComponent::class)->name('delete')->middleware('can:delete faq');
|
||||
});
|
||||
});
|
||||
|
||||
Route::get('/settings/applications', ApplicationComponent::class)->name('studio.setting.application')->middleware('can:view application settings');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user