Pendahuluan
Pelanggan menjadi bagian yang penting dari sebuah perusahaan laundry, maka fitur yang akan dikerjakan pada serial ini adalah sebuah fitur untuk mengelola pelanggan. Adapun schema yang dirancang dimana fungsi untuk menambahkan pelanggan akan dipisah menjadi dua bagian, pertama pada menu customers dan yang kedua pada saat transaksi dilakukan.
Serial kali ini kita akan menyelesaikan bagian pertama terlebih dahulu. Fitur ini hampir serupa dengan serial sebelumnya karena hanya berkaitan dengan CRUD (Create Read Update Delete) yang akan menambah jam terbang kita dalam berinteraksi dengan manipulasi database untuk menyelesaikan data master, karena semakin sering kita melakukan pengulangan, maka semakin terbiasa kita untuk menyatukan part-part kecil yang mungkin saja membosankan tapi justru dibutuhkan pada sebuah aplikasi.
Baca Juga: Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #7: Push Notification Expenses
Manage Customers
Seperti biasanya yang akan diselesaikan pertama kali adalah fitur untuk menampilkan list customers, buat CustomerController
dengan menjalankan command berikut pada command line:
php artisan make:controller API/CustomerController
Kemudian buka file CustomerController.php
dan tambahkan method berikut:
public function index()
{
//GET CUSTOMER DENGAN MENGURUTKAN DATANYA BERDASARKAN CREATED_AT
$customers = Customer::with(['courier'])->orderBy('created_at', 'DESC');
if (request()->q != '') { //JIKA DATA PENCARIAN ADA
$customers = $customers->where('name', 'LIKE', '%' . request()->q . '%'); //MAKA BUAT FUNGSI FILTERING DATA BERDASARKAN NAME
}
return new CustomerCollection($customers->paginate(10));
}
Jangan lupa tambahkan use statement:
use App\Http\Resources\CustomerCollection;
use App\Customer;
Karena model customer me-load eager loading courier
, maka buka file Customer.php
dan tambahkan method berikut:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
protected $guarded = [];
public function courier()
{
return $this->belongsTo(User::class);
}
}
Selanjutnya buat CustomerCollection
untuk meng-handle formatting data json dengan command:
php artisan make:resource CustomerCollection
Buka file App/Http/Resources/CustomerCollection.php
dan modifikasi menjadi:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class CustomerCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection
];
}
}
Definisikan route untuk meng-handle API yang telah dibuat, buka file routes/api.php
dan tambahkan code:
Route::resource('customer', 'API\CustomerController')->except(['create', 'show']);
Dari sisi backend telah selesai, maka saatnya kita berpindah pada sisi client untuk menampilkan data yang diterima. Pertama buat routing aplikasi dari client side, buka file router.js
yang berada di dalam folder resources/js
dan tambahkan code:
{
path: '/customers',
component: IndexCustomer,
meta: { requiresAuth: true },
children: [
{
path: '',
name: 'customers.data',
component: DataCustomer,
meta: { title: 'Manage Customers' }
},
]
}
Masih dengan file yang sama, tambahkan code berikut pada bagian import statement:
import IndexCustomer from './pages/customers/Index.vue'
import DataCustomer from './pages/customers/Customer.vue'
Ada dua buah file baru yang perlu dibuat untuk memenuhi kebutuhan import dari routing diatas, pertama kita buat file Index.vue
di dalam folder /pages/customers
:
<template>
<div class="container">
<section class="content-header">
<h1>
Manage Customers
</h1>
<breadcrumb></breadcrumb>
</section>
<section class="content">
<div class="row">
<router-view></router-view>
</div>
</section>
</div>
</template>
<script>
import Breadcrumb from '../../components/Breadcrumb.vue'
export default {
name: 'IndexOutlet',
components: {
'breadcrumb': Breadcrumb
}
}
</script>
Note: File diatas hanya berisi tag html biasa sebagai parent untuk me-load child yang dimilikinya. tag <router-view>
nantinya akan berisi tag html dari child yang akan di-render secara otomatis sesuai dengan routing yang sedang dibuka.
File selanjutnya adalah Customer.vue
yang berada di dalam folder yang sama, kemudian tambahkan code:
<template>
<div class="col-md-12">
<div class="panel">
<div class="panel-heading">
<router-link :to="{ name: 'customers.add' }" class="btn btn-primary btn-sm btn-flat">Tambah</router-link>
<div class="pull-right">
<input type="text" class="form-control" placeholder="Cari..." v-model="search">
</div>
</div>
<div class="panel-body">
<!-- TABLE UNTUK MENAMPILKAN LIST CUSTOMER -->
<b-table striped hover bordered :items="customers.data" :fields="fields" show-empty>
<template slot="deposit" slot-scope="row">
Rp {{ row.item.deposit }}
</template>
<!-- NAMA KURIR AKAN DITAMPILKAN JIKA ADA -->
<template slot="courier_id" slot-scope="row">
{{ row.item.courier ? row.item.courier.name:'-' }}
</template>
<template slot="actions" slot-scope="row">
<router-link :to="{ name: 'customers.edit', params: {id: row.item.id} }" class="btn btn-warning btn-sm"><i class="fa fa-pencil"></i></router-link>
<button class="btn btn-danger btn-sm" @click="deleteCustomer(row.item.id)"><i class="fa fa-trash"></i></button>
</template>
</b-table>
<!-- TABLE UNTUK MENAMPILKAN LIST CUSTOMER -->
<div class="row">
<div class="col-md-6">
<p v-if="customers.data"><i class="fa fa-bars"></i> {{ customers.data.length }} item dari {{ customers.meta.total }} total data</p>
</div>
<div class="col-md-6">
<div class="pull-right">
<b-pagination
v-model="page"
:total-rows="customers.meta.total"
:per-page="customers.meta.per_page"
aria-controls="customers"
v-if="customers.data && customers.data.length > 0"
></b-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
export default {
name: 'DataCustomer',
created() {
this.getCustomers() //LOAD DATA PELANGGAN KETIKA COMPONENT DI-LOAD
},
data() {
return {
//FIELD YANG AKAN DITAMPILKAN PADA TABLE DIATAS
fields: [
{ key: 'nik', label: 'NIK' },
{ key: 'name', label: 'Nama Lengkap' },
{ key: 'address', label: 'Alamat' },
{ key: 'phone', label: 'Telp' },
{ key: 'point', label: 'Poin' },
{ key: 'deposit', label: 'Deposit' },
{ key: 'courier_id', label: 'Invited By' },
{ key: 'actions', label: 'Aksi' }
],
search: ''
}
},
computed: {
...mapState('customer', {
customers: state => state.customers //MENGAMBIL DATA CUSTOMER DARI STATE CUSTOMER
}),
//MENGAMBIL DATA PAGE DARI STATE CUSTOMER
page: {
get() {
return this.$store.state.customer.page
},
set(val) {
this.$store.commit('customer/SET_PAGE', val)
}
}
},
watch: {
page() {
this.getCustomers() //KETIKA VALUE PAGE TERJADI PERUBAHAN, MAKA REQUEST DATA BARU
},
search() {
this.getCustomers(this.search) //KETIKA VALUE SEARCH TERJADI PERUBAHAN, MAKA REQUEST DATA BARU
}
},
methods: {
...mapActions('customer', ['getCustomers', 'removeCustomer']),
//KETIKA TOMBOL HAPUS DITEKAN MAKA FUNGSI INI AKAN DIJALANKAN
deleteCustomer(id) {
this.$swal({
title: 'Kamu Yakin?',
text: "Tindakan ini akan menghapus secara permanent!",
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Iya, Lanjutkan!'
}).then((result) => {
if (result.value) {
this.removeCustomer(id) //JIKA SETUJU MAKA PERMINTAAN HAPUS AKAN DI EKSEKUSI
}
})
}
}
}
</script>
Bagian terpenting yang harus dibuat adalah module Vuex, buat file customer.js
di dalam folder stores
dan tambahkan code:
import $axios from '../api.js'
const state = () => ({
customers: [], //STATE UNTUK MENAMPUNG DATA CUSTOMERS
//STATE INI UNTUK FORM ADD DAN EDIT NANTINYA
customer: {
nik: '',
name: '',
address: '',
phone: ''
},
page: 1
})
const mutations = {
//MUTATIONS UNTUK ASSIGN DATA CUSTOMER KE DALAM STATE CUSTOMER
ASSIGN_DATA(state, payload) {
state.customers = payload
},
//MENGUBAH STATE PAGE
SET_PAGE(state, payload) {
state.page = payload
},
//MENGUBAH STATE CUSTOMER
ASSIGN_FORM(state, payload) {
state.customer = payload
},
//RESET STATE CUSTOMER
CLEAR_FORM(state) {
state.customer = {
nik: '',
name: '',
address: '',
phone: ''
}
}
}
const actions = {
getCustomers({ commit, state }, payload) {
let search = typeof payload != 'undefined' ? payload:''
return new Promise((resolve, reject) => {
//REQUEST DATA CUSTOMER DENGAN MENGIRIMKAN PARAMETER PAGE YG SEDANG AKTIF DAN VALUE PENCARIAN
$axios.get(`/customer?page=${state.page}&q=${search}`)
.then((response) => {
commit('ASSIGN_DATA', response.data) //JIKA DATA DITERIMA, SIMPAN DATA KEDALMA MUTATIONS
resolve(response.data)
})
})
}
}
export default {
namespaced: true,
state,
actions,
mutations
}
Agar module diatas dapat digunakan, register terlebih dahulu dengan menambahkan code berikut di dalam file store.js
modules: {
auth,
outlet,
courier,
product,
user,
expenses,
notification,
customer //TAMBAHKAN BAGIAN INI
},
Jangan lupa pada bagian import statement tambahkan code:
import customer from './stores/customer.js'
Terakhir, agar halaman management customer dapat diakses melalui menu navigasi, buka file resources/js/components/Header.vue
dan tambahkan tag berikut pada bagian list menu:
<li><router-link :to="{ name: 'customers.data' }">Customer</router-link></li>
Add New Customer
Fitur ini akan digunakan oleh admin dan kurir, adapun schema yang diinginkan adalah ketika kurir yang menambahkan customer maka informasi kurir tersebut akan disimpan kedalam data customer, sedangkan jika admin yang menambahkan maka informasinya tidak akan disimpan atau relasinya dibiarkan null. Gunanya apa? Karena kurir bekerja dilapangan maka dia punya potensi untuk mengajak pelanggan, sedangkan admin hanya bekerja digerai sehingga customer yang datang ketempatnya. Informasi kurir yang ditambahkan pada data customer akan digunakan untuk mencatat bonus yang akan diberikan pada kurir.
Bagian pertama yang akan diselesaikan adalah form untuk meng-input data customer, buka file router.js
dan tambahkan code berikut di dalam children dari customers.
{
path: 'add',
name: 'customers.add',
component: AddCustomer,
meta: { title: 'Add New Customers' }
},
Masih dengan file yang sama, tambahkan line dibawah pada bagian import statement:
import AddCustomer from './pages/customers/Add.vue'
Buat file Add.vue
di dalam folder /pages/customers
dan tambahkan code:
<template>
<div class="col-md-12">
<div class="panel">
<div class="panel-heading">
<h3 class="panel-title">Add New Customer</h3>
</div>
<div class="panel-body">
<!-- LOAD VIEW DARI FORM.VUE -->
<customer-form></customer-form>
<div class="form-group">
<button class="btn btn-primary btn-sm btn-flat" @click.prevent="submit">
<i class="fa fa-save"></i> Add New
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState, mapMutations } from 'vuex'
import FormCustomer from './Form.vue'
export default {
name: 'AddCustomer',
data() {
return {
}
},
methods: {
...mapActions('customer', ['submitCustomer']),
//KETIKA TOMBOL DITEKAN MAKA FUNGSI INI AKAN DIJALANKAN
submit() {
//MELAKUKAN REQUEST KE SERVER UNTUK MENAMBAHKAN DATA
this.submitCustomer().then(() => {
//KEMUDIAN REDIRECT KE HALAMAN LIST CUSTOMERS
this.$router.push({ name: 'customers.data' })
})
}
},
components: {
'customer-form': FormCustomer //MEMBUAT CUSTOM TAG UNTUK ME-LOAD FILE FORM.VUE
}
}
</script>
Karena form input-nya akan dibuat reusable, maka file bagian tersebut di pisahkan ke dalam file tersendiri. Buat file Form.vue
di dalam folder yang sama dan tambahkan code:
<template>
<div>
<div class="form-group" :class="{ 'has-error': errors.nik }">
<label for="">NIK</label>
<input type="text" class="form-control" v-model="customer.nik" :readonly="$route.name == 'customers.edit'">
<p class="text-danger" v-if="errors.nik">{{ errors.nik[0] }}</p>
</div>
<div class="form-group" :class="{ 'has-error': errors.name }">
<label for="">Nama Lengkap</label>
<input type="text" class="form-control" v-model="customer.name">
<p class="text-danger" v-if="errors.name">{{ errors.name[0] }}</p>
</div>
<div class="form-group" :class="{ 'has-error': errors.address }">
<label for="">Alamat</label>
<textarea cols="5" rows="5" class="form-control" v-model="customer.address"></textarea>
<p class="text-danger" v-if="errors.address">{{ errors.address[0] }}</p>
</div>
<div class="form-group" :class="{ 'has-error': errors.phone }">
<label for="">No Telp</label>
<input type="text" class="form-control" v-model="customer.phone">
<p class="text-danger" v-if="errors.phone">{{ errors.phone[0] }}</p>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
name: 'FormCustomer',
computed: {
...mapState(['errors']), //LOAD STATE ERROR UNTUK DITAMPILKAN KETIKA TERJADI ERROR VALIDASI
...mapState('customer', {
customer: state => state.customer //LOAD DATA CUSTOMER DARI STATE CUSTOMER
})
},
methods: {
...mapMutations('customer', ['CLEAR_FORM']),
},
destroyed() {
this.CLEAR_FORM() //KETIKA COMPONENT DITINGGALKAN, BERSIHKAN DATA
}
}
</script>
Sebagai penutup dari client side, maka kita perlu membuat actions pada module Vuex untuk mengirimkan request ke backend. Buka file stores/customer.js
dan tambahkan code berikut pada bagian actions.
submitCustomer({ dispatch, commit, state }) {
return new Promise((resolve, reject) => {
//MENGIRIMKAN REQUEST KE BACKEND DENGAN DATA YANG DIDAPATKAN DARI STATE CUSTOMER
$axios.post(`/customer`, state.customer)
.then((response) => {
//APABILA BERHASIL MAKA LOAD DATA CUSTOMER UNTUK MENGAMBIL DATA TERBARU
dispatch('getCustomers').then(() => {
resolve(response.data)
})
})
.catch((error) => {
//JIKA TERJADI ERROR VALIDASI, ASSIGN ERROR TERSEBUT KE DALAM STATE ERRORS
if (error.response.status == 422) {
commit('SET_ERRORS', error.response.data.errors, { root: true })
}
})
})
},
Dari sisi client side sudah selesai, maka kita akan berpindah pada sisi backend untuk meng-handle request menyimpan data. Buka file CustomerController.php
dan tambahkan method:
public function store(Request $request)
{
//BUAT VALIDASI DATA
$this->validate($request, [
'nik' => 'required|string|unique:customers,nik',
'name' => 'required|string|max:150',
'address' => 'required|string',
'phone' => 'required|string|max:15'
]);
$user = $request->user(); //MENGAMBIL USER YANG SEDANG LOGIN
//DEFAULTNYA POINT DAN DEPOSIT 0
$request->request->add([
'point' => 0,
'deposit' => 0
]);
//JIKA DI INPUT OLEH KURIR
if ($user->role == 3) {
//MAKA ID KURIR DITAMBAHKAN
$request->request->add(['courier_id' => $user->id]);
}
Customer::create($request->all());
return response()->json(['status' => 'success']);
}
Pada schema database sebelumnya, field courier_id
wajib diisi akan tetapi ketika aplikasi sedang di-develop terjadi perubahan dimana data tersebut menjadi tidak wajib. Generate migrations baru dengan command:
php artisan make:migration change_field_courier_id_on_customers_table
Buka file migration yang telah di-generate kemudian modifikasi menjadi:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class ChangeFieldCourierIdOnCustomersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('customers', function(Blueprint $table) {
$table->unsignedBigInteger('courier_id')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
Agar fungsi change()
dapat digunakan, install package doctrine/dbal
dengan command:
composer require doctrine/dbal
Kemudian eksekusi migration diatas dengan command:
php artisan migrate
Edit Data Customer
Perubahan data kerap terjadi ketika aplikasi digunakan, mungkin saja karena kesalahan input atau memang data tersebut perlu pembaharuan, maka pada fitur kali ini kita akan membuat fungsi untuk mengubah data tersebut.
Pertama, definisikan routing dari sisi client side, buka file router.js
dan tambahkan code berikut di dalam children route customers:
{
path: 'edit/:id',
name: 'customers.edit',
component: EditCustomer,
meta: { title: 'Edit Customer' }
},
Masih dengan file yang sama, tambahkan line berikut pada bagian import statement:
import EditCustomer from './pages/customers/Edit.vue'
Kemudian buat file Edit.vue
di dalam folder /pages/customers
<template>
<div class="col-md-12">
<div class="panel">
<div class="panel-heading">
<h3 class="panel-title">Edit Customer</h3>
</div>
<div class="panel-body">
<customer-form></customer-form>
<div class="form-group">
<button class="btn btn-primary btn-sm btn-flat" @click.prevent="submit">
<i class="fa fa-save"></i> Update
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
import FormCustomer from './Form.vue'
export default {
name: 'EditCustomer',
created() {
this.editCustomer(this.$route.params.id) //LOAD SINGLE DATA CUSTOMER BERDASARKAN ID
},
methods: {
...mapActions('customer', ['editCustomer', 'updateCustomer']),
submit() {
//MENGIRIM PERMINTAAN KE SERVER UNTUK ME-NGUBAH DATA
this.updateCustomer(this.$route.params.id).then(() => {
this.$router.push({ name: 'customers.data' }) //KETIKA UPDATE BERHASIL, MAKA REDIRECT KE HALAMAN LIST CUSTOMER
})
}
},
components: {
'customer-form': FormCustomer
},
}
</script>
Note: Code diatas hampir serupa dengan file Add.vue
, yang membedakan hanyalah pada hooks create()
karena terdapat fungsi untuk me-load single data berdasarkan id yang sedang dibuka. Selebihnya sama saja karena menggunakan file Form.vue
dimana fungsinya sudah dikerjakan sebelumnya.
Tugas selanjutnya adalah membuat actions yang akan meng-handle proses request ke backend. Buka file customer.js
dan tambahkan kedua method berikut pada bagian actions.
editCustomer({ commit }, payload) {
return new Promise((resolve, reject) => {
$axios.get(`/customer/${payload}/edit`) //KIRIM PERMINTAAN KE SERVER UNTUK MENGAMBIL SINGLE DATA CUSTOMER BERDASARKAN PAYLOAD (ID)
.then((response) => {
commit('ASSIGN_FORM', response.data.data) //ASSIGN DATA TERSEBUT KE DALAM STATE CUSTOMER
resolve(response.data)
})
})
},
updateCustomer({ state, commit }, payload) {
return new Promise((resolve, reject) => {
$axios.put(`/customer/${payload}`, state.customer) //KIRIM PERMINTAAN KE SERVER UNTUK MENGUPDATE DATA BERDASARKAN PAYLOAD (ID) DAN DATA YANG AKAN DI UPDATE DI AMBIL DARI STATE CUSTOMER
.then((response) => {
commit('CLEAR_FORM') //BERSIHKAN FORM
resolve(response.data)
})
})
},
Langkah terakhir adalah menyediakan API untuk mengirimkan data dan meng-update data, buka file CustomerController.php
dan tambahkan method:
public function edit($id)
{
$customer = Customer::find($id); //MELAKUKAN QUERY UNTUK MENGAMBIL DATA BERDASARKAN ID
return response()->json(['status' => 'success', 'data' => $customer]);
}
public function update(Request $request, $id)
{
//VALIDASI DATA YANG DITERIMA
$this->validate($request, [
'name' => 'required|string|max:150',
'address' => 'required|string',
'phone' => 'required|string|max:15'
]);
$customer = Customer::find($id); //QUERY UNTUK MENGAMBIL DATA BERDASARKAN ID
$customer->update($request->all()); //UPDATE DATA BERDASARKAN DATA YANG DITERIMA
return response()->json(['status' => 'success']);
}
Delete Data Customer
Seperti biasanya, tugas kita pada bagian ini akan menjadi lebih ringan karena sudah di-handle pada fitur sebelumnya. Jadi yang akan kita kerjakan hanya dua bagian, pertama buat API dengan menambahkan method berikut ke dalam CustomerController.php
public function destroy($id)
{
$customer = Customer::find($id); //QUERY DATA BERDASARKAN ID
$customer->delete(); //KEMUDIAN HAPUS DATA TERSEBUT
return response()->json(['status' => 'success']);
}
Kemudian tugas selanjutnya adalah membuat actions untuk mengirim permintaan ke backend. Buka file store.js
dan tambahkan code berikut pada bagian actions.
removeCustomer({ dispatch }, payload) {
return new Promise((resolve, reject) => {
$axios.delete(`/customer/${payload}`) //KIRIM REQUEST KE SERVER BERDASARKAN PAYLOAD (ID)
.then((response) => {
dispatch('getCustomers').then(() => resolve()) //AMBIL DATA CUSTOMER TERBARU
})
})
}
Baca Juga: Belajar Flutter Basic #6: Stateless & Stateful Widget
Kesimpulan
Management customer bagian pertama telah selesai dimana pada fitur admin maupun kurir dapat menambahkan data customer sebelum nantinya digunakan pada saat meng-input transaksi. Maka pada artikel selanjutnya kita akan memasuki fitur transaksi dimana salah satu bagian yang akan dibuat adalah input customer juga tapi secara langsung pada menu transaksi.
Tujuannya adalah agar user tidak bolak balik antara menu customer dan menu transaksi, sehingga disediakan fitur tersebut yang mampu menambahkan data customer langsung pada halaman transaksi.
Adapun dokumentasi code dari artikel ini dapat kamu lihat di Github.
Comments