Muhammad Iqbal Afandi преди 3 години
родител
ревизия
0089c78e35

+ 29
- 11
app/Http/Controllers/CustomerController.php Целия файл

@@ -2,8 +2,9 @@
2 2
 
3 3
 namespace App\Http\Controllers;
4 4
 
5
+use App\Http\Requests\Customer\StoreCustomerRequest;
6
+use App\Http\Requests\Customer\UpdateCustomerRequest;
5 7
 use App\Models\Customer;
6
-use Illuminate\Http\Request;
7 8
 
8 9
 class CustomerController extends Controller
9 10
 {
@@ -19,7 +20,20 @@ class CustomerController extends Controller
19 20
      */
20 21
     public function index()
21 22
     {
22
-        return inertia('Customers/Index.vue');
23
+        return inertia('Customers/Index.vue', [
24
+            'initialSearch' => request('search'),
25
+            'customers' => Customer::filter(request()->only('search'))
26
+                ->latest()
27
+                ->paginate(10)
28
+                ->withQueryString()
29
+                ->through(fn($customer) => [
30
+                    'id' => $customer->id,
31
+                    'name' => $customer->name,
32
+                    'address' => $customer->address,
33
+                    'phone' => $customer->phone,
34
+                    'npwp' => $customer->npwp
35
+                ])
36
+        ]);
23 37
     }
24 38
 
25 39
     /**
@@ -29,7 +43,7 @@ class CustomerController extends Controller
29 43
      */
30 44
     public function create()
31 45
     {
32
-        //
46
+        return inertia('Customers/Create');
33 47
     }
34 48
 
35 49
     /**
@@ -38,9 +52,11 @@ class CustomerController extends Controller
38 52
      * @param  \Illuminate\Http\Request  $request
39 53
      * @return \Illuminate\Http\Response
40 54
      */
41
-    public function store(Request $request)
55
+    public function store(StoreCustomerRequest $request)
42 56
     {
43
-        //
57
+        Customer::create($request->validated());
58
+
59
+        return back()->with('success', __('messages.success.store.customer'));
44 60
     }
45 61
 
46 62
     /**
@@ -57,24 +73,26 @@ class CustomerController extends Controller
57 73
     /**
58 74
      * Show the form for editing the specified resource.
59 75
      *
60
-     * @param  int  $id
76
+     * @param  Customer  $customer
61 77
      * @return \Illuminate\Http\Response
62 78
      */
63
-    public function edit($id)
79
+    public function edit(Customer $customer)
64 80
     {
65
-        //
81
+        return inertia('Customers/Edit.vue', compact('customer'));
66 82
     }
67 83
 
68 84
     /**
69 85
      * Update the specified resource in storage.
70 86
      *
71 87
      * @param  \Illuminate\Http\Request  $request
72
-     * @param  int  $id
88
+     * @param  Customer  $customer
73 89
      * @return \Illuminate\Http\Response
74 90
      */
75
-    public function update(Request $request, $id)
91
+    public function update(UpdateCustomerRequest $request, Customer $customer)
76 92
     {
77
-        //
93
+        $customer->update($request->validated());
94
+
95
+        return back()->with('success', __('messages.success.update.customer'));
78 96
     }
79 97
 
80 98
     /**

+ 33
- 0
app/Http/Requests/Customer/StoreCustomerRequest.php Целия файл

@@ -0,0 +1,33 @@
1
+<?php
2
+
3
+namespace App\Http\Requests\Customer;
4
+
5
+use Illuminate\Foundation\Http\FormRequest;
6
+
7
+class StoreCustomerRequest extends FormRequest
8
+{
9
+    /**
10
+     * Determine if the user is authorized to make this request.
11
+     *
12
+     * @return bool
13
+     */
14
+    public function authorize()
15
+    {
16
+        return true;
17
+    }
18
+
19
+    /**
20
+     * Get the validation rules that apply to the request.
21
+     *
22
+     * @return array<string, mixed>
23
+     */
24
+    public function rules()
25
+    {
26
+        return [
27
+            'name' => 'required|string|max:50',
28
+            'address' => 'required|string',
29
+            'phone' => 'required|numeric|digits_between:12,15|unique:customers,phone',
30
+            'npwp' => 'required|numeric|digits_between:15,20|unique:customers,npwp'
31
+        ];
32
+    }
33
+}

+ 33
- 0
app/Http/Requests/Customer/UpdateCustomerRequest.php Целия файл

@@ -0,0 +1,33 @@
1
+<?php
2
+
3
+namespace App\Http\Requests\Customer;
4
+
5
+use Illuminate\Foundation\Http\FormRequest;
6
+
7
+class UpdateCustomerRequest extends FormRequest
8
+{
9
+    /**
10
+     * Determine if the user is authorized to make this request.
11
+     *
12
+     * @return bool
13
+     */
14
+    public function authorize()
15
+    {
16
+        return true;
17
+    }
18
+
19
+    /**
20
+     * Get the validation rules that apply to the request.
21
+     *
22
+     * @return array<string, mixed>
23
+     */
24
+    public function rules()
25
+    {
26
+        return [
27
+            'name' => 'required|string|max:50',
28
+            'address' => 'required|string',
29
+            'phone' => 'required|numeric|digits_between:12,15|unique:customers,phone,' . $this->customer->id,
30
+            'npwp' => 'required|numeric|digits_between:15,20|unique:customers,npwp,' . $this->customer->id
31
+        ];
32
+    }
33
+}

+ 13
- 0
app/Models/Customer.php Целия файл

@@ -15,4 +15,17 @@ class Customer extends Model
15 15
         'phone',
16 16
         'npwp'
17 17
     ];
18
+
19
+    protected $hidden = ['created_at', 'updated_at'];
20
+
21
+    public function scopeFilter($query, array $filters)
22
+    {
23
+        $query->when($filters['search'] ?? null, function ($query, $search) {
24
+            $query->where(function ($query) use ($search) {
25
+                $query->where('name', 'like', '%' . $search . '%')
26
+                    ->orWhere('phone', 'like', '%' . $search . '%')
27
+                    ->orWhere('npwp', 'like', '%' . $search . '%');
28
+            });
29
+        });
30
+    }
18 31
 }

+ 4
- 2
lang/en/messages.php Целия файл

@@ -31,7 +31,8 @@ return [
31 31
             'expense' => 'Expense successfully added',
32 32
             'transaction' => 'Transaction successfully added',
33 33
             'user' => 'User Account successfully added',
34
-            'reset_password' => 'Password successfully reset'
34
+            'reset_password' => 'Password successfully reset',
35
+            'customer' => 'Customer successfully addedd'
35 36
         ],
36 37
         'update' => [
37 38
             'type_vehicle' => 'Type vehicle successfully changed',
@@ -40,7 +41,8 @@ return [
40 41
             'type_member' => 'Jenis member successfully changed',
41 42
             'change_password' => 'Password successfully changed',
42 43
             'transaction_status' => 'Transaction Status successfully changed',
43
-            'user' => 'User Account successfully changed'
44
+            'user' => 'User Account successfully changed',
45
+            'customer' => 'Customer successfully changed'
44 46
         ],
45 47
         'destroy' => [
46 48
             'type_member' => 'Jenis member successfully deleted',

+ 4
- 2
lang/id/messages.php Целия файл

@@ -31,7 +31,8 @@ return [
31 31
             'expense' => 'Pengeluaran berhasil ditambahkan',
32 32
             'transaction' => 'Transaksi berhasil ditambahkan',
33 33
             'user' => 'Akun user berhasil ditambahkan',
34
-            'reset_password' => 'Kata sandi berhasil di reset'
34
+            'reset_password' => 'Kata sandi berhasil di reset',
35
+            'customer' => 'Pelanggan berhasil ditambahkan'
35 36
         ],
36 37
         'update' => [
37 38
             'type_vehicle' => 'Jenis kendaraan berhasil diubah',
@@ -40,7 +41,8 @@ return [
40 41
             'type_member' => 'Jenis member berhasil diubah',
41 42
             'change_password' => 'Password berhasil diubah',
42 43
             'transaction_status' => 'Status transaksi berhasil diubah',
43
-            'user' => 'Akun user berhasil diubah'
44
+            'user' => 'Akun user berhasil diubah',
45
+            'customer' => 'Pelanggan berhasil diubah'
44 46
         ],
45 47
         'destroy' => [
46 48
             'type_member' => 'Jenis member berhasil dihapus',

+ 6
- 2
public/js/resources_js_pages_Auth_Login_vue.js Целия файл

@@ -28,6 +28,10 @@ __webpack_require__.r(__webpack_exports__);
28 28
       type: String,
29 29
       required: true
30 30
     },
31
+    type: {
32
+      type: String,
33
+      "default": 'text'
34
+    },
31 35
     error: {
32 36
       type: String,
33 37
       "default": null
@@ -285,7 +289,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
285 289
     "class": (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["w-full", {
286 290
       'p-invalid': $setup.isError
287 291
     }]),
288
-    type: "text",
292
+    type: $props.type,
289 293
     id: $setup.forLabel,
290 294
     "aria-describedby": $setup.ariaDescribedbyLabel,
291 295
     "model-value": $props.modelValue,
@@ -297,7 +301,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
297 301
     })
298 302
   }, null, 8
299 303
   /* PROPS */
300
-  , ["class", "id", "aria-describedby", "model-value", "placeholder", "value", "disabled"]), $props.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("small", {
304
+  , ["type", "class", "id", "aria-describedby", "model-value", "placeholder", "value", "disabled"]), $props.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("small", {
301 305
     key: 0,
302 306
     id: $setup.ariaDescribedbyLabel,
303 307
     "class": (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({

+ 1896
- 0
public/js/resources_js_pages_Customers_Create_vue.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1897
- 0
public/js/resources_js_pages_Customers_Edit_vue.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 17781
- 43
public/js/resources_js_pages_Customers_Index_vue.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 30
- 0
public/js/resources_js_pages_Customers_tableHeader_js.js Целия файл

@@ -0,0 +1,30 @@
1
+"use strict";
2
+(self["webpackChunk"] = self["webpackChunk"] || []).push([["resources_js_pages_Customers_tableHeader_js"],{
3
+
4
+/***/ "./resources/js/pages/Customers/tableHeader.js":
5
+/*!*****************************************************!*\
6
+  !*** ./resources/js/pages/Customers/tableHeader.js ***!
7
+  \*****************************************************/
8
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
9
+
10
+__webpack_require__.r(__webpack_exports__);
11
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
12
+/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
13
+/* harmony export */ });
14
+/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ([{
15
+  field: 'name',
16
+  header: 'Nama'
17
+}, {
18
+  field: 'address',
19
+  header: 'Alamat'
20
+}, {
21
+  field: 'phone',
22
+  header: 'No HP'
23
+}, {
24
+  field: 'npwp',
25
+  header: 'NPWP'
26
+}]);
27
+
28
+/***/ })
29
+
30
+}]);

+ 6
- 2
public/js/resources_js_pages_Users_Create_vue.js Целия файл

@@ -128,6 +128,10 @@ __webpack_require__.r(__webpack_exports__);
128 128
       type: String,
129 129
       required: true
130 130
     },
131
+    type: {
132
+      type: String,
133
+      "default": 'text'
134
+    },
131 135
     error: {
132 136
       type: String,
133 137
       "default": null
@@ -606,7 +610,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
606 610
     "class": (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["w-full", {
607 611
       'p-invalid': $setup.isError
608 612
     }]),
609
-    type: "text",
613
+    type: $props.type,
610 614
     id: $setup.forLabel,
611 615
     "aria-describedby": $setup.ariaDescribedbyLabel,
612 616
     "model-value": $props.modelValue,
@@ -618,7 +622,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
618 622
     })
619 623
   }, null, 8
620 624
   /* PROPS */
621
-  , ["class", "id", "aria-describedby", "model-value", "placeholder", "value", "disabled"]), $props.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("small", {
625
+  , ["type", "class", "id", "aria-describedby", "model-value", "placeholder", "value", "disabled"]), $props.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("small", {
622 626
     key: 0,
623 627
     id: $setup.ariaDescribedbyLabel,
624 628
     "class": (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({

+ 6
- 2
public/js/resources_js_pages_Users_Edit_vue.js Целия файл

@@ -167,6 +167,10 @@ __webpack_require__.r(__webpack_exports__);
167 167
       type: String,
168 168
       required: true
169 169
     },
170
+    type: {
171
+      type: String,
172
+      "default": 'text'
173
+    },
170 174
     error: {
171 175
       type: String,
172 176
       "default": null
@@ -729,7 +733,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
729 733
     "class": (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["w-full", {
730 734
       'p-invalid': $setup.isError
731 735
     }]),
732
-    type: "text",
736
+    type: $props.type,
733 737
     id: $setup.forLabel,
734 738
     "aria-describedby": $setup.ariaDescribedbyLabel,
735 739
     "model-value": $props.modelValue,
@@ -741,7 +745,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
741 745
     })
742 746
   }, null, 8
743 747
   /* PROPS */
744
-  , ["class", "id", "aria-describedby", "model-value", "placeholder", "value", "disabled"]), $props.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("small", {
748
+  , ["type", "class", "id", "aria-describedby", "model-value", "placeholder", "value", "disabled"]), $props.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("small", {
745 749
     key: 0,
746 750
     id: $setup.ariaDescribedbyLabel,
747 751
     "class": (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({

+ 6
- 2
public/js/resources_js_pages_Users_Show_vue.js Целия файл

@@ -28,6 +28,10 @@ __webpack_require__.r(__webpack_exports__);
28 28
       type: String,
29 29
       required: true
30 30
     },
31
+    type: {
32
+      type: String,
33
+      "default": 'text'
34
+    },
31 35
     error: {
32 36
       type: String,
33 37
       "default": null
@@ -503,7 +507,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
503 507
     "class": (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["w-full", {
504 508
       'p-invalid': $setup.isError
505 509
     }]),
506
-    type: "text",
510
+    type: $props.type,
507 511
     id: $setup.forLabel,
508 512
     "aria-describedby": $setup.ariaDescribedbyLabel,
509 513
     "model-value": $props.modelValue,
@@ -515,7 +519,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
515 519
     })
516 520
   }, null, 8
517 521
   /* PROPS */
518
-  , ["class", "id", "aria-describedby", "model-value", "placeholder", "value", "disabled"]), $props.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("small", {
522
+  , ["type", "class", "id", "aria-describedby", "model-value", "placeholder", "value", "disabled"]), $props.error ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("small", {
519 523
     key: 0,
520 524
     id: $setup.ariaDescribedbyLabel,
521 525
     "class": (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({

+ 25
- 1
public/js/vue.js Целия файл

@@ -58308,6 +58308,22 @@ var map = {
58308 58308
 		"./resources/js/pages/Auth/Login.vue",
58309 58309
 		"resources_js_pages_Auth_Login_vue"
58310 58310
 	],
58311
+	"./Customers/Create": [
58312
+		"./resources/js/pages/Customers/Create.vue",
58313
+		"resources_js_pages_Customers_Create_vue"
58314
+	],
58315
+	"./Customers/Create.vue": [
58316
+		"./resources/js/pages/Customers/Create.vue",
58317
+		"resources_js_pages_Customers_Create_vue"
58318
+	],
58319
+	"./Customers/Edit": [
58320
+		"./resources/js/pages/Customers/Edit.vue",
58321
+		"resources_js_pages_Customers_Edit_vue"
58322
+	],
58323
+	"./Customers/Edit.vue": [
58324
+		"./resources/js/pages/Customers/Edit.vue",
58325
+		"resources_js_pages_Customers_Edit_vue"
58326
+	],
58311 58327
 	"./Customers/Index": [
58312 58328
 		"./resources/js/pages/Customers/Index.vue",
58313 58329
 		"resources_js_pages_Customers_Index_vue"
@@ -58316,6 +58332,14 @@ var map = {
58316 58332
 		"./resources/js/pages/Customers/Index.vue",
58317 58333
 		"resources_js_pages_Customers_Index_vue"
58318 58334
 	],
58335
+	"./Customers/tableHeader": [
58336
+		"./resources/js/pages/Customers/tableHeader.js",
58337
+		"resources_js_pages_Customers_tableHeader_js"
58338
+	],
58339
+	"./Customers/tableHeader.js": [
58340
+		"./resources/js/pages/Customers/tableHeader.js",
58341
+		"resources_js_pages_Customers_tableHeader_js"
58342
+	],
58319 58343
 	"./Dashboards/Index": [
58320 58344
 		"./resources/js/pages/Dashboards/Index.vue",
58321 58345
 		"resources_js_pages_Dashboards_Index_vue"
@@ -58513,7 +58537,7 @@ module.exports = JSON.parse('{"name":"axios","version":"0.21.4","description":"P
58513 58537
 /******/ 		// This function allow to reference async chunks
58514 58538
 /******/ 		__webpack_require__.u = (chunkId) => {
58515 58539
 /******/ 			// return url for filenames based on template
58516
-/******/ 			return "js/" + chunkId + ".js?id=" + {"node_modules_chart_js_auto_auto_esm_js":"9296b829a7757dee","resources_js_pages_Auth_Login_vue":"cb7d9267c0b9275e","resources_js_pages_Customers_Index_vue":"eb78407dfd08456f","resources_js_pages_Dashboards_Index_vue":"7f4150c836fe81d8","resources_js_pages_Purchases_Index_vue":"41fddd7f79c7a85d","resources_js_pages_Sales_Index_vue":"1da65c4ce926abbc","resources_js_pages_StockProducts_Index_vue":"74a65be5ec079b3f","resources_js_pages_Suppliers_Index_vue":"0f2fb6e79fdbec94","resources_js_pages_Users_Create_vue":"d77599e458334081","resources_js_pages_Users_Edit_vue":"e0eb01c3dc9f721f","resources_js_pages_Users_Index_vue":"575b2403097c7e16","resources_js_pages_Users_Show_vue":"5c32daddba0a1355","resources_js_pages_Users_tableHeader_js":"48f19bd820caf015"}[chunkId] + "";
58540
+/******/ 			return "js/" + chunkId + ".js?id=" + {"node_modules_chart_js_auto_auto_esm_js":"9296b829a7757dee","resources_js_pages_Auth_Login_vue":"8e53429f130c83f5","resources_js_pages_Customers_Create_vue":"1ec93aefdbb7bac8","resources_js_pages_Customers_Edit_vue":"47650583b348b8eb","resources_js_pages_Customers_Index_vue":"527935b3268e6218","resources_js_pages_Customers_tableHeader_js":"7a40a3d5ad60171c","resources_js_pages_Dashboards_Index_vue":"7f4150c836fe81d8","resources_js_pages_Purchases_Index_vue":"41fddd7f79c7a85d","resources_js_pages_Sales_Index_vue":"1da65c4ce926abbc","resources_js_pages_StockProducts_Index_vue":"74a65be5ec079b3f","resources_js_pages_Suppliers_Index_vue":"0f2fb6e79fdbec94","resources_js_pages_Users_Create_vue":"290f8cea7ecbd8da","resources_js_pages_Users_Edit_vue":"4f8b3ff98efb4ef3","resources_js_pages_Users_Index_vue":"575b2403097c7e16","resources_js_pages_Users_Show_vue":"5561027a65c32ca4","resources_js_pages_Users_tableHeader_js":"48f19bd820caf015"}[chunkId] + "";
58517 58541
 /******/ 		};
58518 58542
 /******/ 	})();
58519 58543
 /******/ 	

+ 5
- 1
resources/js/components/AppInputText.vue Целия файл

@@ -14,6 +14,10 @@ const props = defineProps({
14 14
     type: String,
15 15
     required: true,
16 16
   },
17
+  type: {
18
+    type: String,
19
+    default: 'text',
20
+  },
17 21
   error: {
18 22
     type: String,
19 23
     default: null,
@@ -38,7 +42,7 @@ const ariaDescribedbyLabel = computed(
38 42
 
39 43
     <InputText
40 44
       class="w-full"
41
-      type="text"
45
+      :type="type"
42 46
       :class="{ 'p-invalid': isError }"
43 47
       :id="forLabel"
44 48
       :aria-describedby="ariaDescribedbyLabel"

+ 86
- 0
resources/js/pages/Customers/Create.vue Целия файл

@@ -0,0 +1,86 @@
1
+<script setup>
2
+import { useForm, Head } from '@inertiajs/inertia-vue3'
3
+import { useFormErrorReset } from '@/components/useFormErrorReset'
4
+import AppInputText from '@/components/AppInputText.vue'
5
+import DashboardLayout from '@/layouts/DashboardLayout.vue'
6
+
7
+const form = useForm({
8
+  name: null,
9
+  address: null,
10
+  phone: null,
11
+  npwp: null,
12
+})
13
+
14
+useFormErrorReset(form)
15
+
16
+const onSubmit = () => {
17
+  form.post(route('customers.store'), { onSuccess: () => form.reset() })
18
+}
19
+</script>
20
+
21
+<template>
22
+  <Head title="Tambah Pelanggan" />
23
+
24
+  <DashboardLayout>
25
+    <div class="grid">
26
+      <div class="col-12 lg:col-8">
27
+        <Card>
28
+          <template #title> Tambah Pelanggan </template>
29
+          <template #content>
30
+            <div class="grid">
31
+              <div class="col-12 md:col-6">
32
+                <AppInputText
33
+                  label="Nama"
34
+                  placeholder="nama"
35
+                  :error="form.errors.name"
36
+                  v-model="form.name"
37
+                />
38
+              </div>
39
+
40
+              <div class="col-12 md:col-6">
41
+                <AppInputText
42
+                  label="Alamat"
43
+                  placeholder="alamat"
44
+                  :error="form.errors.address"
45
+                  v-model="form.address"
46
+                />
47
+              </div>
48
+
49
+              <div class="col-12 md:col-6">
50
+                <AppInputText
51
+                  label="No HP"
52
+                  placeholder="no hp"
53
+                  type="number"
54
+                  :error="form.errors.phone"
55
+                  v-model="form.phone"
56
+                />
57
+              </div>
58
+
59
+              <div class="col-12 md:col-6">
60
+                <AppInputText
61
+                  label="NPWP"
62
+                  placeholder="npwp"
63
+                  type="number"
64
+                  :error="form.errors.npwp"
65
+                  v-model="form.npwp"
66
+                />
67
+              </div>
68
+            </div>
69
+          </template>
70
+
71
+          <template #footer>
72
+            <div class="flex flex-column md:flex-row justify-content-end">
73
+              <Button
74
+                label="Simpan"
75
+                icon="pi pi-check"
76
+                class="p-button-outlined"
77
+                :disabled="form.processing"
78
+                @click="onSubmit"
79
+              />
80
+            </div>
81
+          </template>
82
+        </Card>
83
+      </div>
84
+    </div>
85
+  </DashboardLayout>
86
+</template>

+ 90
- 0
resources/js/pages/Customers/Edit.vue Целия файл

@@ -0,0 +1,90 @@
1
+<script setup>
2
+import { useForm, Head } from '@inertiajs/inertia-vue3'
3
+import { useFormErrorReset } from '@/components/useFormErrorReset'
4
+import AppInputText from '@/components/AppInputText.vue'
5
+import DashboardLayout from '@/layouts/DashboardLayout.vue'
6
+
7
+const props = defineProps({
8
+  customer: Object,
9
+})
10
+
11
+const form = useForm({
12
+  name: props.customer.name,
13
+  address: props.customer.address,
14
+  phone: props.customer.phone,
15
+  npwp: props.customer.npwp,
16
+})
17
+
18
+useFormErrorReset(form)
19
+
20
+const onSubmit = () => {
21
+  form.put(route('customers.update', props.customer.id))
22
+}
23
+</script>
24
+
25
+<template>
26
+  <Head title="Tambah Pelanggan" />
27
+
28
+  <DashboardLayout>
29
+    <div class="grid">
30
+      <div class="col-12 lg:col-8">
31
+        <Card>
32
+          <template #title> Tambah Pelanggan </template>
33
+          <template #content>
34
+            <div class="grid">
35
+              <div class="col-12 md:col-6">
36
+                <AppInputText
37
+                  label="Nama"
38
+                  placeholder="nama"
39
+                  :error="form.errors.name"
40
+                  v-model="form.name"
41
+                />
42
+              </div>
43
+
44
+              <div class="col-12 md:col-6">
45
+                <AppInputText
46
+                  label="Alamat"
47
+                  placeholder="alamat"
48
+                  :error="form.errors.address"
49
+                  v-model="form.address"
50
+                />
51
+              </div>
52
+
53
+              <div class="col-12 md:col-6">
54
+                <AppInputText
55
+                  label="No HP"
56
+                  placeholder="no hp"
57
+                  type="number"
58
+                  :error="form.errors.phone"
59
+                  v-model="form.phone"
60
+                />
61
+              </div>
62
+
63
+              <div class="col-12 md:col-6">
64
+                <AppInputText
65
+                  label="NPWP"
66
+                  placeholder="npwp"
67
+                  type="number"
68
+                  :error="form.errors.npwp"
69
+                  v-model="form.npwp"
70
+                />
71
+              </div>
72
+            </div>
73
+          </template>
74
+
75
+          <template #footer>
76
+            <div class="flex flex-column md:flex-row justify-content-end">
77
+              <Button
78
+                label="Simpan"
79
+                icon="pi pi-check"
80
+                class="p-button-outlined"
81
+                :disabled="form.processing"
82
+                @click="onSubmit"
83
+              />
84
+            </div>
85
+          </template>
86
+        </Card>
87
+      </div>
88
+    </div>
89
+  </DashboardLayout>
90
+</template>

+ 77
- 2
resources/js/pages/Customers/Index.vue Целия файл

@@ -1,10 +1,85 @@
1 1
 <script setup>
2
+import { watch } from 'vue'
3
+import { Inertia } from '@inertiajs/inertia'
2 4
 import { Head } from '@inertiajs/inertia-vue3'
5
+import { pickBy } from 'lodash'
6
+import tableHeader from './tableHeader'
7
+import { useSearchText } from '@/components/useSearchText'
3 8
 import DashboardLayout from '@/layouts/DashboardLayout.vue'
9
+import AppButtonLink from '@/components/AppButtonLink.vue'
10
+import AppPagination from '@/components/AppPagination.vue'
11
+
12
+const props = defineProps({
13
+  customers: Object,
14
+  initialSearch: String,
15
+})
16
+
17
+const { search } = useSearchText(props)
18
+
19
+watch(search, () => {
20
+  Inertia.get('/customers', pickBy({ search: search.value }), {
21
+    preserveState: true,
22
+  })
23
+})
4 24
 </script>
5 25
 
6 26
 <template>
7
-  <Head title="Halaman Pelangan" />
27
+  <Head title="Daftar User" />
28
+
29
+  <DashboardLayout>
30
+    <DataTable
31
+      responsiveLayout="scroll"
32
+      columnResizeMode="expand"
33
+      :value="customers.data"
34
+      :rowHover="true"
35
+      :stripedRows="true"
36
+    >
37
+      <template #header>
38
+        <h1>Pelanggan</h1>
39
+
40
+        <div class="grid">
41
+          <div class="col-12 md:col-8">
42
+            <div class="flex align-items-center">
43
+              <InputText
44
+                class="w-full md:w-27rem"
45
+                placeholder="cari, contoh: tina, 08xx, 0x"
46
+                v-model="search"
47
+              />
48
+            </div>
49
+          </div>
50
+
51
+          <div
52
+            class="col-12 md:col-4 flex flex-column md:flex-row justify-content-end"
53
+          >
54
+            <AppButtonLink
55
+              label="Tambah User"
56
+              icon="pi pi-pencil"
57
+              class="p-button-outlined"
58
+              :href="route('customers.create')"
59
+            />
60
+          </div>
61
+        </div>
62
+      </template>
63
+
64
+      <Column
65
+        v-for="value in tableHeader"
66
+        :field="value.field"
67
+        :header="value.header"
68
+        :key="value.field"
69
+      />
70
+
71
+      <Column>
72
+        <template #body="{ data }">
73
+          <AppButtonLink
74
+            icon="pi pi-pencil"
75
+            class="p-button-icon-only p-button-rounded p-button-text"
76
+            v-tooltip.bottom="'Ubah User'"
77
+            :href="route('customers.edit', data.id)"
78
+          />
79
+        </template>
80
+      </Column>
81
+    </DataTable>
8 82
 
9
-  <DashboardLayout> Halaman Pelangan </DashboardLayout>
83
+    <AppPagination :links="customers.links" />
84
+  </DashboardLayout>
10 85
 </template>

+ 6
- 0
resources/js/pages/Customers/tableHeader.js Целия файл

@@ -0,0 +1,6 @@
1
+export default [
2
+  { field: 'name', header: 'Nama' },
3
+  { field: 'address', header: 'Alamat' },
4
+  { field: 'phone', header: 'No HP' },
5
+  { field: 'npwp', header: 'NPWP' },
6
+]