From 0abb8f5a074a8e728b22eb2e487e513819c080cc Mon Sep 17 00:00:00 2001 From: Aji Kamaludin Date: Thu, 15 Jun 2023 00:52:53 +0700 Subject: [PATCH] point customer done --- app/Http/Controllers/CustomerController.php | 4 +- .../Controllers/CustomerPointController.php | 76 ++++++++- app/Imports/CustomerPointsImport.php | 33 ++++ app/Models/Customer.php | 5 + app/Models/CustomerPoint.php | 5 + ...23_06_14_022155_create_customers_table.php | 4 +- ...14_022203_create_customer_points_table.php | 4 +- database/seeders/PermissionSeeder.php | 2 - public/point.csv | 5 + resources/js/Layouts/Partials/routes.js | 2 +- .../CustomerPoint/CustomerSelectionModal.jsx | 95 +++++++++++ resources/js/Pages/CustomerPoint/Form.jsx | 156 +++++++++++++++++ .../js/Pages/CustomerPoint/ImportModal.jsx | 102 +++++++++++ resources/js/Pages/CustomerPoint/Index.jsx | 158 ++++++++++++++++++ routes/web.php | 3 +- 15 files changed, 636 insertions(+), 18 deletions(-) create mode 100644 app/Imports/CustomerPointsImport.php create mode 100644 public/point.csv create mode 100644 resources/js/Pages/CustomerPoint/CustomerSelectionModal.jsx create mode 100644 resources/js/Pages/CustomerPoint/Form.jsx create mode 100644 resources/js/Pages/CustomerPoint/ImportModal.jsx create mode 100644 resources/js/Pages/CustomerPoint/Index.jsx diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index db0f60a..28ff3af 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -18,7 +18,7 @@ class CustomerController extends Controller ->orWhere('code', 'like', "%{$request->q}%"); } - $query->orderBy('created_at', 'desc'); + $query->orderBy('updated_at', 'desc'); return inertia('Customer/Index', [ 'query' => $query->paginate(), @@ -47,7 +47,7 @@ class CustomerController extends Controller public function update(Request $request, Customer $customer) { $request->validate([ - 'code' => 'required|string|max:255|unique:customers,code', + 'code' => 'required|string|max:255|unique:customers,code,' . $customer->id, 'name' => 'required|string|max:255', 'point' => 'required|numeric', ]); diff --git a/app/Http/Controllers/CustomerPointController.php b/app/Http/Controllers/CustomerPointController.php index 00961fd..11b6855 100644 --- a/app/Http/Controllers/CustomerPointController.php +++ b/app/Http/Controllers/CustomerPointController.php @@ -2,23 +2,85 @@ namespace App\Http\Controllers; +use App\Imports\CustomerPointsImport; +use App\Models\Customer; +use App\Models\CustomerPoint; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Maatwebsite\Excel\Facades\Excel; class CustomerPointController extends Controller { - public function index() - { - } - public function store() + public function index(Request $request) { + $query = CustomerPoint::with(['customer'])->orderBy('created_at', 'desc'); + + if ($request->customer_id != '') { + $query->where('customer_id', $request->customer_id); + } + + if ($request->q != '') { + $query->whereHas('customer', function ($query) use ($request) { + $query->where('name', 'like', "%{$request->q}%") + ->orWhere('code', 'like', "%{$request->q}%"); + }); + } + + return inertia('CustomerPoint/Index', [ + 'query' => $query->paginate() + ]); } - public function update() + + public function create(Request $request) { + $customer = Customer::query()->orderBy('updated_at', 'desc'); + + if ($request->q != '') { + $customer->where('name', 'like', "%{$request->q}%") + ->orWhere('code', 'like', "%{$request->q}%"); + } + + return inertia('CustomerPoint/Form', [ + 'customers' => $customer->paginate(10) + ]); } - public function destroy() + + public function store(Request $request) { + $request->validate([ + 'items' => 'required|array', + 'items.*.customer_id' => 'required|exists:customers,id', + 'items.*.point' => 'required|numeric', + 'items.*.description' => 'nullable|string' + ]); + + DB::beginTransaction(); + foreach ($request->items as $item) { + $customer = Customer::find($item['customer_id']); + $customer->update([ + 'last_point' => $customer->last_point + $item['point'] + ]); + $customer->points()->create([ + 'point' => $item['point'], + 'description' => $item['description'] + ]); + } + + DB::commit(); + + return redirect()->route('customer-point.index') + ->with('message', ['type' => 'success', 'message' => 'Create item success']); } - public function import() + + public function import(Request $request) { + $request->validate([ + 'file' => 'required|file' + ]); + + Excel::import(new CustomerPointsImport, $request->file('file')); + + return redirect()->route('customer-point.index') + ->with('message', ['type' => 'success', 'message' => 'Import Success']); } } diff --git a/app/Imports/CustomerPointsImport.php b/app/Imports/CustomerPointsImport.php new file mode 100644 index 0000000..da58e3a --- /dev/null +++ b/app/Imports/CustomerPointsImport.php @@ -0,0 +1,33 @@ +first(); + + if ($customer != null) { + $customer->update([ + 'last_point' => $customer->last_point + $row['point'] + ]); + + return new CustomerPoint([ + 'customer_id' => $customer->id, + 'description' => $row['description'], + 'point' => $row['point'], + ]); + } + } +} diff --git a/app/Models/Customer.php b/app/Models/Customer.php index c48a7cb..63db19f 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -11,4 +11,9 @@ class Customer extends Model 'start_point', 'last_point', ]; + + public function points() + { + return $this->hasMany(CustomerPoint::class); + } } diff --git a/app/Models/CustomerPoint.php b/app/Models/CustomerPoint.php index adaeb9b..6ec81b4 100644 --- a/app/Models/CustomerPoint.php +++ b/app/Models/CustomerPoint.php @@ -9,4 +9,9 @@ class CustomerPoint extends Model 'description', 'point', ]; + + public function customer() + { + return $this->belongsTo(Customer::class); + } } diff --git a/database/migrations/2023_06_14_022155_create_customers_table.php b/database/migrations/2023_06_14_022155_create_customers_table.php index 9916218..eb31f92 100644 --- a/database/migrations/2023_06_14_022155_create_customers_table.php +++ b/database/migrations/2023_06_14_022155_create_customers_table.php @@ -15,8 +15,8 @@ return new class extends Migration $table->ulid('id')->primary(); $table->string('name'); $table->string('code')->unique(); - $table->decimal('start_point', 20, 2); - $table->decimal('last_point', 20, 2); + $table->decimal('start_point', 20, 2)->default(0); + $table->decimal('last_point', 20, 2)->default(0); $table->timestamps(); $table->softDeletes(); $table->ulid('created_by')->nullable(); diff --git a/database/migrations/2023_06_14_022203_create_customer_points_table.php b/database/migrations/2023_06_14_022203_create_customer_points_table.php index 04060c7..235d211 100644 --- a/database/migrations/2023_06_14_022203_create_customer_points_table.php +++ b/database/migrations/2023_06_14_022203_create_customer_points_table.php @@ -14,8 +14,8 @@ return new class extends Migration Schema::create('customer_points', function (Blueprint $table) { $table->ulid('id')->primary(); $table->ulid('customer_id'); - $table->string('description', 1000); - $table->decimal('point', 20, 2); + $table->string('description', 1000)->nullable(); + $table->decimal('point', 20, 2)->default(0); $table->timestamps(); $table->softDeletes(); $table->ulid('created_by')->nullable(); diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php index 82f3e2c..dda3ed9 100644 --- a/database/seeders/PermissionSeeder.php +++ b/database/seeders/PermissionSeeder.php @@ -39,9 +39,7 @@ class PermissionSeeder extends Seeder ['id' => Str::ulid(), 'label' => 'Delete Customer', 'name' => 'delete-customer'], ['id' => Str::ulid(), 'label' => 'Create Customer Point', 'name' => 'create-customer-point'], - ['id' => Str::ulid(), 'label' => 'Update Customer Point', 'name' => 'update-customer-point'], ['id' => Str::ulid(), 'label' => 'View Customer Point', 'name' => 'view-customer-point'], - ['id' => Str::ulid(), 'label' => 'Delete Customer Point', 'name' => 'delete-customer-point'], ]; foreach ($permissions as $permission) { diff --git a/public/point.csv b/public/point.csv new file mode 100644 index 0000000..0e30cc1 --- /dev/null +++ b/public/point.csv @@ -0,0 +1,5 @@ +code,point,description +AACRB400,1,'some note' +AAISU221,20, +AAISU221,-10, +AAMMZ012,-2, \ No newline at end of file diff --git a/resources/js/Layouts/Partials/routes.js b/resources/js/Layouts/Partials/routes.js index 17125f1..17408a4 100644 --- a/resources/js/Layouts/Partials/routes.js +++ b/resources/js/Layouts/Partials/routes.js @@ -30,7 +30,7 @@ export default [ show: true, icon: HiTrophy, route: route('customer-point.index'), - active: 'customer-point.index', + active: 'customer-point.*', permission: 'view-customer-point', }, { diff --git a/resources/js/Pages/CustomerPoint/CustomerSelectionModal.jsx b/resources/js/Pages/CustomerPoint/CustomerSelectionModal.jsx new file mode 100644 index 0000000..1662aa0 --- /dev/null +++ b/resources/js/Pages/CustomerPoint/CustomerSelectionModal.jsx @@ -0,0 +1,95 @@ +import React, { useState, useEffect } from 'react' +import { router, usePage } from '@inertiajs/react' +import { Spinner } from 'flowbite-react' +import { usePrevious } from 'react-use' + +import Modal from '@/Components/Modal' +import SearchInput from '@/Components/SearchInput' +import Pagination from '@/Components/Pagination' + +export default function CustomerSelectionModal(props) { + const { modalState, onItemSelected } = props + const [loading, setLoading] = useState(false) + + const { + props: { + customers: { data, links }, + }, + } = usePage() + + const [search, setSearch] = useState('') + const preValue = usePrevious(search) + const params = { customer_q: search } + + const handleItemSelected = (item) => { + onItemSelected(item) + modalState.toggle() + } + + useEffect(() => { + if (preValue) { + router.get(route(route().current()), params, { + replace: true, + preserveState: true, + }) + } + }, [search]) + + useEffect(() => { + router.on('start', () => setLoading(true)) + router.on('finish', () => setLoading(false)) + }, []) + + return ( + + setSearch(e.target.value)} + value={search} + /> + {loading ? ( +
+ +
+ ) : ( +
+
+ + + + + + + + {data.map((customer) => ( + + handleItemSelected(customer) + } + > + + + ))} + +
+ Name +
+ {customer.name} +
+
+
+ +
+
+ )} +
+ ) +} diff --git a/resources/js/Pages/CustomerPoint/Form.jsx b/resources/js/Pages/CustomerPoint/Form.jsx new file mode 100644 index 0000000..b1ef037 --- /dev/null +++ b/resources/js/Pages/CustomerPoint/Form.jsx @@ -0,0 +1,156 @@ +import React from 'react' +import { Head, useForm } from '@inertiajs/react' + +import { useModalState } from '@/hooks' +import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout' +import Button from '@/Components/Button' +import CustomerSelectionModal from './CustomerSelectionModal' +import FormInput from '@/Components/FormInput' +import { HiXCircle } from 'react-icons/hi2' + +export default function Setting(props) { + const { data, setData, post, processing, errors } = useForm({ + items: [], + }) + + const customerSelectionModal = useModalState() + + const addItem = (customer) => { + const isExists = data.items.find((i) => i.customer.id === customer.id) + if (!isExists) { + let items = data.items.concat({ + customer: customer, + customer_id: customer.id, + point: 0, + description: '', + }) + setData('items', items) + } + } + + const removeItem = (index) => { + let items = data.items.filter((_, i) => i !== index) + setData('items', items) + } + + const handleChangeValue = (name, value, index) => { + setData( + 'items', + data.items.map((item, i) => { + if (i === index) { + item[name] = value + } + return item + }) + ) + } + + const handleSubmit = () => { + post(route('customer-point.store')) + } + + return ( + + + +
+
+
+
Point
+
+ + + + + + + + + + + {data.items.map((item, index) => ( + + + + + + + ))} + +
+ Customer + + Point + + Description + +
+ {item.customer.name} ({' '} + {item.customer.code} ) + + + handleChangeValue( + 'point', + e.target.value, + index + ) + } + /> + + + handleChangeValue( + 'description', + e.target.value, + index + ) + } + /> + + + removeItem(index) + } + /> +
+
+
+ +
+
+
+
+ +
+ ) +} diff --git a/resources/js/Pages/CustomerPoint/ImportModal.jsx b/resources/js/Pages/CustomerPoint/ImportModal.jsx new file mode 100644 index 0000000..b06964c --- /dev/null +++ b/resources/js/Pages/CustomerPoint/ImportModal.jsx @@ -0,0 +1,102 @@ +import React, { useEffect, useRef } from 'react' +import { useForm } from '@inertiajs/react' +import Modal from '@/Components/Modal' +import Button from '@/Components/Button' +export default function ImportModal(props) { + const { modalState } = props + + const { data, setData, post, progress, processing, errors, clearErrors } = + useForm({ + file: null, + }) + + const inputFileImport = useRef() + + const handleReset = () => { + setData({ file: null }) + inputFileImport.current.value = '' + clearErrors() + } + + const handleCancel = () => { + modalState.toggle() + handleReset() + } + + const handleClose = () => { + handleReset() + modalState.toggle() + } + + function handleSubmit(e) { + e.preventDefault() + post(route('customer-point.import'), { + forceFormData: false, + onSuccess: () => Promise.all([handleReset(), modalState.toggle()]), + }) + return + } + + return ( + +
{ + console.log(inputFileImport.current.click()) + }} + > +
+ Pilih File:{' '} +
+
{data.file ? data.file.name : 'Pilih File'}
+
+
{errors.file}
+ setData('file', e.target.files[0])} + /> + {progress && ( +
+
+ {' '} + {progress.percentage}% +
+
+ )} + +

+ Unduh format file import{' '} + + disini + +

+
+ + +
+
+ ) +} diff --git a/resources/js/Pages/CustomerPoint/Index.jsx b/resources/js/Pages/CustomerPoint/Index.jsx new file mode 100644 index 0000000..4e9aed9 --- /dev/null +++ b/resources/js/Pages/CustomerPoint/Index.jsx @@ -0,0 +1,158 @@ +import React, { useEffect, useState } from 'react' +import { router, Link } from '@inertiajs/react' +import { usePrevious } from 'react-use' +import { Head } from '@inertiajs/react' +import { Button } from 'flowbite-react' +import { useModalState } from '@/hooks' + +import { hasPermission } from '@/utils' +import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout' +import Pagination from '@/Components/Pagination' +import SearchInput from '@/Components/SearchInput' +import ImportModal from './ImportModal' +import CustomerSelectionInput from '../Customer/SelectionInput' + +export default function Customer(props) { + const { + query: { links, data }, + auth, + } = props + + const [search, setSearch] = useState('') + const [customer_id, setCustomerId] = useState(null) + const preValue = usePrevious(`${search}-${customer_id}`) + + const importModal = useModalState() + + const toggleImportModal = () => { + importModal.toggle() + } + + const params = { q: search, customer_id: customer_id } + useEffect(() => { + if (preValue) { + router.get( + route(route().current()), + { q: search, customer_id: customer_id }, + { + replace: true, + preserveState: true, + } + ) + } + }, [search, customer_id]) + + const canCreate = hasPermission(auth, 'create-customer-point') + + return ( + + + +
+
+
+
+ {canCreate && ( +
+ + + Create + + + +
+ )} +
+
+ + setCustomerId(id) + } + /> +
+
+ + setSearch(e.target.value) + } + value={search} + /> +
+
+
+
+
+ + + + + + + + + + {data.map((point) => ( + + + + + + ))} + +
+ Customer + + Point + + Description +
+ {point.customer.name} ({' '} + {point.customer.code} ) + + {point.point} + + {point.description} +
+
+
+ +
+
+
+
+
+ +
+ ) +} diff --git a/routes/web.php b/routes/web.php index a29c44d..69484cf 100644 --- a/routes/web.php +++ b/routes/web.php @@ -52,9 +52,8 @@ Route::middleware(['auth'])->group(function () { // Customer Point Route::get('/customers-points', [CustomerPointController::class, 'index'])->name('customer-point.index'); Route::post('/customers-points/import', [CustomerPointController::class, 'import'])->name('customer-point.import'); + Route::get('/customers-points/create', [CustomerPointController::class, 'create'])->name('customer-point.create'); Route::post('/customers-points', [CustomerPointController::class, 'store'])->name('customer-point.store'); - Route::put('/customers-points/{customer}', [CustomerPointController::class, 'update'])->name('customer-point.update'); - Route::delete('/customers-points/{customer}', [CustomerPointController::class, 'destroy'])->name('customer-point.destroy'); }); Route::middleware('auth')->group(function () {