Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #4: Management Couriers

Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #4: Management Couriers

Pendahuluan

Dewasa ini perkembangan usaha laundry terbilang cukup pesat karena telah memiliki layanan antar jemput setiap pesanan yang dilakukan oleh customer-nya. Hal ini tentu saja dalam rangka memenuhi kebutuhan setiap customer yang tidak lagi punya kesempatan atau hanya sekedar malas untuk membawa pesanannya sendiri.

Dengan adanya layanan ini, tentu saja terjadi pertambahan karyawan untuk meng-handle kegiatan tersebut, maka dibutuhkan sebuah fitur untuk mencatat dan mengelola karyawan yang tugasnya khusus pada bidang pelayanan antar jemput pesanan. Pada artikel kali ini kita akan membahas bagaimana cara membuat fitur Management Couriers pada Aplikasi Laundry menggunakan Laravel 5.8 & Vue.js dengan konsep SPA (Single Page Application).

Baca Juga: Rekomendasi Package Laravel Yang Dapat Mempermudah Pekerjaan Kamu

Kelola Data Kurir

Pada bagian ini kita akan membuat sebuah fitur untuk menampilkan seluruh data kurir yang ada, namun sebelum melanjutkan, ada yang kurang dari serial sebelumnya yakni fungsi untuk mengharuskan login ketika mengakses halaman /outlets. Buka file resources/js/router.js dan tambahkan code:

{
    path: '/outlets',
    component: IndexOutlet,
    meta: { requiresAuth: true }, //CUKUP TAMBAHKAN CODE INI
    children: [
        //CODE CHILDRENNYA
    ]
}, 

Kembali ke topik pembahasan, langkah pertama adalah membuat API untuk mengambil data kurir. Pada command line, jalankan command:

php artisan make:controller API/UserController

Kemudian buka file UserController.php dan tambahkan method berikut:

public function index()
{
    $users = User::with(['outlet'])->orderBy('created_at', 'DESC')->courier();
    if (request()->q != '') {
        $users = $users->where('name', 'LIKE', '%' . request()->q . '%');
    }
    $users = $users->paginate(10);
    return new UserCollection($users);
}

Penjelasan: Model user me-load data outlet yang terkait menggunakan eager loading with() dan mengurutkannnya berdasarkan created_at. courier() adalah sebuah local scope untuk men-filter data user yang role-nya adalah 3.

Jangan lupa untuk menambahkan use statement pada file yang sama:

use App\User;

Ada 3 hal yang perlu kita penuhi dari method index() diatas, pertama adalah fungsi relasi antar model User dan Outlet. Buka file User.php dan tambahkan method:

public function outlet()
{
    return $this->belongsTo(Outlet::class);
}

Fungsi selanjutnya adalah membuat local scope, masih pada file model User.php, tambahkan method:

public function scopeCourier($query)
{
    return $query->where('role', 3);
}

Penjelasan: Fungsi ini untuk mengambil data dimana role = 3. Karena role 3 adalah user dengan tipe kurir.

Langkah terakhir adalah membuat file UserCollection, pada command line, jalankan perintah:

php artisan make:resource UserCollection

Kemudian buka file App\Http\Resources\UserCollection dan modifikasi menjadi:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'data' => $this->collection
        ];
    }
}

Kembali ke UserController.php, tambahkan use statement:

use App\Http\Resources\UserCollection;

Tugas selanjutnya adalah membuat routing untuk kurir, buka file routes/api.php dan tambahkan code berikut di dalam route group yang menggunakan middleware auth:api :

Route::resource('/couriers', 'API\UserController')->except(['create', 'show', 'update']);
Route::post('/couriers/{id}', 'API\UserController@update')->name('couriers.update');

Berpindah ke Vue.js, dimana kita akan membuat fungsi untuk menampilkan data yang telah didapatkan dari API. Pertama, buat file resources/js/stores/courier.js dan definisikan beberapa bagian berikut:

import $axios from '../api.js'

const state = () => ({
    couriers: [], //UNTUK MENAMPUNG DATA KURIR
    page: 1, //PAGE AKTIF
    id: '' //NANTI AKAN DIGUNAKAN UNTUK EDIT DATA
})

const mutations = {
    //MEMASUKKAN DATA YANG DITERIMA KE DALAM STATE KURIR
    ASSIGN_DATA(state, payload) {
        state.couriers = payload
    },
    //MENGUBAH STATE PAGE
    SET_PAGE(state, payload) {
        state.page = payload
    },
    //MENGUBAH STATE ID
    SET_ID_UPDATE(state, payload) {
        state.id = payload
    }
}

const actions = {
    //FUNGSI INI AKAN MELAKUKAN REQUEST KE SERVER UNTUK MENGAMBILD ATA
    getCouriers({ commit, state }, payload) {
        let search = typeof payload != 'undefined' ? payload:''
        return new Promise((resolve, reject) => {
            //DENGAN MENGGUNAKAN AXIOS METHOD GET
            $axios.get(`/couriers?page=${state.page}&q=${search}`)
            .then((response) => {
                //KEMUDIAN DI COMMIT UNTUK MELAKUKAN PERUBAHA STATE
                commit('ASSIGN_DATA', response.data)
                resolve(response.data)
            })
        })
    }
}

export default {
    namespaced: true,
    state,
    actions,
    mutations
}

Agar file di atas dapat digunakan, buka file resources/js/store.js dan import file di atas:

import Vue from 'vue'
import Vuex from 'vuex'

import auth from './stores/auth.js'
import outlet from './stores/outlet.js'
import courier from './stores/courier.js' //IMPORT FILE BARU

Vue.use(Vuex)

const store = new Vuex.Store({
    modules: {
        auth,
        outlet,
        courier //DEFINISIKAN
    },  
    //[.. CODE LAINNYA ..]
})
export default store

Selanjutnya adalah membuat sebuah file pages untuk meng-handle data yang telah diterima, buat file resources/js/pages/couriers/Index.vue dan tambahkan code:

<template>
    <div class="container">
        <section class="content-header">
            <h1>
                Manage Couriers
            </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: 'IndexCourier',
        components: {
            'breadcrumb': Breadcrumb
        }
    }
</script>

Note: Code diatas fungsinya serupa dengan serial sebelumnya pada saat membuat management outlets.

Buat routing pada Vue.js, buat file resources/js/router.js dan tambahkan code:

//[.. CODE SEBELUMNYA ..]

//IMPORT BAGIAN BERIKUT
import IndexCourier from './pages/couriers/Index.vue'
import DataCouriers from './pages/couriers/Courier.vue'

Vue.use(Router)

const router = new Router({
    mode: 'history',
    routes: [
        //[.. CODE SEBELUMNYA ..]
        {
            path: '/couriers',
            component: IndexCourier,
            meta: { requiresAuth: true },
            children: [
                {
                    path: '',
                    name: 'couriers.data',
                    component: DataCourier,
                    meta: { title: 'Manage Couriers' }
                },
                //NANTINYA AKAN BERISI CHILDREN LAINNYA DARI PAGE COURIERS
            ]
        }
    ]
});

//[.. CODE SETELAHNYA ..]

Terdapat file Courier.vue yang kita import pada code di atas, buat tersebut di dalam folder /pages/couriers dan masukkan potongan code berikut yang berfungsi untuk menampilkan list data:

<template>
    <div class="col-md-12">
        <div class="panel">
            <div class="panel-heading">
                <router-link :to="{ name: 'couriers.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">
              	
              	<!-- BAGIAN INI AKAN MENAMPILKAN DATA LIST KURIR DALAM BENTUK TABLE -->
                <b-table striped hover bordered :items="couriers.data" :fields="fields" show-empty>
                  
                  <!-- KITA MENGGUNAKAN CUSTOM TEMPLATE KARENA AKAN MENAMPILKAN GAMBAR -->
                    <template slot="photo" slot-scope="row">
                        <img :src="'/storage/couriers/' + row.item.photo" :width="80" :height="50" :alt="row.item.name">
                    </template>
                    <template slot="outlet_id" slot-scope="row">
                        {{ row.item.outlet.name }}
                    </template>
                    <template slot="actions" slot-scope="row">
                        <router-link :to="{ name: 'couriers.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="deleteCourier(row.item.id)"><i class="fa fa-trash"></i></button>
                    </template>
                </b-table>
								
              	<!-- BERISI INFORMASI JUMLAH DATA DAN PAGINATION -->
                <div class="row">
                    <div class="col-md-6">
                        <p v-if="couriers.data"><i class="fa fa-bars"></i> {{ couriers.data.length }} item dari {{ couriers.meta.total }} total data</p>
                    </div>
                    <div class="col-md-6">
                        <div class="pull-right">
                            <b-pagination
                                v-model="page"
                                :total-rows="couriers.meta.total"
                                :per-page="couriers.meta.per_page"
                                aria-controls="couriers"
                                v-if="couriers.data && couriers.data.length > 0"
                                ></b-pagination>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import { mapActions, mapState } from 'vuex'

export default {
    name: 'DataCourier',
    created() {
        this.getCouriers() //KETIKA PAGE DI-LOAD, FUNGSI UNTUK MENGAMBIL DATA DIJALANKAN
    },
    data() {
        return {
            //FIELD TABLE YANG AKAN DITAMPILKAND DATANYA
            fields: [
                { key: 'photo', label: '#' },
                { key: 'name', label: 'Nama Lengkap' },
                { key: 'email', label: 'Email' },
                { key: 'outlet_id', label: 'Outlet' },
                { key: 'actions', label: 'Aksi' }
            ],
            search: ''
        }
    },
    computed: {
        ...mapState('courier', {
            couriers: state => state.couriers //STATE YANG MENYIMPAN DATA KURIR
        }),
        //STATE PAGE UNTUK MENGAMBIL DAN MENGUBAH DATA STATE
        page: {
            get() {
                return this.$store.state.courier.page
            },
            set(val) {
                this.$store.commit('courier/SET_PAGE', val)
            }
        }
    },
    watch: {
        //KETIKA TERJADI PERUBAHAN VALUE DARI PAGE
        page() {
            this.getCouriers() //MAKA FUNGSI INI DIJALANKAN
        },
        //KETIKA TERJADI PERUBAHAN VALUE DARI SEARCH BOX
        search() {
            this.getCouriers(this.search) //FUNGSI INI DIJALANKAN
        }
    },
    methods: {
        ...mapActions('courier', ['getCouriers', 'removeCourier']), //MEMANGGIL FUNGSI YANG ADA DI STORE COURIER
        
        //FUNGSI DELETE YANG AKAN DIBAHAS NANTINYA
        deleteCourier(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.removeCourier(id)
                }
            })
        }
    }
}
</script>

Terakhir tambahkan navigasi berikut pada code list navigasi agar dapat menampilkan menu untuk mengakses halaman management couriers. Buka file resources/js/components/Header.vue dan tambahkan code:

<li><router-link :to="{ name: 'couriers.data' }">Courier</router-link></li>

Adapun hasil yang akan kita peroleh akan tampak seperti gambar dibawah ini

Fitur Tambah Data

Fitur selanjutnya adalah sebuah form dimana user dapat menambahkan data kurir yang baru, untuk membuatnya, langkah pertama yang akan kita lakukan adalah membuat routing untuk halaman tersebut. Buka file router.js dan tambahkan code berikut pada children dari couriers.

{
      path: 'add',
      name: 'couriers.add',
      component: AddCouriers,
      meta: { title: 'Add New Courier' }
  },

Jangan lupa import file-nya dengan menambahkan:

import AddCouriers from './pages/couriers/Add.vue'

Tugas selanjutnya adalah membuat file Add.vue di dalam folder /pages/couriers dan tambahkan code:

<template>
    <div class="col-md-12">
        <div class="panel">
            <div class="panel-heading">
                <h3 class="panel-title">Add New Courier</h3>
            </div>
            <div class="panel-body">
                <courier-form ref="formCourier"></courier-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 FormCourier from './Form.vue'
    export default {
        name: 'AddCourier',
        methods: {
            submit() {
                this.$refs.formCourier.submit()
            }
        },
        components: {
            'courier-form': FormCourier
        }
    }
</script>

Penjelasan: Hanya berisi sebuah form, dimana ketika tombol Add New di tekan maka akan menjalankan fungsi submit(), pada fungsi tersebut berisi sebuah code yang akan menjalankan fungsi submit() lagi pada file Form.vue menggunakan $refs. Catatan yang perlu diketahui adalah ketika menggunakan fungsi $refs, maka kita perlu menambahkan attribute ref pada custom tag-nya menjadi <courier-form ref="formCourier"></courier-form>

Buat file Form.vue di dalam folder /pages/couriers dan masukkan potongan code:

<template>
    <div>
        <div class="form-group" :class="{ 'has-error': errors.name }">
            <label for="">Nama Lengkap</label>
            <input type="text" class="form-control" v-model="courier.name" :readonly="$route.name == 'outlets.edit'">
            <p class="text-danger" v-if="errors.name">{{ errors.name[0] }}</p>
        </div>
        <div class="form-group" :class="{ 'has-error': errors.email }">
            <label for="">Email</label>
            <input type="text" class="form-control" v-model="courier.email">
            <p class="text-danger" v-if="errors.email">{{ errors.email[0] }}</p>
        </div>
        <div class="form-group" :class="{ 'has-error': errors.password }">
            <label for="">Password</label>
            <input type="password" class="form-control" v-model="courier.password">
            <p class="text-warning">Leave blank if you don't want to change password</p>
            <p class="text-danger" v-if="errors.password">{{ errors.password[0] }}</p>
        </div>
        <div class="form-group" :class="{ 'has-error': errors.outlet_id }">
            <label for="">Outlet</label>
            <select name="outlet_id" class="form-control" v-model="courier.outlet_id">
                <option value="">Pilih</option>
                <option v-for="(row, index) in outlets.data" :value="row.id" :key="index">{{ row.name }}</option>
            </select>
            <p class="text-danger" v-if="errors.outlet_id">{{ errors.outlet_id[0] }}</p>
        </div>
        <div class="form-group" :class="{ 'has-error': errors.photo }">
            <label for="">Foto</label>
            <input type="file" class="form-control" accept="image/*" @change="uploadImage($event)" id="file-input">
            <p class="text-warning">Leave blank if you don't want to change photo</p>
            <p class="text-danger" v-if="errors.photo">{{ errors.photo[0] }}</p>
        </div>
    </div>
</template>

<script>
import { mapState, mapMutations, mapActions } from 'vuex'
export default {
    name: 'FormCourier',
    created() {
        this.getOutlets() //KETIKA HALAMAN DI-LOAD, FUNGSI UNTUK MENGAMBIL DATA OUTLETS DIJALANKAN
      
        //KETIKA PAGE YANG SEDANG BERJALAN ADALAH PAGE EDIT
        if (this.$route.name == 'couriers.edit') {
            //MAKA FUNGSI UNTUK MENGAMBIL DATA YANG AKAN DIEDIT DIJALANKAN BERDASARKAN PARAMETER ID YANG ADA DI URL
            this.editCourier(this.$route.params.id).then((res) => {
                //RESPON YANG DITERIMA AKAN DIMASUKKAN KEDALAM ATTRIBTUE KURIR 
                this.courier = {
                    name: res.data.name,
                    email: res.data.email,
                    password: '',
                    photo: '',
                    outlet_id: res.data.outlet_id
                }
            })
        }
    },
    data() {
        return {
            courier: {
                name: '',
                email: '',
                password: '',
                photo: '',
                outlet_id: ''
            }
        }
    },
    computed: {
        ...mapState(['errors']),
        ...mapState('outlet', {
            outlets: state => state.outlets //MENGAMBIL DATA OUTLETS
        })
    },
    methods: {
        ...mapActions('outlet', ['getOutlets']), //MENDEFINISIKAN FUNGSI getOutlets
        ...mapActions('courier', ['submitCourier', 'editCourier', 'updateCourier']), //MENDEFINISIKAN FUNGSI submitCourier, editCourier, dan updateCourier
        ...mapMutations('courier', ['SET_ID_UPDATE']), //MEMANGGIL MUTATIONS
        //KETIKA TERJADI PENGINPUTAN GAMBAR, MAKA FILE TERSEBUT AKAN DI ASSIGN KE DALAM courier.photo
        uploadImage(event) {
            this.courier.photo = event.target.files[0]
        },
        //KETIKA TOMBOL ADD NEW DITEKAN MAKA AKAN MENJALAN FUNGSI DIBAWAH
        submit() {
            //DIMANA UNTUK MENGUPLOAD GAMBAR HARUS MENGGUNAKAN FORMDATA
            let form = new FormData()
            form.append('name', this.courier.name)
            form.append('email', this.courier.email)
            form.append('password', this.courier.password)
            form.append('outlet_id', this.courier.outlet_id)
            form.append('photo', this.courier.photo)

            //KETIKA HALAMAN ADD KURIR YANG DI AKSES
            if (this.$route.name == 'couriers.add') {
                //MAKA AKAN MENJALANKAN FUNGSI submitCourier
                this.submitCourier(form).then(() => {
                    //KEMUDIAN FORM DI KOSONGKAN
                    this.courier = {
                        name: '',
                        email: '',
                        password: '',
                        photo: '',
                        outlet_id: ''
                    }
                    //DI DIRECT KE HALAMAN LIST DATA KURIR
                    this.$router.push({ name: 'couriers.data' })
                })
            //JIKA YANG DIAKSES HALAMAN EDIT KURIR
            } else if (this.$route.name == 'couriers.edit') {
                //MAKA ID NYA DI ASSING KE STATE ID
                this.SET_ID_UPDATE(this.$route.params.id)
                //DAN FUNGSI updateCourier DIJALANKAN
                this.updateCourier(form).then(() => {
                    //KEMUDIAN FORM DI KOSONGKAN
                    this.courier = {
                        name: '',
                        email: '',
                        password: '',
                        photo: '',
                        outlet_id: ''
                    }
                    //DI DIRECT KE HALAMAN LIST DATA KURIR
                    this.$router.push({ name: 'couriers.data' })
                })
            }
        }
    }
}
</script>

Note: Fungsi edit dan update yang ada diatas saya masukkan sekaligus, karena file Form.vue digunakan oleh dua buah halaman yakni Add dan Edit.

Buka file /stores/courier.js, pada bagian actions tambahkan fungsi submitCourier():

submitCourier({ dispatch, commit }, payload) {
    return new Promise((resolve, reject) => {
        //MENGIRIMKAN PERMINTAAN KE SERVER DENGAN METHOD POST
        $axios.post(`/couriers`, payload, {
            //KARENA TERDAPAT FILE FOTO, MAKA HEADERNYA DITAMBAHKAN multipart/form-data
            headers: {
                'Content-Type': 'multipart/form-data'
            }
        })
        .then((response) => {
            //KETIKA BERHASIL, MAKA DILAKUKAN REQUEST UNTUK MENGAMBIL DATA KURIR TERBARU
            dispatch('getCouriers').then(() => {
                resolve(response.data)
            })
        })
        .catch((error) => {
            //JIKA GAGALNYA VALIDASI MAKA ERRONYA AKAN DI ASSIGN
            if (error.response.status == 422) {
                commit('SET_ERRORS', error.response.data.errors, { root: true })
            }
        })
    })
},

Terakhir, pada bagian API, buka file UserController.php dan tambahkan method store()

public function store(Request $request)
{
    //VALIDASI
    $this->validate($request, [
        'name' => 'required|string|max:150',
        'email' => 'required|email|unique:users,email',
        'password' => 'required|min:6|string',
        'outlet_id' => 'required|exists:outlets,id',
        'photo' => 'required|image'
    ]);

    DB::beginTransaction();
    try {
        $name = NULL;
        //APABILA ADA FILE YANG DIKIRIMKAN
        if ($request->hasFile('photo')) {
            //MAKA FILE TERSEBUT AKAN DISIMPAN KE STORAGE/APP/PUBLIC/COURIERS
            $file = $request->file('photo');
            $name = $request->email . '-' . time() . '.' . $file->getClientOriginalExtension();
            $file->storeAs('public/couriers', $name);
        }
        //BUAT DATA BARUNYA KE DATABASE
        User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => $request->password,
            'role' => $request->role,
            'photo' => $name,
            'outlet_id' => $request->outlet_id,
            'role' => 3
        ]);
        DB::commit();
        return response()->json(['status' => 'success'], 200);
    } catch (\Exception $e) {
        DB::rollback();
        return response()->json(['status' => 'error', 'data' => $e->getMessage()], 200);
    }
}

Jangan lupa untuk menambahkan use statement berikut:

use DB;

Adapun form yang akan terlihat kurang lebih seperti gambar di bawah ini

Fitur Edit Data Kurir

Pekerjaan kita pada fitur edit data kurir menjadi lebih mudah, karena fungsi dan form-nya sudah dibuat sebelumnya, sehingga kita hanya perlu membuat routing dan page-nya saja. Buka file router.js dan tambahkan potongan code ini pada bagian children couriers.

{
    path: 'edit/:id',
    name: 'couriers.edit',
    component: EditCouriers,
    meta: { title: 'Edit Courier' }
},

Jangan lupa untuk meng-import file-nya:

import EditCouriers from './pages/couriers/Edit.vue'

Buat file Edit.vue di dalam folder /pages/couriers dan tambahkan code dibawah ini:

<template>
    <div class="col-md-12">
        <div class="panel">
            <div class="panel-heading">
                <h3 class="panel-title">Edit Courier</h3>
            </div>
            <div class="panel-body">
                <courier-form ref="courierForm"></courier-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 FormCourier from './Form.vue'
    export default {
        name: 'EditCourier',
        methods: {
            submit() {
                this.$refs.courierForm.submit()
            }
        },
        components: {
            'courier-form': FormCourier
        },
    }
</script>

Note: Code diatas serupa dengan fungsi dari code yang ada di file Add.vue

Kemudian buka file /stores/courier.js dan tambahkan kedua fungsi berikut pada bagian actions:

editCourier({ commit }, payload) {
    return new Promise((resolve, reject) => {
        //FUNGSI UNTUK MELAKUKAN REQUEST SINGLE DATA BERDASARKAN ID KURIR
        $axios.get(`/couriers/${payload}/edit`)
        .then((response) => {
            //DATA YANG DITERIMA AKAN DIKIRIMKAN KE FORM
            resolve(response.data)
        })
    })
},
updateCourier({ state }, payload) {
    return new Promise((resolve, reject) => {
        //FUNGSI UNTUK MELAKUKAN REQUEST DATA PERUBAHAN DATA KURIR BERDASARKAN STATE ID KURIR
        $axios.post(`/couriers/${state.id}`, payload, {
            headers: {
                'Content-Type': 'multipart/form-data'
            }
        })
        .then((response) => {
            resolve(response.data)
        })
    })
} ,

Terakhir adalah membuat API untuk melakukan pengambilan data dan perubahan data, buka file UserController.php dan tambahkan kedua method dibawah ini:

public function edit($id)
{
    $user = User::find($id); //MENGAMBIL DATA BERDASARKAN ID
    return response()->json(['status' => 'success', 'data' => $user], 200);
}

public function update(Request $request, $id)
{
    //VALIDASI DATA
    $this->validate($request, [
        'name' => 'required|string|max:150',
        'email' => 'required|email',
        'password' => 'nullable|min:6|string',
        'outlet_id' => 'required|exists:outlets,id',
        'photo' => 'nullable|image'
    ]);

    try {
        $user = User::find($id); //MENGAMBIL DATA YANG AKAN DI UBAH
        //JIKA FORM PASSWORD TIDAK DI KOSONGKAN, MAKA PASSWORD AKAN DIPERBAHARUI
        $password = $request->password != '' ? bcrypt($request->password):$user->password;
        $filename = $user->photo; //NAMA FILE FOTO SEBELUMNYA

        //JIKA ADA FILE BARU YANG DIKIRIMKAN
        if ($request->hasFile('photo')) {
            //MAKA FOTO YANG LAMA AKAN DIGANTI
            $file = $request->file('photo');
            //DAN FILE FOTO YANG LAMA AKAN DIHAPUS
            File::delete(storage_path('app/public/couriers/' . $filename));
            $filename = $request->email . '-' . time() . '.' . $file->getClientOriginalExtension();
            $file->storeAs('public/couriers', $filename);
        }
        
        //PERBAHARUI DATA YANG ADA DI DATABASE
        $user->update([
            'name' => $request->name,
            'password' => $password,
            'photo' => $filename,
            'outlet_id' => $request->outlet_id
        ]);
        return response()->json(['status' => 'success'], 200);
    } catch (\Exception $e) {
        return response()->json(['status' => 'error', 'data' => $e->getMessage()], 200);
    }
}

Jangan lupa untuk menambahkan use statement:

use File;

Fitur Hapus Data Kurir

Fitur terakhir yang akan dibuat adalah fungsi untuk menghapus data kurir yang ada, dimana sebelumnya pada bagian Kelola Data Kurir kita telah membuat sebuah tombol yang akan memicu method deleteCourier() dan didalam method tersebut terdapat sebuah fungsi untuk melakukan permintaan ke server bernama removeCourier(). Buka file stores/courier.js dan pada bagian actions tambahkan code berikut:

removeCourier({ dispatch }, payload) {
    return new Promise((resolve, reject) => {
        //MELAKUKAN PERMINTAAN KE SERVER DENGAN METHOD DELETE DAN MENGIRIMKAN ID YANG AKAN DIHAPUS
        $axios.delete(`/couriers/${payload}`)
        .then((response) => {
            //MENGAMBIL DATA TERBARU DARI SERVER
            dispatch('getCouriers').then(() => resolve(response.data))
        })
    })
}

Kemudian pada bagian API, buka file UserController dan tambahkan method untuk menghapus data:

public function destroy($id)
{
    $user = User::find($id); //MENGAMBIL DATA YANG AKAN DIHAPUS
    File::delete(storage_path('app/public/couriers/' . $user->photo)); //MENGHAPUS FILE FOTO
    $user->delete(); //MENGHAPUS DATANYA
    return response()->json(['status' => 'success']);
}

Baca Juga: Mengolah Data Relasi Antar Table Menggunakan Eloquent

[ISSUE] Symlink Storage to Public

Sebagaimana kita ketahui bahwa file gambar yang berisi foto dari masing-masing kurir disimpan ke dalam folder storage, dimana folder ini tidak bisa diakses secara langsung oleh browser sehingga kita harus membuat symlink atau shortcut dari folder storage ke public. Pada command line, jalankan command

php artisan storage:link

Kesimpulan

Menutup serial kali ini, kesimpulan yang dapat ditarik adalah fitur ini serupa dengan fitur yang ada pada management outlets. Hanya saja pada fitur, terdapat hal baru yakni fungsi untuk meng-upload gambar dan mengambil select box untuk memilih outlet yang telah tersedia. Selain itu fungsinya sama saja, hanya mengulang materi kita sebelumnya, dan besar harapan teman-teman menjadi lebih terbiasa bekerja Laravel dan Vue.js.

Adapun dokumentasi code dari artikel ini dapat kamu lihat di Github.

Category:
Share:

Comments