~ We are changing the world with technology. ~

Matt Mullenweg

Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #8: Manage Customers

Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #8: Manage Customers

518 Dilihat

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>

aplikasi laundry laravel

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.

Backend Developer iTechShark, salah satu perusahaan asal Amerika. Senang dengan teknologi baru. Laravel addict yang tidak fanatik. Merekam jejak dengan menulis

Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #7: Push Notification Expenses Laravel VueJS

Aplikasi Laundry (Laravel 5.8 - Vue.js -...

Dalam setiap kegiatan berwirausaha pasti terdapat biaya biaya yang harus dikeluarkan untuk menjalankan operasional dari usaha tersebut, maka dalam aplikasi ini, kita tidak hanya akan mencatat transaks...

Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #6: Role & User Permissions Laravel VueJS

Aplikasi Laundry (Laravel 5.8 - Vue.js -...

Sambil menikmati secangkir kopi ini, kutuliskan serial lanjutan membuat Aplikasi Laundry menggunakan Laravel 5.8 & Vue.js dengan metode SPA (Single Page Application) yang telah memasuki serial ke-6 ny...

Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #5: Manage Laundry Products Laravel VueJS

Aplikasi Laundry (Laravel 5.8 - Vue.js -...

Usaha laundry tak hanya mencakup satu bagian saja yakni laundry kiloan, akan tetapi telah memiliki banyak variant diantaranya, laundry jaket, selimut dan berbagai macam yang bersifat satu. Tidak cukup...

Komentar