Deposit manual approve done

dev
Aji Kamaludin 1 year ago
parent 0cf29e6911
commit 2f9cd2994c
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -11,8 +11,8 @@
- [x] Import Voucher
- [x] Setting Web (enable affilate, amount bonus affilate)
- [x] Setting Level Customer (view levels, edit name of level,minimal saldo, max amount saldo, max hutang)
- [ ] Deposit Menu (view daftar histori deposit)
- [ ] Manual Approve Deposit
- [x] Deposit Menu (view daftar histori deposit)
- [x] Manual Approve Deposit
- [ ] Setting Bonus Coin (memasukan amount bonus coin yang didapat dengan level dan harga voucher) - coin rewards
- [ ] View Customer Coin History
- [ ] List Customer Verification

@ -44,7 +44,7 @@ class AuthController extends Controller
}
return redirect()->route('customer.login')
->with('message', ['type' => 'error', 'message' => 'invalid credentials']);
->with('message', ['type' => 'error', 'message' => 'Invalid credentials']);
}
public function signin_google()

@ -2,15 +2,62 @@
namespace App\Http\Controllers;
use App\Models\DepositHistory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
class DepositController extends Controller
{
public function index()
public function index(Request $request)
{
$query = DepositHistory::with(['customer', 'account'])
->where('credit', 0)
->orderBy('is_valid', 'desc')
->orderBy('updated_at', 'desc');
if ($request->q != '') {
$query->where(function ($query) use ($request) {
$query->where('description', 'like', "%$request->q%")
->orWhereHas('customer', function ($query) use ($request) {
$query->where('fullname', 'like', "%$request->q%")
->orWhere('email', 'like', "%$request->q%")
->orWhere('phone', 'like', "%$request->q%");
});
});
}
if ($request->status != '') {
$query->where('is_valid', $request->status);
}
if ($request->customer_id != '') {
$query->where('is_valid', $request->customer_id);
}
public function update()
return inertia('DepositHistory/Index', [
'query' => $query->paginate()
]);
}
public function update(Request $request, DepositHistory $deposit)
{
$request->validate([
'status' => [
'required',
Rule::in([DepositHistory::STATUS_VALID, DepositHistory::STATUS_REJECT])
]
]);
DB::beginTransaction();
$deposit->update([
'is_valid' => $request->status,
]);
if ($request->status === DepositHistory::STATUS_VALID) {
$deposit->update_customer_balance();
}
DB::commit();
session()->flash('message', ['type' => 'success', 'message' => 'Item has beed updated']);
}
}

@ -48,12 +48,12 @@ class DepositHistory extends Model
{
return Attribute::make(get: function () {
return [
self::STATUS_VALID => ['text' => 'Success', 'color' => 'bg-green-600'],
self::STATUS_WAIT_UPLOAD => ['text' => 'Upload bukti transfer', 'color' => 'bg-red-600'],
self::STATUS_WAIT_APPROVE => ['text' => 'Menunggu Approve', 'color' => 'bg-green-600'],
self::STATUS_WAIT_PAYMENT => ['text' => 'Menunggu Pembayaran', 'color' => 'bg-green-600'],
self::STATUS_INVALID => ['text' => 'Error', 'color' => 'bg-red-600'],
self::STATUS_REJECT => ['text' => 'Reject', 'color' => 'bg-red-600'],
self::STATUS_VALID => ['text' => 'Success', 'color' => 'bg-green-600', 'text_color' => 'text-green-600'],
self::STATUS_WAIT_UPLOAD => ['text' => 'Upload bukti transfer', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
self::STATUS_WAIT_APPROVE => ['text' => 'Menunggu Approve', 'color' => 'bg-green-600', 'text_color' => 'text-green-600'],
self::STATUS_WAIT_PAYMENT => ['text' => 'Menunggu Pembayaran', 'color' => 'bg-green-600', 'text_color' => 'text-green-600'],
self::STATUS_INVALID => ['text' => 'Error', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
self::STATUS_REJECT => ['text' => 'Reject', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
][$this->is_valid];
});
}
@ -76,9 +76,9 @@ class DepositHistory extends Model
{
return Attribute::make(get: function () {
if ($this->credit == 0) {
return $this->debit;
return 'Rp' . number_format($this->debit, 0, ',', '.');
}
return $this->credit;
return '-Rp' . number_format($this->credit, 0, ',', '.');
});
}
@ -94,6 +94,11 @@ class DepositHistory extends Model
return $this->belongsTo(Customer::class);
}
public function account()
{
return $this->belongsTo(Account::class);
}
public function update_customer_balance()
{
$customer = Customer::find($this->customer_id);

@ -61,8 +61,8 @@ class PermissionSeeder extends Seeder
['id' => Str::ulid(), 'label' => 'View Setting', 'name' => 'view-setting'],
['id' => Str::ulid(), 'label' => 'View Deposit', 'name' => 'view-deposite'],
['id' => Str::ulid(), 'label' => 'Update Deposit', 'name' => 'update-deposite'],
['id' => Str::ulid(), 'label' => 'View Deposit', 'name' => 'view-deposit'],
['id' => Str::ulid(), 'label' => 'Update Deposit', 'name' => 'update-deposit'],
['id' => Str::ulid(), 'label' => 'View Coin', 'name' => 'view-coin'],
['id' => Str::ulid(), 'label' => 'Update Coin', 'name' => 'update-coin'],

@ -31,6 +31,7 @@ export default function FormInput({
{leftItem}
</div>
<Input
id={name}
className={formClassName}
type={type}
name={name}

@ -37,6 +37,7 @@ export default function FormInputWith({
</div>
)}
<input
id={name}
type={type}
className={`mb-2 bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-gray-700 dark:placeholder-gray-400 dark:text-white ${
error

@ -1,6 +1,7 @@
import React from 'react'
export default function Input({
id = '',
type = 'text',
name,
onChange,
@ -17,6 +18,7 @@ export default function Input({
return (
<>
<input
id={id}
type={type}
className={`mb-2 bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-gray-700 dark:placeholder-gray-400 dark:text-white ${
error

@ -5,6 +5,7 @@ import { router, usePage } from '@inertiajs/react'
import { HiOutlineHome, HiOutlineUserCircle } from 'react-icons/hi'
import {
HiArrowPathRoundedSquare,
HiBars3,
HiOutlineShoppingCart,
} from 'react-icons/hi2'
@ -20,10 +21,10 @@ export default function CustomerLayout({ children }) {
const isActive = (r) => {
if (route().current(r)) {
return 'text-blue-700 h-8 w-8'
return 'text-blue-700'
}
return 'text-gray-600 h-8 w-8'
return 'text-gray-600'
}
return (
@ -33,41 +34,44 @@ export default function CustomerLayout({ children }) {
</div>
<div className="fixed bottom-0 flex flex-row justify-around w-full bg-gray-50 max-w-md">
<div
className="py-2 px-5 hover:bg-blue-200"
className={`pb-1 pt-2 px-5 hover:bg-blue-200 flex flex-col items-center ${isActive(
'home.index'
)}`}
onClick={() => handleOnClick('home.index')}
>
<HiOutlineHome className={isActive('home.index')} />
<HiOutlineHome className="h-6 w-6" />
<div className="text-xs font-light">Beranda</div>
</div>
<div className="py-2 px-5 hover:bg-blue-200 flex flex-row">
<HiOutlineShoppingCart className="text-gray-600 h-8 w-8" />
<div className="py-2 px-5 hover:bg-blue-200 flex flex-col items-center text-gray-600">
<div className="flex flex-row">
<HiOutlineShoppingCart className="h-6 w-6" />
<div>
<div className="bg-blue-300 text-blue-600 rounded-lg px-1 text-xs -ml-2">
1
</div>
</div>
</div>
<div className="py-2 px-5 hover:bg-blue-200">
<HiArrowPathRoundedSquare className="text-gray-600 h-8 w-8" />
<div className="text-xs font-light">Keranjang</div>
</div>
{user !== null ? (
<div
className="py-2 px-5 hover:bg-blue-200"
onClick={() => handleOnClick('customer.profile.index')}
>
<HiOutlineUserCircle
className={isActive('customer.profile.*')}
/>
<div className="py-2 px-5 hover:bg-blue-200 flex flex-col items-center text-gray-600">
<HiArrowPathRoundedSquare className="h-6 w-6" />
<div className="text-xs font-light">Transaksi</div>
</div>
) : (
<div
className="py-2 px-5 hover:bg-blue-200"
onClick={() => handleOnClick('customer.login')}
className={`py-2 px-5 hover:bg-blue-200 flex flex-col items-center ${isActive(
'customer.profile.*'
)}`}
onClick={() =>
handleOnClick(
user !== null
? 'customer.profile.index'
: 'customer.login'
)
}
>
<HiOutlineUserCircle
className={isActive('customer.login')}
/>
<HiBars3 className="h-6 w-6" />
<div className="text-xs font-light">Menu</div>
</div>
)}
</div>
<ToastContainer />
</div>

@ -8,6 +8,8 @@ import {
} from 'react-icons/hi'
import {
HiBanknotes,
HiCurrencyDollar,
HiFaceSmile,
HiMap,
HiOutlineGlobeAlt,
HiQuestionMarkCircle,
@ -24,7 +26,14 @@ export default [
active: 'dashboard',
permission: 'view-dashboard',
},
{
name: 'Deposit',
show: true,
icon: HiCurrencyDollar,
route: route('deposit.index'),
active: 'deposit.*',
permission: 'view-deposit',
},
{
name: 'Lokasi',
show: true,

@ -0,0 +1,162 @@
import React, { useEffect } from 'react'
import Modal from '@/Components/Modal'
import { useForm } from '@inertiajs/react'
import Button from '@/Components/Button'
import FormInput from '@/Components/FormInput'
import RoleSelectionInput from '../Role/SelectionInput'
import { isEmpty } from 'lodash'
export default function FormModal(props) {
const { modalState } = props
const { data, setData, post, processing, errors, reset, clearErrors } =
useForm({
amount: '',
image_prove_url: '',
status: '',
account: {
name: '',
bank_name: '',
holder_name: '',
account_number: '',
},
payment_channel: '',
is_valid: 0,
status: '',
status_text: '',
text_color: '',
customer_name: '',
customer_phone: '',
description: '',
})
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const handleReset = () => {
modalState.setData(null)
reset()
clearErrors()
}
const handleClose = () => {
handleReset()
modalState.toggle()
}
const handleSubmit = () => {
const deposit = modalState.data
post(route('deposit.update', deposit), {
onSuccess: () => handleClose(),
})
}
useEffect(() => {
const deposit = modalState.data
if (isEmpty(deposit) === false) {
setData({
amount: deposit.amount,
account: deposit.account,
image_prove_url: deposit.image_prove_url,
payment_channel: deposit.payment_channel,
is_valid: deposit.is_valid,
status_text: deposit.status.text,
text_color: deposit.status.text_color,
customer_name: `${deposit.customer.name} ( ${
deposit.customer.phone ?? deposit.customer.email
} )`,
description: deposit.description,
})
return
}
}, [modalState])
return (
<Modal
isOpen={modalState.isOpen}
toggle={handleClose}
title={'Deposit'}
>
<table className="relative w-full overflow-x-auto border-collapse border p-2 rounded">
<tbody>
<tr>
<td className="font-bold">Customer</td>
<td>:</td>
<td>{data.customer_name}</td>
</tr>
<tr>
<td className="font-bold">Deskripsi</td>
<td>:</td>
<td>{data.description}</td>
</tr>
<tr>
<td className="font-bold">Metode Pembayaran</td>
<td>:</td>
<td>{data.payment_channel}</td>
</tr>
{data.account !== null && (
<tr>
<td className="font-bold">Bank Akun</td>
<td>:</td>
<td>
{data.account.name} ({data.account.bank_name})
</td>
</tr>
)}
<tr>
<td className="font-bold">Jumlah</td>
<td>:</td>
<td>{data.amount}</td>
</tr>
<tr>
<td className="font-bold">Status</td>
<td>:</td>
<td className={data.text_color}>{data.status_text}</td>
</tr>
</tbody>
</table>
{isEmpty(data.image_prove_url) === false && (
<div>
<img
src={data.image_prove_url}
className="w-full object-fill h-96"
/>
</div>
)}
{+data.is_valid !== 0 && (
<>
<div className="my-4">
<div className="mb-1 text-sm">Status </div>
<select
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
onChange={handleOnChange}
value={+data.status}
name="status"
>
<option value=""></option>
<option value={0}>Approve</option>
<option value={5}>Reject</option>
</select>
</div>
<div className="flex items-center">
<Button onClick={handleSubmit} processing={processing}>
Simpan
</Button>
<Button onClick={handleClose} type="secondary">
Batal
</Button>
</div>
</>
)}
</Modal>
)
}

@ -0,0 +1,169 @@
import React, { useEffect, useState } from 'react'
import { router } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { Head } from '@inertiajs/react'
import { Button, Dropdown } from 'flowbite-react'
import { HiPencil, HiTrash } from 'react-icons/hi'
import { useModalState } from '@/hooks'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import ModalConfirm from '@/Components/ModalConfirm'
import FormModal from './FormModal'
import SearchInput from '@/Components/SearchInput'
import { formatIDR, hasPermission } from '@/utils'
import { HiEye } from 'react-icons/hi2'
export default function Info(props) {
const {
query: { links, data },
auth,
} = props
const [search, setSearch] = useState('')
const preValue = usePrevious(search)
const formModal = useModalState()
const toggleFormModal = (deposit = null) => {
formModal.setData(deposit)
formModal.toggle()
}
const params = { q: search }
useEffect(() => {
if (preValue) {
router.get(
route(route().current()),
{ q: search },
{
replace: true,
preserveState: true,
}
)
}
}, [search])
const canUpdate = hasPermission(auth, 'update-deposit')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Deposit'}
action={''}
>
<Head title="Deposit" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="flex justify-between">
<div className="flex items-center">
<SearchInput
onChange={(e) => setSearch(e.target.value)}
value={search}
/>
</div>
</div>
<div className="overflow-auto">
<div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th
scope="col"
className="py-3 px-6"
>
Customer
</th>
<th
scope="col"
className="py-3 px-6"
>
Deposit
</th>
<th
scope="col"
className="py-3 px-6"
>
Deskripsi
</th>
<th
scope="col"
className="py-3 px-6"
>
Status
</th>
<th
scope="col"
className="py-3 px-6"
/>
</tr>
</thead>
<tbody>
{data.map((deposit) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={deposit.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{deposit.customer.name}
</td>
<td className="py-4 px-6">
{deposit.amount}
</td>
<td className="py-4 px-6">
{deposit.description}
</td>
<td
className={`py-4 px-6 ${deposit.status.text_color}`}
>
{deposit.status.text}
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}
dismissOnClick={true}
size={'sm'}
>
{canUpdate && (
<Dropdown.Item
onClick={() =>
toggleFormModal(
deposit
)
}
>
<div className="flex space-x-1 items-center">
<HiEye />
<div>
Lihat
</div>
</div>
</Dropdown.Item>
)}
</Dropdown>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} />
</div>
</div>
</div>
</div>
</div>
<FormModal modalState={formModal} />
</AuthenticatedLayout>
)
}

@ -159,7 +159,7 @@ const FormUpload = () => {
<option value=""></option>
{accounts.map((account) => (
<option value={account.id} key={account.id}>
{account.bank_name}
{account.name} - {account.bank_name}
</option>
))}
</select>
@ -257,7 +257,7 @@ export default function Detail({ deposit }) {
{deposit.description}
</div>
<div className="font-bold text-3xl">
Rp {formatIDR(deposit.amount)}
{deposit.amount}
</div>
<div className="text-gray-400">
{deposit.format_created_at}

@ -76,7 +76,7 @@ export default function Index({
</div>
<div className="flex flex-col items-end">
<div className="font-bold text-lg">
{formatIDR(history.amount)}
{history.amount}
</div>
{+history.is_valid !== 0 && (
<div

@ -2,7 +2,7 @@ import { formatIDR } from '@/utils'
export default function VoucherCard({ voucher }) {
return (
<div className="px-3 py-1 shadow-md rounded border border-gray-100">
<div className="px-3 py-1 shadow-md rounded border border-gray-100 hover:bg-gray-50">
<div className="text-base font-bold">{voucher.location.name}</div>
<div className="w-full border border-dashed"></div>
<div className="flex flex-row justify-between items-center">

@ -5,6 +5,7 @@ use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\BannerController;
use App\Http\Controllers\CustomerController;
use App\Http\Controllers\CustomerLevelController;
use App\Http\Controllers\DepositController;
use App\Http\Controllers\GeneralController;
use App\Http\Controllers\InfoController;
use App\Http\Controllers\LocationController;
@ -103,5 +104,9 @@ Route::middleware(['http_secure_aware', 'inertia.admin'])
// setting
Route::get('/settings', [SettingController::class, 'index'])->name('setting.index');
Route::post('/settings', [SettingController::class, 'update'])->name('setting.update');
// deposit
Route::get('/deposites', [DepositController::class, 'index'])->name('deposit.index');
Route::post('/deposites/{deposit}', [DepositController::class, 'update'])->name('deposit.update');
});
});

Loading…
Cancel
Save