data mitra

dev
Aji Kamaludin 1 year ago
parent 585888f37e
commit 4d8d26610e
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -62,6 +62,7 @@ class CustomerController extends Controller
public function store(Request $request)
{
$request->validate([
'email' => 'nullable|email|unique:customers,email',
'username' => 'required|string|min:5|alpha_dash|unique:customers,username',
'password' => 'required|string|min:8',
'name' => 'required|string',
@ -73,6 +74,7 @@ class CustomerController extends Controller
]);
$customer = Customer::make([
'email' => $request->email,
'username' => $request->username,
'password' => bcrypt($request->password),
'name' => $request->name,
@ -106,6 +108,7 @@ class CustomerController extends Controller
public function update(Request $request, Customer $customer)
{
$request->validate([
'email' => 'nullable|email|unique:customers,email,' . $customer->id,
'username' => 'required|string|min:5|alpha_dash|unique:customers,username,' . $customer->id,
'password' => 'nullable|string|min:8',
'name' => 'required|string',
@ -127,6 +130,7 @@ class CustomerController extends Controller
}
$customer->update([
'email' => $request->email,
'username' => $request->username,
'password' => $customer->password,
'name' => $request->name,
@ -154,6 +158,7 @@ class CustomerController extends Controller
$request->validate([
'level' => 'required|exists:customer_levels,key',
'paylater_limit' => 'required|numeric',
'day_deadline' => 'required|numeric',
]);
$level = CustomerLevel::where('key', $request->level)->first();
@ -164,6 +169,7 @@ class CustomerController extends Controller
'customer_id' => $customer->id,
], [
'limit' => $request->paylater_limit,
'day_deadline' => $request->day_deadline
]);
return redirect()->route('customer.index')
@ -173,7 +179,8 @@ class CustomerController extends Controller
public function update_partner(Request $request, Customer $customer)
{
$request->validate([
'job' => 'required|string',
'id_number' => 'nullable|string',
'job' => 'nullable|string',
'image_selfie' => 'nullable|file',
'file_statement' => 'nullable|file',
'file_agreement' => 'nullable|file',
@ -187,6 +194,7 @@ class CustomerController extends Controller
$partner = CustomerAsDataPartner::updateOrCreate([
'customer_id' => $customer->id,
], [
'id_number' => $request->id_number,
'job' => $request->job,
'additional_json' => json_encode($request->items),
]);

@ -0,0 +1,298 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Models\CustomerLevel;
use App\Models\PaylaterCustomer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class CustomerMitraController extends Controller
{
public function index(Request $request)
{
$stats = [
'total_mitra' => Customer::whereHas('level', function ($q) {
return $q->where('key', CustomerLevel::GOLD)
->orWhere('key', CustomerLevel::PLATINUM);
})->count(),
'blocked_mitra' => Customer::whereHas('level', function ($q) {
return $q->where('key', CustomerLevel::GOLD)
->orWhere('key', CustomerLevel::PLATINUM);
})
->where('status', Customer::STATUS_SUSPEND)
->count(),
'active_mitra' => Customer::whereHas('level', function ($q) {
return $q->where('key', CustomerLevel::GOLD)
->orWhere('key', CustomerLevel::PLATINUM);
})
->where('status', '!=', Customer::STATUS_SUSPEND)
->count(),
'sum_paylater_limit' => PaylaterCustomer::sum('limit'),
'sum_paylater_usage' => PaylaterCustomer::sum('usage'),
'sum_paylater_remain' => PaylaterCustomer::selectRaw('(SUM("limit") - SUM(usage)) as remain')->value('remain'),
];
$query = Customer::query()->with(['level', 'paylater', 'locationFavorites'])
->whereHas('level', function ($q) {
$q->where('key', CustomerLevel::GOLD)
->orWhere('key', CustomerLevel::PLATINUM);
});
if ($request->q != '') {
$query->where(function ($query) use ($request) {
$query->where('name', 'like', "%$request->q%")
->orWhere('fullname', 'like', "%$request->q%")
->orWhere('email', 'like', "%$request->q%")
->orWhere('phone', 'like', "%$request->q%");
});
}
if ($request->location_id != '') {
$query->whereHas('locationFavorites', fn ($q) => $q->where('id', $request->location_id));
}
if ($request->level_id != '') {
$query->where('customer_level_id', $request->level_id);
}
if ($request->sortBy != '' && $request->sortRule != '') {
$query->orderBy($request->sortBy, $request->sortRule);
} else {
$query->orderBy('updated_at', 'desc');
}
return inertia('CustomerMitra/Index', [
'query' => $query->paginate(),
'stats' => $stats,
]);
}
public function create()
{
$levels = CustomerLevel::where('key', CustomerLevel::GOLD)
->orWhere('key', CustomerLevel::PLATINUM)
->get();
return inertia('CustomerMitra/Form', [
'levels' => $levels,
'statuses' => Customer::STATUS
]);
}
public function store(Request $request)
{
$request->validate([
'email' => 'nullable|email|unique:customers,email',
'username' => 'required|string|min:5|alpha_dash|unique:customers,username',
'password' => 'required|string|min:8',
'name' => 'required|string',
'fullname' => 'required|string',
'address' => 'required|string',
'phone' => 'required|string',
'status' => 'required|numeric',
'image' => 'nullable|image',
'identity_image' => 'nullable|image',
//
'level' => 'required|exists:customer_levels,key',
'paylater_limit' => 'required|numeric',
'day_deadline' => 'required|numeric',
//
'id_number' => 'nullable|string',
'job' => 'nullable|string',
'image_selfie' => 'nullable|image',
'file_statement' => 'nullable|file',
'file_agreement' => 'nullable|file',
'items' => 'nullable|array',
'items.*.name' => 'nullable|string',
'items.*.type' => 'required|in:text,file',
'items.*.value' => 'nullable|string',
]);
$level = CustomerLevel::where('key', $request->level)->first();
DB::beginTransaction();
$customer = Customer::make([
'email' => $request->email,
'username' => $request->username,
'password' => bcrypt($request->password),
'name' => $request->name,
'fullname' => $request->fullname,
'address' => $request->address,
'phone' => $request->phone,
'status' => $request->status,
'customer_level_id' => $level->id,
'identity_verified' => $request->hasFile('identity_image') ? Customer::VERIFIED : Customer::NOT_VERIFIED
]);
if ($request->hasFile('image')) {
$file = $request->file('image');
$file->store('uploads', 'public');
$customer->image = $file->hashName('uploads');
}
if ($request->hasFile('identity_image')) {
$file = $request->file('identity_image');
$file->store('uploads', 'public');
$customer->identity_image = $file->hashName('uploads');
}
$customer->save();
$customer->paylater()->create([
'limit' => $request->paylater_limit,
'day_deadline' => $request->day_deadline
]);
$partner = $customer->partner()->create([
'id_number' => $request->id_number,
'job' => $request->job,
'additional_json' => json_encode($request->items),
]);
if ($request->hasFile('image_selfie')) {
$file = $request->file('image_selfie');
$file->store('uploads', 'public');
$partner->update(['image_selfie' => $file->hashName('uploads')]);
}
if ($request->hasFile('file_statement')) {
$file = $request->file('file_statement');
$file->store('uploads', 'public');
$partner->update(['file_statement' => $file->hashName('uploads')]);
}
if ($request->hasFile('file_agreement')) {
$file = $request->file('file_agreement');
$file->store('uploads', 'public');
$partner->update(['file_agreement' => $file->hashName('uploads')]);
}
DB::commit();
return redirect()->route('mitra.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed created']);
}
public function edit(Customer $customer)
{
$levels = CustomerLevel::where('key', CustomerLevel::GOLD)
->orWhere('key', CustomerLevel::PLATINUM)
->get();
return inertia('CustomerMitra/Form', [
'customer' => $customer->load(['paylater', 'partner']),
'levels' => $levels,
'statuses' => Customer::STATUS
]);
}
public function update(Request $request, Customer $customer)
{
$request->validate([
'email' => 'nullable|email|unique:customers,email,' . $customer->id,
'username' => 'required|string|min:5|alpha_dash|unique:customers,username,' . $customer->id,
'password' => 'nullable|string|min:8',
'name' => 'required|string',
'fullname' => 'required|string',
'address' => 'required|string',
'phone' => 'required|string',
'status' => 'required|numeric',
'image' => 'nullable|image',
'identity_image' => 'nullable|image',
//
'level' => 'required|exists:customer_levels,key',
'paylater_limit' => 'required|numeric',
'day_deadline' => 'required|numeric',
//
'id_number' => 'nullable|string',
'job' => 'nullable|string',
'image_selfie' => 'nullable|image',
'file_statement' => 'nullable|file',
'file_agreement' => 'nullable|file',
'items' => 'nullable|array',
'items.*.name' => 'nullable|string',
'items.*.type' => 'required|in:text,file',
'items.*.value' => 'nullable|string',
]);
$level = CustomerLevel::where('key', $request->level)->first();
DB::beginTransaction();
$customer->fill([
'email' => $request->email,
'username' => $request->username,
'name' => $request->name,
'fullname' => $request->fullname,
'address' => $request->address,
'phone' => $request->phone,
'status' => $request->status,
'customer_level_id' => $level->id,
'identity_verified' => $request->hasFile('identity_image') ? Customer::VERIFIED : Customer::NOT_VERIFIED
]);
if ($request->password != '') {
$customer->password = bcrypt($request->password);
}
if ($request->hasFile('image')) {
$file = $request->file('image');
$file->store('uploads', 'public');
$customer->image = $file->hashName('uploads');
}
if ($request->hasFile('identity_image')) {
$file = $request->file('identity_image');
$file->store('uploads', 'public');
$customer->identity_image = $file->hashName('uploads');
}
$customer->save();
$customer->paylater()->updateOrCreate([
'customer_id' => $customer->id,
], [
'limit' => $request->paylater_limit,
'day_deadline' => $request->day_deadline
]);
$partner = $customer->partner()->updateOrCreate([
'customer_id' => $customer->id,
], [
'id_number' => $request->id_number,
'job' => $request->job,
'additional_json' => json_encode($request->items),
]);
if ($request->hasFile('image_selfie')) {
$file = $request->file('image_selfie');
$file->store('uploads', 'public');
$partner->update(['image_selfie' => $file->hashName('uploads')]);
}
if ($request->hasFile('file_statement')) {
$file = $request->file('file_statement');
$file->store('uploads', 'public');
$partner->update(['file_statement' => $file->hashName('uploads')]);
}
if ($request->hasFile('file_agreement')) {
$file = $request->file('file_agreement');
$file->store('uploads', 'public');
$partner->update(['file_agreement' => $file->hashName('uploads')]);
}
DB::commit();
return redirect()->route('mitra.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed updated']);
}
public function destroy(Customer $customer)
{
$customer->delete();
return redirect()->route('mitra.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed deleted']);
}
}

@ -10,6 +10,12 @@ class CustomerLevelController extends Controller
{
public function index(Request $request)
{
return CustomerLevel::all();
$query = CustomerLevel::query();
if ($request->get('only') != '') {
$query->whereIn('key', $request->get('only'));
}
return $query->get();
}
}

@ -48,6 +48,11 @@ class AuthController extends Controller
'username' => $request->username,
])->first();
if ($user === null) {
return redirect()->route('customer.login')
->with('message', ['type' => 'error', 'message' => 'Invalid credentials']);
}
$password = Hash::check($request->password, $user->password);
if (!$password) {
return redirect()->route('customer.login')

@ -253,7 +253,7 @@ class Customer extends Authenticatable
public function paylater()
{
return $this->hasOne(PaylaterCustomer::class);
return $this->hasOne(PaylaterCustomer::class, 'customer_id', 'id');
}
public function paylaterHistories()

@ -8,6 +8,7 @@ class CustomerAsDataPartner extends Model
{
protected $fillable = [
'customer_id',
'id_number',
'job',
'image_selfie',
'file_statement',

@ -12,4 +12,9 @@ class PaylaterCustomer extends Model
'day_deadline',
'day_deadline_at'
];
public function customer()
{
return $this->hasOne(Customer::class, 'customer_id', 'id');
}
}

@ -15,6 +15,7 @@ return new class extends Migration
$table->ulid('id')->primary();
$table->ulid('customer_id')->nullable();
$table->string('id_number')->nullable();
$table->string('job')->nullable();
$table->string('image_selfie')->nullable();
$table->string('file_statement')->nullable();

@ -55,6 +55,18 @@ class PermissionSeeder extends Seeder
['id' => Str::ulid(), 'label' => 'Update Customer Level', 'name' => 'update-customer-level'],
['id' => Str::ulid(), 'label' => 'View Customer Level', 'name' => 'view-customer-level'],
['id' => Str::ulid(), 'label' => 'Create Mitra', 'name' => 'create-mitra'],
['id' => Str::ulid(), 'label' => 'Update Mitra', 'name' => 'update-mitra'],
['id' => Str::ulid(), 'label' => 'View Mitra', 'name' => 'view-mitra'],
['id' => Str::ulid(), 'label' => 'Delete Mitra', 'name' => 'delete-mitra'],
['id' => Str::ulid(), 'label' => 'Update Limit Mitra', 'name' => 'update-limit-mitra'],
['id' => Str::ulid(), 'label' => 'Update Limit Tenor', 'name' => 'update-limit-tenor'],
['id' => Str::ulid(), 'label' => 'Create Pembayaran Hutang', 'name' => 'create-paylater-repayment'],
['id' => Str::ulid(), 'label' => 'Update Pembayaran Hutang', 'name' => 'update-paylater-repayment'],
['id' => Str::ulid(), 'label' => 'View Pembayaran Hutang', 'name' => 'view-paylater-repayment'],
['id' => Str::ulid(), 'label' => 'View Customer Verification', 'name' => 'view-customer-verification'],
['id' => Str::ulid(), 'label' => 'View Setting', 'name' => 'view-setting'],

@ -42,7 +42,7 @@ export default function FormInputTime({
<select
value={clock}
onChange={(e) => selectTime(e.target.value, 'c')}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
{clocks.map((c) => (
<option key={c} value={c}>
@ -53,7 +53,7 @@ export default function FormInputTime({
<select
value={minute}
onChange={(e) => selectTime(e.target.value, 'm')}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
{minutes.map((m) => (
<option key={m} value={m}>

@ -9,6 +9,7 @@ import Alert from '@/Components/Alert'
import FormInputWith from '@/Components/FormInputWith'
import TextArea from '@/Components/TextArea'
import FormFile from '@/Components/FormFile'
import { isEmpty } from 'lodash'
export default function Index({ auth: { user }, flash }) {
const { data, setData, post, processing, errors } = useForm({
@ -138,12 +139,14 @@ export default function Index({ auth: { user }, flash }) {
label="password confirm"
/>
</div>
<div className="w-full text-sm font-medium">
<div className="mb-2">email </div>
<div className="px-2 py-2.5 border rounded-md bg-gray-200">
{user.email}
{isEmpty(user.email) === false && (
<div className="w-full text-sm font-medium">
<div className="mb-2">email </div>
<div className="px-2 py-2.5 border rounded-md bg-gray-200">
{user.email}
</div>
</div>
</div>
)}
<div className="w-full">
<FormFile
label={'Profile Image'}

@ -1,19 +1,35 @@
import React from 'react'
import ApplicationLogo from '@/Components/Defaults/ApplicationLogo'
import { Link } from '@inertiajs/react'
import { Flowbite } from 'flowbite-react'
const customTheme = {
button: {
color: {
primary: 'bg-blue-700 hover:bg-blue-600 text-white',
},
},
dropdown: {
color: {
primary: 'bg-blue-700 hover:bg-blue-600 text-white',
},
},
}
export default function Guest({ children }) {
return (
<div className="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100 dark:bg-gray-900">
<div>
<Link href="/">
<ApplicationLogo className="w-auto h-20 fill-current text-gray-500 text-5xl font-bold text-center" />
</Link>
</div>
<Flowbite theme={{ theme: customTheme }}>
<div className="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100 dark:bg-gray-900">
<div>
<Link href="/">
<ApplicationLogo className="w-auto h-20 fill-current text-gray-500 text-5xl font-bold text-center" />
</Link>
</div>
<div className="w-full max-w-md mt-6 px-6 py-4 bg-white dark:bg-gray-800 shadow-md overflow-hidden sm:rounded-lg">
{children}
<div className="w-full max-w-md mt-6 px-6 py-4 bg-white dark:bg-gray-800 shadow-md overflow-hidden sm:rounded-lg">
{children}
</div>
</div>
</div>
</Flowbite>
)
}

@ -156,8 +156,8 @@ export default [
name: 'List', //daftar mitra dan stats
show: true,
icon: HiClipboardDocumentList,
route: route('customer.index'),
active: 'customer.*',
route: route('mitra.index'),
active: 'mitra.*',
permission: 'view-customer',
},
{

@ -20,6 +20,7 @@ export default function Partner() {
const generalUploadRef = useRef()
const { data, setData, post, processing, errors } = useForm({
id_number: '',
job: '',
image_selfie: null,
image_selfie_url: '',
@ -116,6 +117,7 @@ export default function Partner() {
items = []
}
setData({
id_number: customer.id_number,
job: customer.partner.job,
image_selfie_url: customer.partner.image_selfie_url,
file_statement_url: customer.partner.file_statement_url,
@ -147,7 +149,16 @@ export default function Partner() {
error={errors.job}
/>
</div>
<div className="">
<div>
<FormInput
name="id_number"
value={data.id_number}
onChange={handleOnChange}
label="No KTP"
error={errors.id_number}
/>
</div>
<div>
<FormFile
label={'Image Selfie'}
onChange={(e) => setData('image_selfie', e.target.files[0])}
@ -166,7 +177,7 @@ export default function Partner() {
}
/>
</div>
<div className="">
<div>
<FormFile
label={'Surat Pernyataan'}
onChange={(e) =>
@ -188,7 +199,7 @@ export default function Partner() {
}
/>
</div>
<div className="">
<div>
<FormFile
label={'Surat Perjanjian'}
onChange={(e) =>
@ -245,7 +256,7 @@ export default function Partner() {
</td>
<td className="px-2 py-1">
<select
className="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
value={item.type}
name="type"
onChange={(e) =>

@ -12,6 +12,7 @@ export default function Paylater() {
const { data, setData, post, processing, errors } = useForm({
level: customer?.level.key,
paylater_limit: +customer?.paylater?.limit,
day_deadline: +customer?.paylater?.day_deadline,
})
const handleOnChange = (event) => {
@ -49,7 +50,7 @@ export default function Paylater() {
)}
<div className="mb-1 text-sm">Level</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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={data.level}
name="level"
@ -67,7 +68,7 @@ export default function Paylater() {
</p>
)}
</div>
<div className="mb-4 mt-2">
<div className="mt-2">
<FormInputNumeric
type="number"
label="Limit Hutang"
@ -77,6 +78,16 @@ export default function Paylater() {
error={errors.paylater_limit}
/>
</div>
<div className="mb-4 mt-2">
<FormInputNumeric
type="number"
label="Tenor (Hari)"
name="day_deadline"
onChange={handleOnChange}
value={data.day_deadline}
error={errors.day_deadline}
/>
</div>
<div className="flex items-center">
<Button onClick={handleSubmit} processing={processing}>
Simpan

@ -14,6 +14,7 @@ export default function Profile() {
} = usePage()
const { data, setData, post, processing, errors } = useForm({
email: '',
username: '',
password: '',
name: '',
@ -46,6 +47,7 @@ export default function Profile() {
useEffect(() => {
if (isEmpty(customer) === false) {
setData({
email: customer.email,
username: customer.username,
password: customer.password,
name: customer.name,
@ -93,6 +95,13 @@ export default function Profile() {
error={errors.address}
rows={4}
/>
<FormInput
name="email"
value={data.email}
onChange={handleOnChange}
label="email"
error={errors.email}
/>
<FormInput
name="username"
value={data.username}
@ -110,7 +119,7 @@ export default function Profile() {
<div className="mt-2">
<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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={data.status}
name="status"
@ -124,7 +133,7 @@ export default function Profile() {
</select>
</div>
<FormFile
label={'Image'}
label={'Profile Image'}
onChange={(e) => setData('image', e.target.files[0])}
error={errors.image}
preview={

@ -19,6 +19,7 @@ export default function SelectionInput(props) {
placeholder = '',
error = '',
all = 0,
only = [],
} = props
const [showItems, setShowItem] = useState([])
@ -81,12 +82,19 @@ export default function SelectionInput(props) {
const fetch = (q = '') => {
setLoading(true)
axios
.get(route('api.customer-level.index', { q: q, all: all }), {
headers: {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer ' + auth.user.jwt_token
},
})
.get(
route('api.customer-level.index', {
q: q,
all: all,
only: only,
}),
{
headers: {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer ' + auth.user.jwt_token
},
}
)
.then((response) => {
setShowItem(response.data)
})

@ -0,0 +1,564 @@
import React, { useEffect, useState, useRef } from 'react'
import { Head, useForm } from '@inertiajs/react'
import { isEmpty } from 'lodash'
import { HiXCircle } from 'react-icons/hi2'
import { Spinner } from 'flowbite-react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import FormInput from '@/Components/FormInput'
import FormInputWith from '@/Components/FormInputWith'
import TextArea from '@/Components/TextArea'
import FormFile from '@/Components/FormFile'
import Button from '@/Components/Button'
import FormInputNumeric from '@/Components/FormInputNumeric'
export default function Form(props) {
const { customer, statuses, levels, csrf_token } = props
const [uploadIndex, setUploadIndex] = useState(null)
const [loading, setLoading] = useState(false)
const generalUploadRef = useRef()
const { data, setData, post, processing, errors } = useForm({
email: '',
username: '',
password: '',
name: '',
fullname: '',
address: '',
phone: '',
identity_image: null,
identity_image_url: '',
image: null,
image_url: '',
status: 0,
level: '',
paylater_limit: '',
day_deadline: '',
id_number: '',
job: '',
image_selfie: null,
image_selfie_url: '',
file_statement: null,
file_statement_url: '',
file_agreement: null,
file_agreement_url: '',
items: [],
locations: [],
})
const addItem = () => {
setData(
'items',
data.items.concat({
name: '',
type: 'text',
value: '',
})
)
}
const removeItem = (index) => {
setData(
'items',
data.items.filter((_, i) => i !== index)
)
}
const changeValueItem = (index, e) => {
setData(
'items',
data.items.map((item, i) => {
if (i === index) {
item[e.target.name] = e.target.value
}
return item
})
)
}
const handleClickUpload = (index) => {
setUploadIndex(index)
generalUploadRef.current.click()
}
const handleFileUpload = (e) => {
const body = new FormData()
body.append('_token', csrf_token)
body.append('image', e.target.files[0])
setLoading(true)
fetch(route('post.upload'), {
method: 'post',
body: body,
headers: {
'accept-content': 'application/json',
'X-CSSRF-TOKEN': csrf_token,
},
credentials: 'include',
})
.then((res) => res.json())
.then((res) => {
changeValueItem(uploadIndex, {
target: { name: 'value', value: res.url },
})
})
.catch((err) => {
alert(err)
})
.finally(() => {
setLoading(false)
})
}
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const handleSubmit = () => {
if (isEmpty(customer) === false) {
post(route('mitra.update', customer))
return
}
post(route('mitra.store'))
}
useEffect(() => {
if (isEmpty(customer) === false) {
let items = []
if (isEmpty(customer.partner) == false) {
items = JSON.parse(customer.partner.additional_json)
}
setData({
email: customer.email,
username: customer.username,
password: customer.password,
name: customer.name,
fullname: customer.fullname,
address: customer.address,
phone: customer.phone,
status: customer.status,
image_url: customer.image_url,
identity_image_url: customer.identity_image_url,
level: customer.level.key,
paylater_limit: customer.paylater.limit,
day_deadline: customer.paylater.day_deadline,
id_number: customer?.partner?.id_number,
job: customer?.partner?.job,
image_selfie_url: customer?.partner?.image_selfie_url,
file_statement_url: customer?.partner?.file_statement_url,
file_agreement_url: customer?.partner?.file_agreement_url,
items: items,
})
}
}, [customer])
return (
<AuthenticatedLayout
page={'Mitra WBB'}
action={'Form'}
parent={route('mitra.index')}
>
<Head title="Mitra WBB" />
<div>
<div className="mx-auto sm:px-6 lg:px-8">
<div className="overflow-hidden p-4 shadow-sm sm:rounded-lg bg-white dark:bg-gray-800 flex flex-col ">
<div className="text-xl font-bold mb-4">Mitra WBB</div>
<FormInput
name="fullname"
value={data.fullname}
onChange={handleOnChange}
label="Nama Lengkap"
error={errors.fullname}
/>
<FormInput
name="name"
value={data.name}
onChange={handleOnChange}
label="Nama Panggilan"
error={errors.name}
/>
<FormInputWith
type="number"
leftItem={<div className="text-sm">+62</div>}
name="phone"
value={data.phone}
onChange={handleOnChange}
error={errors.phone}
formClassName={'pl-10'}
label="Whatsapp"
/>
<TextArea
name="address"
value={data.address}
onChange={handleOnChange}
label="Alamat Lengkap"
error={errors.address}
rows={4}
/>
<FormInput
name="email"
value={data.email}
onChange={handleOnChange}
label="email"
error={errors.email}
/>
<FormInput
name="username"
value={data.username}
onChange={handleOnChange}
label="username"
error={errors.username}
/>
<FormInput
name="password"
value={data.password}
onChange={handleOnChange}
label="password"
error={errors.password}
/>
<div className="mt-2">
<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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={data.status}
name="status"
>
<option value=""></option>
{statuses.map((status, index) => (
<option value={index} key={status}>
{status}
</option>
))}
</select>
</div>
<FormFile
label={'Profile Image'}
onChange={(e) =>
setData('image', e.target.files[0])
}
error={errors.image}
preview={
isEmpty(data.image_url) === false && (
<img
src={data.image_url}
className="mb-1 h-24 w-24 object-cover rounded-full"
alt="preview"
loading="lazy"
/>
)
}
/>
<div className="mb-1 text-sm">Level</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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={data.level}
name="level"
>
<option value=""></option>
{levels.map((level) => (
<option value={level.key} key={level.key}>
{level.name}
</option>
))}
</select>
{errors.level && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{errors.level}
</p>
)}
<div className="mt-2">
<FormInputNumeric
type="number"
label="Limit Hutang"
name="paylater_limit"
onChange={handleOnChange}
value={data.paylater_limit}
error={errors.paylater_limit}
/>
</div>
<div className="mt-2">
<FormInputNumeric
type="number"
label="Tenor (Hari)"
name="day_deadline"
onChange={handleOnChange}
value={data.day_deadline}
error={errors.day_deadline}
/>
</div>
<input
ref={generalUploadRef}
type="file"
onChange={handleFileUpload}
className="hidden"
/>
<div className="mt-2">
<FormInput
name="job"
value={data.job}
onChange={handleOnChange}
label="Pekerjaan"
error={errors.job}
/>
</div>
<div className="mt-2">
<FormInput
name="id_number"
value={data.id_number}
onChange={handleOnChange}
label="No KTP"
error={errors.id_number}
/>
</div>
<div>
<FormFile
label={'KTP Image'}
onChange={(e) =>
setData('identity_image', e.target.files[0])
}
error={errors.identity_image}
preview={
isEmpty(data.identity_image_url) ===
false && (
<a
href={data.identity_image_url}
target="_blank"
>
<img
src={data.identity_image_url}
className="mb-1 h-40 object-fill"
alt="preview"
loading="lazy"
/>
</a>
)
}
/>
</div>
<div>
<FormFile
label={'Image Selfie'}
onChange={(e) =>
setData('image_selfie', e.target.files[0])
}
error={errors.image_selfie}
preview={
isEmpty(data.image_selfie_url) ===
false && (
<a
href={data.image_selfie_url}
target="_blank"
>
<img
src={data.image_selfie_url}
className="mb-1 h-40 object-fill"
alt="preview"
loading="lazy"
/>
</a>
)
}
/>
</div>
<div>
<FormFile
label={'Surat Pernyataan'}
onChange={(e) =>
setData('file_statement', e.target.files[0])
}
error={errors.file_statement}
preview={
isEmpty(data.file_statement_url) ===
false && (
<div className="w-full text-right">
<a
href={data.file_statement_url}
target="_blank"
className="underline text-sm "
>
Download Surat Pernyataan
</a>
</div>
)
}
/>
</div>
<div>
<FormFile
label={'Surat Perjanjian'}
onChange={(e) =>
setData('file_agreement', e.target.files[0])
}
error={errors.file_agreement}
preview={
isEmpty(data.file_agreement_url) ===
false && (
<div className="w-full text-right">
<a
href={data.file_agreement_url}
target="_blank"
className="underline text-sm "
>
Download Surat Perjanjian
</a>
</div>
)
}
/>
</div>
<div className="border rounded-md px-2">
<div className="font-semibold p-2">
Data Lainnya
</div>
{loading ? (
<Spinner
className="w-full h-72 py-5"
size="xl"
/>
) : (
<>
<table className="w-full">
<thead>
<tr>
<th className="text-left px-2 py-1 w-2/6">
Nama
</th>
<th className="text-left px-2 py-1 w-1/6">
Tipe
</th>
<th className="text-left px-2 py-1 w-3/6">
Nilai
</th>
<th />
</tr>
</thead>
<tbody>
{data.items.map((item, index) => (
<tr key={index}>
<td className="px-2 py-1">
<FormInput
value={item.name}
name="name"
onChange={(e) =>
changeValueItem(
index,
e
)
}
/>
</td>
<td className="px-2 py-1">
<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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
value={item.type}
name="type"
onChange={(e) =>
changeValueItem(
index,
e
)
}
>
<option value="text">
text
</option>
<option value="file">
file
</option>
</select>
</td>
<td className="px-2 py-1">
{item.type ===
'text' ? (
<FormInput
value={
item.value
}
name="value"
onChange={(e) =>
changeValueItem(
index,
e
)
}
/>
) : (
<div className="w-full flex flex-row gap-1 items-center">
<div
className="px-2 py-1 border rounded-md hover:bg-gray-200"
onClick={() =>
handleClickUpload(
index
)
}
>
Choose File
</div>
<div>
{isEmpty(
item.value
) ? (
'No file chosen'
) : (
<a
href={
item.value
}
target="_blank"
className="underline pl-2"
>
File
Uploaded
</a>
)}
</div>
</div>
)}
</td>
<td>
<div
onClick={() =>
removeItem(
index
)
}
>
<HiXCircle className="text-red-700 w-10 h-7" />
</div>
</td>
</tr>
))}
</tbody>
</table>
<div className="w-full flex flex-row justify-end">
<Button onClick={() => addItem()}>
Tambah
</Button>
</div>
</>
)}
</div>
<div className="mt-8">
<Button
onClick={handleSubmit}
processing={processing}
>
Simpan
</Button>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -0,0 +1,413 @@
import React, { useEffect, useState } from 'react'
import { Link, 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 { formatIDR, hasPermission } from '@/utils'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import ModalConfirm from '@/Components/ModalConfirm'
import SearchInput from '@/Components/SearchInput'
import LocationSelectionInput from '../Location/SelectionInput'
import LevelSelectionInput from '../CustomerLevel/SelectionInput'
import ThSort from '@/Components/ThSortComponent'
export default function Customer(props) {
const {
query: { links, data },
stats,
auth,
_search,
_sortBy,
_sortOrder,
} = props
const [location, setLocation] = useState(null)
const [level, setLevel] = useState(null)
const [search, setSearch] = useState({
q: _search,
sortBy: _sortBy,
sortOrder: _sortOrder,
})
const preValue = usePrevious(`${search}${location}${level}`)
const confirmModal = useModalState()
const handleDeleteClick = (customer) => {
confirmModal.setData(customer)
confirmModal.toggle()
}
const onDelete = () => {
if (confirmModal.data !== null) {
router.delete(route('mitra.destroy', confirmModal.data.id))
}
}
const handleSearchChange = (e) => {
setSearch({
...search,
q: e.target.value,
})
}
const sort = (key, sort = null) => {
if (sort !== null) {
setSearch({
...search,
sortBy: key,
sortRule: sort,
})
return
}
setSearch({
...search,
sortBy: key,
sortRule: search.sortRule == 'asc' ? 'desc' : 'asc',
})
}
const params = { q: search, location_id: location, level_id: level }
useEffect(() => {
if (preValue) {
router.get(
route(route().current()),
{ ...search, location_id: location, level_id: level },
{
replace: true,
preserveState: true,
}
)
}
}, [search, location, level])
const canCreate = hasPermission(auth, 'create-customer')
const canUpdate = hasPermission(auth, 'update-customer')
const canDelete = hasPermission(auth, 'delete-customer')
return (
<AuthenticatedLayout page={'Mitra WBB'} action={''}>
<Head title="Mitra WBB" />
<div className="w-full lg:max-w-[1100px] 2xl:max-w-full">
<div className="mx-auto sm:px-6 lg:px-8">
<div className="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 mb-2">
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Total Mitra
</div>
<div className="font-bold text-xl pt-2">
{formatIDR(stats.total_mitra)} Orang
</div>
</div>
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Mitra diblock
</div>
<div className="font-bold text-xl pt-2">
{formatIDR(stats.blocked_mitra)} Orang
</div>
</div>
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Mitra Aktif
</div>
<div className="font-bold text-xl pt-2">
{formatIDR(stats.active_mitra)} Orang
</div>
</div>
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Total Limit Mitra
</div>
<div className="font-bold text-xl pt-2">
Rp. {formatIDR(stats.sum_paylater_limit)}
</div>
</div>
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Total Hutang Mitra
</div>
<div className="font-bold text-xl pt-2">
Rp. {formatIDR(stats.sum_paylater_usage)}
</div>
</div>
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Total Sisa Limit Mitra
</div>
<div className="font-bold text-xl pt-2">
Rp. {formatIDR(stats.sum_paylater_remain)}
</div>
</div>
</div>
<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">
{canCreate && (
<Link href={route('mitra.create')}>
<Button color="primary" size="sm">
Tambah
</Button>
</Link>
)}
<div className="flex flex-col gap-1 items-end">
<div className="max-w-md">
<SearchInput
onChange={handleSearchChange}
value={search.q}
/>
</div>
<div className="flex flex-row gap-1">
<div className="w-full max-w-md">
<LevelSelectionInput
itemSelected={level}
onItemSelected={(id) =>
setLevel(id)
}
placeholder={'filter level'}
only={['gold', 'platinum']}
/>
</div>
<div className="w-full max-w-md">
<LocationSelectionInput
itemSelected={location}
onItemSelected={(id) =>
setLocation(id)
}
placeholder={'filter lokasi'}
/>
</div>
</div>
</div>
</div>
<div className="w-full 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"
>
Nama
</th>
<th
scope="col"
className="py-3 px-6"
>
Level
</th>
<ThSort
sort={sort}
label={'deposit_balance'}
search={search}
>
Deposit
</ThSort>
<ThSort
sort={sort}
label={'poin_balance'}
search={search}
>
Poin
</ThSort>
<th
scope="col"
className="py-3 px-6"
>
Sisa Saldo Hutang
</th>
<th
scope="col"
className="py-3 px-6"
>
Limit Hutang
</th>
<th
scope="col"
className="py-3 px-6"
>
Referral Code
</th>
<th
scope="col"
className="py-3 px-6"
>
Lokasi
</th>
<th
scope="col"
className="py-3 px-6"
>
Whatsapp
</th>
<th
scope="col"
className="py-3 px-6"
>
Status
</th>
<th
scope="col"
className="py-3 px-6 w-1/8"
/>
</tr>
</thead>
<tbody>
{data.map((customer) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={customer.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white hover:underline"
>
<Link
href={route(
'mitra.edit',
customer
)}
>
{customer.name}
</Link>
</td>
<td
scope="row"
className="py-4 px-6"
>
{customer.level.name}
</td>
<td
scope="row"
className="py-4 px-6"
>
{customer.display_deposit}
</td>
<td
scope="row"
className="py-4 px-6"
>
{customer.display_poin}
</td>
<td
scope="row"
className="py-4 px-6"
>
{formatIDR(
customer.paylater_remain
)}
</td>
<td
scope="row"
className="py-4 px-6"
>
{formatIDR(
customer.paylater_limit
)}
</td>
<td
scope="row"
className="py-4 px-6"
>
{customer.referral_code}
</td>
<td
scope="row"
className="py-4 px-6"
>
{customer.location_favorites.map(
(location) => (
<div
key={
location.id
}
>
{location.name}
</div>
)
)}
</td>
<td
scope="row"
className="py-4 px-6"
>
{customer.phone !==
null && (
<a
href={`https://wa.me/+62${customer.phone}`}
target="_blank"
className="underline"
>
+62{customer.phone}
</a>
)}
</td>
<td
scope="row"
className="py-4 px-6"
>
{customer.status_text}
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}
dismissOnClick={true}
size={'sm'}
>
{canUpdate && (
<Dropdown.Item>
<Link
href={route(
'mitra.edit',
customer
)}
className="flex space-x-1 items-center"
>
<HiPencil />
<div>
Ubah
</div>
</Link>
</Dropdown.Item>
)}
{canDelete && (
<Dropdown.Item
onClick={() =>
handleDeleteClick(
customer
)
}
>
<div className="flex space-x-1 items-center">
<HiTrash />
<div>
Hapus
</div>
</div>
</Dropdown.Item>
)}
</Dropdown>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} params={params} />
</div>
</div>
</div>
</div>
</div>
<ModalConfirm modalState={confirmModal} onConfirm={onDelete} />
</AuthenticatedLayout>
)
}

@ -166,7 +166,7 @@ export default function FormModal(props) {
<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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={+data.status}
name="status"

@ -127,7 +127,7 @@ export default function Form(props) {
<div className="my-4">
<div className="mb-1 text-sm">Aktif </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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={+data.is_active}
name="is_active"

@ -91,7 +91,7 @@ export default function Form(props) {
<div className="my-4">
<div className="mb-1 text-sm">Publish </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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={+data.is_publish}
name="is_publish"

@ -205,7 +205,7 @@ export default function Form(props) {
/>
<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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={data.expired_unit}
name="expired_unit"

@ -83,7 +83,7 @@ export default function FormModal(props) {
<div className="my-4">
<div className="mb-1 text-sm">Level</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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={data.customer_level_id}
name="customer_level_id"

@ -84,7 +84,7 @@ export default function Form(props) {
<div className="mt-4">
<div className="mb-1 text-sm">Level Upgrade</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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={data.level}
name="level"

@ -74,7 +74,7 @@ export default function ModalDelete(props) {
Hapus Keseluruhan
</label>
<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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={data.rule}
name="rule"

@ -33,7 +33,7 @@ export default function ModalFilter(props) {
Filter
</label>
<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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={(e) => setSortBy(e.target.value)}
value={sortBy}
name="filter"
@ -48,7 +48,7 @@ export default function ModalFilter(props) {
Order
</label>
<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"
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 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={(e) => setSortRule(e.target.value)}
value={sortRule}
name="sort"

@ -5,6 +5,7 @@ use App\Http\Controllers\Admin\AccountController;
use App\Http\Controllers\Admin\BannerController;
use App\Http\Controllers\Admin\CustomerController;
use App\Http\Controllers\Admin\CustomerLevelController;
use App\Http\Controllers\Admin\CustomerMitraController;
use App\Http\Controllers\Admin\DepositController;
use App\Http\Controllers\Admin\DepositLocationController;
use App\Http\Controllers\Admin\GeneralController;
@ -119,6 +120,14 @@ Route::middleware(['http_secure_aware', 'inertia.admin'])
Route::post('/customers/{customer}/level', [CustomerController::class, 'update_level'])->name('customer.update_level');
Route::post('/customers/{customer}/partner', [CustomerController::class, 'update_partner'])->name('customer.update_partner');
// mitra
Route::get('/mitra', [CustomerMitraController::class, 'index'])->name('mitra.index');
Route::get('/mitra/create', [CustomerMitraController::class, 'create'])->name('mitra.create');
Route::post('/mitra', [CustomerMitraController::class, 'store'])->name('mitra.store');
Route::get('/mitra/{customer}', [CustomerMitraController::class, 'edit'])->name('mitra.edit');
Route::post('/mitra/{customer}', [CustomerMitraController::class, 'update'])->name('mitra.update');
Route::delete('/mitra/{customer}', [CustomerMitraController::class, 'destroy'])->name('mitra.destroy');
// voucher
Route::get('/vouchers/import', [VoucherController::class, 'form_import'])->name('voucher.form_import');
Route::post('/vouchers/import', [VoucherController::class, 'import'])->name('voucher.import');

Loading…
Cancel
Save