upgrade flowbite deps

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

@ -34,4 +34,8 @@
- [x] tambah logo bank
- [x] tambah setor tunai
- [x] pengaturan share dapat menggunakan html
- [ ] menu mitrawbb
- [ ] menu mitrawbb (list, topup limit mitra -> after save redirect topup limit,
tambah batas bayar -> after save redirect history penambahan batas bayar)
- [ ] menu mitra wbb ada tombol tambah mitra untuk registrasi dengan full form (edit tetap di customer)
- [ ] untuk detail mitra nanti akan ada button untuk (transaksi mitra dengan cakupan: pembelian voucher, pembayaran hutang,
topuplimit, penambahan batas bayar, history deposit)

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Models\CustomerAsDataPartner;
use App\Models\CustomerLevel;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
@ -96,7 +97,7 @@ class CustomerController extends Controller
public function edit(Customer $customer)
{
return inertia('Customer/Form', [
'customer' => $customer->load(['level']),
'customer' => $customer->load(['level', 'partner']),
'levels' => CustomerLevel::all(),
'statuses' => Customer::STATUS
]);
@ -168,4 +169,47 @@ class CustomerController extends Controller
return redirect()->route('customer.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed updated']);
}
public function update_partner(Request $request, Customer $customer)
{
$request->validate([
'job' => 'required|string',
'image_selfie' => 'nullable|file',
'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',
]);
//
$partner = CustomerAsDataPartner::updateOrCreate([
'customer_id' => $customer->id,
], [
'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_statement' => $file->hashName('uploads')]);
}
return redirect()->route('customer.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed updated']);
}
}

@ -42,6 +42,11 @@ class DepositController extends Controller
]);
}
// TODO: ubah deposit confirm menggunakan page form
public function edit(DepositHistory $deposit)
{
}
public function update(Request $request, DepositHistory $deposit)
{
$request->validate([

@ -117,6 +117,7 @@ class GeneralController extends Controller
public function upload(Request $request)
{
$request->validate(['image' => 'required|file']);
$file = $request->file('image');
$file->store('uploads', 'public');

@ -271,6 +271,16 @@ class Customer extends Authenticatable
return $this->hasMany(CustomerCart::class);
}
public function locationFavorites()
{
return $this->belongsToMany(Location::class, CustomerLocationFavorite::class);
}
public function partner()
{
return $this->hasOne(CustomerAsDataPartner::class);
}
public function repayPaylater(DepositHistory $deposit): void
{
if ($this->paylater != null && $this->paylater->usage > 0) {
@ -289,9 +299,4 @@ class Customer extends Authenticatable
$deposit->update_customer_balance();
}
}
public function locationFavorites()
{
return $this->belongsToMany(Location::class, CustomerLocationFavorite::class);
}
}

@ -0,0 +1,51 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
class CustomerAsDataPartner extends Model
{
protected $fillable = [
'customer_id',
'job',
'image_selfie',
'file_statement',
'file_agreement',
'additional_json',
];
protected $appends = [
'image_selfie_url',
'file_statement_url',
'file_agreement_url',
];
public function imageSelfieUrl(): Attribute
{
return Attribute::make(get: function () {
if ($this->image_selfie != null) {
return asset($this->image_selfie);
}
return null;
});
}
public function fileStatementUrl(): Attribute
{
return Attribute::make(get: function () {
if ($this->file_statement != null) {
return asset($this->file_statement);
}
return null;
});
}
public function fileAgreementUrl(): Attribute
{
return Attribute::make(get: function () {
if ($this->file_agreement != null) {
return asset($this->file_agreement);
}
return null;
});
}
}

@ -9,5 +9,7 @@ class PaylaterCustomer extends Model
'usage',
'description',
'customer_id',
'day_deadline',
'day_deadline_at'
];
}

@ -7,11 +7,34 @@ use Illuminate\Support\Carbon;
class PaylaterHistory extends Model
{
const STATUS_VALID = 0;
const STATUS_WAIT_UPLOAD = 1;
const STATUS_WAIT_APPROVE = 2;
const STATUS_WAIT_PAYMENT = 3;
const STATUS_INVALID = 4;
const STATUS_REJECT = 5;
const STATUS_EXPIRED = 6;
const TYPE_PAYMENT = 0;
const TYPE_UPGRADE = 1;
const TYPE_REPAYMENT = 3;
protected $fillable = [
'debit',
'credit',
'description',
'customer_id',
'type',
'is_valid',
'image_prove'
];
protected $appends = [
@ -24,7 +47,10 @@ class PaylaterHistory extends Model
{
$customer = Customer::find($this->customer_id);
$paylater = $customer->paylater;
$paylater->update(['usage' => $paylater->usage + $this->debit - $this->credit]);
$paylater->update([
'usage' => $paylater->usage + $this->debit - $this->credit,
// TODO: add day dateline
]);
}
public function formatHumanCreatedAt(): Attribute

@ -18,6 +18,9 @@ return new class extends Migration
$table->decimal('credit', 20, 2)->default(0);
$table->text('description')->nullable();
$table->ulid('customer_id')->nullable();
$table->smallInteger('type')->default(0);
$table->smallInteger('is_valid')->default(0);
$table->string('image_prove');
$table->timestamps();
$table->softDeletes();

@ -18,6 +18,8 @@ return new class extends Migration
$table->ulid('customer_id')->nullable();
$table->decimal('limit', 20, 2)->default(0);
$table->decimal('usage', 20, 2)->default(0);
$table->smallInteger('day_deadline')->default(0);
$table->timestamp('day_deadline_at')->nullable();
$table->timestamps();
$table->softDeletes();

@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('customer_as_data_partners', function (Blueprint $table) {
$table->ulid('id')->primary();
$table->ulid('customer_id')->nullable();
$table->string('job')->nullable();
$table->string('image_selfie')->nullable();
$table->string('file_statement')->nullable();
$table->string('file_agreement')->nullable();
$table->text('additional_json')->nullable();
$table->timestamps();
$table->softDeletes();
$table->ulid('created_by')->nullable();
$table->ulid('updated_by')->nullable();
$table->ulid('deleted_by')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('customer_as_data_partners');
}
};

53
package-lock.json generated

@ -7,8 +7,8 @@
"dependencies": {
"@tinymce/tinymce-react": "^4.3.0",
"chart.js": "^4.3.0",
"flowbite": "^1.6.3",
"flowbite-react": "^0.3.8",
"flowbite": "^1.6.6",
"flowbite-react": "^0.4.9",
"is": "^3.3.0",
"moment": "^2.29.4",
"nprogress": "^0.2.0",
@ -35,7 +35,7 @@
"postcss": "^8.4.18",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.2.1",
"tailwindcss": "^3.3.2",
"vite": "^4.0.0"
}
},
@ -769,11 +769,11 @@
}
},
"node_modules/@floating-ui/react": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.16.0.tgz",
"integrity": "sha512-h+69TJSAY2R/k5rw+az56RzzDFc/Tg7EHn/qEgwkIVz56Zg9LlaRMMUvxkcvd+iN3CNFDLtEnDlsXnpshjsRsQ==",
"version": "0.24.3",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.24.3.tgz",
"integrity": "sha512-wWC9duiog4HmbgKSKObDRuXqMjZR/6m75MIG+slm5CVWbridAjK9STcnCsGYmdpK78H/GmzYj4ADVP8paZVLYQ==",
"dependencies": {
"@floating-ui/react-dom": "^1.1.2",
"@floating-ui/react-dom": "^2.0.1",
"aria-hidden": "^1.1.3",
"tabbable": "^6.0.1"
},
@ -783,11 +783,11 @@
}
},
"node_modules/@floating-ui/react-dom": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-1.3.0.tgz",
"integrity": "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz",
"integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==",
"dependencies": {
"@floating-ui/dom": "^1.2.1"
"@floating-ui/dom": "^1.3.0"
},
"peerDependencies": {
"react": ">=16.8.0",
@ -1164,9 +1164,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001508",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001508.tgz",
"integrity": "sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==",
"version": "1.0.30001509",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz",
"integrity": "sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA==",
"dev": true,
"funding": [
{
@ -1568,17 +1568,17 @@
}
},
"node_modules/flowbite-react": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/flowbite-react/-/flowbite-react-0.3.8.tgz",
"integrity": "sha512-IzbpvnUBDXsdf3HflbYv2W1lmTXITizMaX4G0SYoh/GxSp+25E97yNuwdBItwtCacUU1MJLwqIYXeicAxScRfA==",
"version": "0.4.9",
"resolved": "https://registry.npmjs.org/flowbite-react/-/flowbite-react-0.4.9.tgz",
"integrity": "sha512-azrWNzzjQcnjnNGkjEP1wzw9g10t3LzBC0LEgHN2uvUvm4Sj4W/9MrT27FsPIeEBhlSBMRpSx58/mtYRDgXXgQ==",
"dependencies": {
"@floating-ui/react": "^0.16.0",
"classnames": "^2.3.2",
"react-icons": "^4.6.0",
"react-indiana-drag-scroll": "^2.2.0"
"@floating-ui/react": "^0.24.3",
"flowbite": "^1.6.6",
"react-icons": "^4.10.1",
"react-indiana-drag-scroll": "^2.2.0",
"tailwind-merge": "^1.13.2"
},
"peerDependencies": {
"flowbite": "^1",
"react": "^18",
"react-dom": "^18",
"tailwindcss": "^3"
@ -2840,6 +2840,15 @@
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
},
"node_modules/tailwind-merge": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.13.2.tgz",
"integrity": "sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",

@ -11,8 +11,7 @@
"@vitejs/plugin-react": "^3.0.0",
"autoprefixer": "^10.4.12",
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.2",
"lodash": "^4.17.19",
"laravel-vite-plugin": "^0.7.5",
"postcss": "^8.4.18",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@ -20,10 +19,11 @@
"vite": "^4.0.0"
},
"dependencies": {
"lodash": "^4.17.19",
"@tinymce/tinymce-react": "^4.3.0",
"chart.js": "^4.3.0",
"flowbite": "^1.6.3",
"flowbite-react": "^0.3.8",
"flowbite": "^1.6.6",
"flowbite-react": "^0.4.9",
"is": "^3.3.0",
"moment": "^2.29.4",
"nprogress": "^0.2.0",
@ -38,4 +38,4 @@
"react-use": "^17.4.0",
"tinymce": "^6.4.2"
}
}
}

@ -1,6 +1,7 @@
module.exports = {
plugins: {
'postcss-import': {},
tailwindcss: {},
autoprefixer: {},
},
};
}

@ -17,7 +17,7 @@ export default function Checkbox({
onChange={onChange}
name={name}
disabled={disabled}
className="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
/>
{label !== '' && (
<label className="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">

@ -1,3 +1,4 @@
import { isEmpty } from 'lodash'
import React, { useRef } from 'react'
export default function FormFile({
@ -18,9 +19,22 @@ export default function FormFile({
</label>
)}
{preview && preview}
<div className="w-full flex flex-row gap-1 items-center border rounded-md py-1 px-1">
<div
className="px-2 py-1 border rounded-md text-white bg-black hover:bg-gray-600"
onClick={() => inputRef.current.click()}
>
Choose File
</div>
<div className="pl-2">
{isEmpty(inputRef.current?.value)
? 'No choosen file'
: inputRef.current.value}
</div>
</div>
<input
id={label}
className="block w-full mb-5 text-xs text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
className="w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 hidden"
type="file"
onChange={onChange}
ref={inputRef}

@ -74,9 +74,6 @@ export default function Index({ app_name, flash }) {
<Button processing={processing} onClick={handleSubmit}>
Sign in
</Button>
{/* <a href="#" className="text-sm underline text-blue-600">
forgot password
</a> */}
</div>
<div className="flex flex-row items-center space-x-2 justify-between my-3">
<div className="border-b-2 w-full" />

@ -65,7 +65,7 @@ export const FormUploadCashDeposit = () => {
<img
src={`${imageUrl}`}
loading="lazy"
className="w-full h-52 mb-1"
className="w-full mb-1"
alt="bukti Pembayaran"
/>
<div

@ -65,7 +65,7 @@ export const FormUploadManual = () => {
<img
src={`${imageUrl}`}
loading="lazy"
className="w-full h-52 mb-1"
className="w-full object-contain h-[calc(50dvh)] mb-1"
alt="bukti transfer"
/>
<div

@ -64,7 +64,7 @@ export default function Index({
role="alert"
>
<div>
Lunasi pinjaman kamu dengan melakukan topup deposit
lunasi pinjaman kamu sebelum jatuh tempo pada ...
</div>
</div>
</div>

@ -14,7 +14,7 @@ import { isEmpty } from 'lodash'
const EmptyHere = () => {
return (
<div className="w-full px-5 text-center flex flex-col my-auto">
<div className="font-bold text-xl">Voucher segera tersedia</div>
<div className="font-bold text-xl">Belum ada data tersedia</div>
<div className="text-gray-400">
Yuk, share referral kamu untuk tingkatkan poinnya
</div>

@ -3,12 +3,25 @@ import { ToastContainer, toast } from 'react-toastify'
import ApplicationLogo from '@/Components/Defaults/ApplicationLogo'
import Dropdown from '@/Components/Defaults/Dropdown'
import { Link, usePage } from '@inertiajs/react'
import { Breadcrumb } from 'flowbite-react'
import { Breadcrumb, Flowbite } from 'flowbite-react'
import { HiMenu, HiChevronDown, HiHome } from 'react-icons/hi'
import { router } from '@inertiajs/react'
import SidebarNav from './Partials/SidebarNav'
import { HiOutlineBell } from 'react-icons/hi2'
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 Authenticated({
children,
page = '',
@ -33,179 +46,189 @@ export default function Authenticated({
}, [flash])
return (
<div className="min-h-screen flex flex-col bg-gray-100 dark:bg-gray-700">
<nav className="bg-white dark:bg-gray-800 border-b dark:border-gray-700">
<div className="mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<div className="shrink-0 flex items-center">
<Link href={route('dashboard')}>
<ApplicationLogo className="block pt-2 h-12 w-full font-bold text-2xl fill-current" />
</Link>
<Flowbite theme={{ theme: customTheme }}>
<div className="min-h-screen flex flex-col bg-gray-100 dark:bg-gray-700">
<nav className="bg-white dark:bg-gray-800 border-b dark:border-gray-700">
<div className="mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<div className="shrink-0 flex items-center">
<Link href={route('dashboard')}>
<ApplicationLogo className="block pt-2 h-12 w-full font-bold text-2xl fill-current dark:text-gray-200" />
</Link>
</div>
</div>
</div>
<div className="hidden sm:flex sm:items-center sm:ml-6">
<div className="ml-3 relative">
<Dropdown>
<Dropdown.Trigger>
<div className="flex flex-row">
<HiOutlineBell className="h-6 w-6" />
<div>
<div className="bg-blue-300 text-blue-600 rounded-lg px-1 text-xs -ml-2">
{count_unread_notifications}
</div>
</div>
</div>
</Dropdown.Trigger>
<Dropdown.Content width="64">
{notifications.map((notif) => (
<div
className={`px-4 py-2 hover:bg-gray-100 border-b`}
onClick={() =>
handleNotification(notif)
}
key={notif.id}
>
<div
className={`${
+notif.is_read === 0 &&
'font-bold'
}`}
>
{notif.description}
</div>
<div className="text-xs">
{notif.format_created_at}
<div className="hidden sm:flex sm:items-center sm:ml-6">
<div className="ml-3 relative">
<Dropdown>
<Dropdown.Trigger>
<div className="flex flex-row">
<HiOutlineBell className="h-6 w-6" />
<div>
<div className="bg-blue-300 text-blue-600 rounded-lg px-1 text-xs -ml-2">
{
count_unread_notifications
}
</div>
</div>
</div>
))}
{notifications.length > 0 && (
<div className="w-full flex flex-row justify-between px-3">
</Dropdown.Trigger>
<Dropdown.Content width="64">
{notifications.map((notif) => (
<div
className="text-xs hover:text-blue-500 hover:underline"
className={`px-4 py-2 hover:bg-gray-100 border-b`}
onClick={() =>
router.get(
route(
'notifications.index'
)
handleNotification(
notif
)
}
key={notif.id}
>
lihat semua
<div
className={`${
+notif.is_read ===
0 && 'font-bold'
}`}
>
{notif.description}
</div>
<div className="text-xs">
{
notif.format_created_at
}
</div>
</div>
<div
className="text-xs hover:text-blue-500 hover:underline"
onClick={() =>
handleNotification()
}
>
tandai semua dibaca
))}
{notifications.length > 0 && (
<div className="w-full flex flex-row justify-between px-3">
<div
className="text-xs hover:text-blue-500 hover:underline"
onClick={() =>
router.get(
route(
'notifications.index'
)
)
}
>
lihat semua
</div>
<div
className="text-xs hover:text-blue-500 hover:underline"
onClick={() =>
handleNotification()
}
>
tandai semua dibaca
</div>
</div>
</div>
)}
</Dropdown.Content>
</Dropdown>
</div>
<div className="ml-3 relative">
<Dropdown>
<Dropdown.Trigger>
<span className="inline-flex rounded-md">
<button
type="button"
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150 dark:bg-gray-700 dark:hover:text-gray-50 dark:text-gray-200 gap-2"
)}
</Dropdown.Content>
</Dropdown>
</div>
<div className="ml-3 relative">
<Dropdown>
<Dropdown.Trigger>
<span className="inline-flex rounded-md">
<button
type="button"
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150 dark:bg-gray-700 dark:hover:text-gray-50 dark:text-gray-200 gap-2"
>
<img
src={
auth.user.photo_url
}
alt="user profile image"
className="h-10 rounded-full border border-gray-100"
loading="lazy"
/>
{auth.user.name}
<HiChevronDown />
</button>
</span>
</Dropdown.Trigger>
<Dropdown.Content>
<a
href={route('home.index')}
target="_blank"
className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none transition duration-150 ease-in-out dark:text-white hover:dark:bg-gray-500"
>
Visit Site
</a>
<Dropdown.Link
href={route('profile.edit')}
>
Profile
</Dropdown.Link>
<Dropdown.Link
href={route('logout')}
method="post"
as="button"
>
<img
src={auth.user.photo_url}
alt="user profile image"
className="h-10 rounded-full border border-gray-100"
loading="lazy"
/>
{auth.user.name}
<HiChevronDown />
</button>
</span>
</Dropdown.Trigger>
Log Out
</Dropdown.Link>
</Dropdown.Content>
</Dropdown>
</div>
</div>
<Dropdown.Content>
<a
href={route('home.index')}
target="_blank"
className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none transition duration-150 ease-in-out dark:text-white hover:dark:bg-gray-500"
>
Visit Site
</a>
<Dropdown.Link
href={route('profile.edit')}
>
Profile
</Dropdown.Link>
<Dropdown.Link
href={route('logout')}
method="post"
as="button"
>
Log Out
</Dropdown.Link>
</Dropdown.Content>
</Dropdown>
<div className="-mr-2 flex items-center sm:hidden space-x-2">
<button
onClick={() =>
setShowingNavigationDropdown(
(previousState) => !previousState
)
}
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"
>
<HiMenu />
</button>
</div>
</div>
</div>
</nav>
<div className="-mr-2 flex items-center sm:hidden space-x-2">
<button
<div className="flex-1 flex flex-row">
<div
className={`w-fit ${
showingNavigationDropdown
? 'absolute h-screen z-10'
: 'md:block hidden'
}`}
>
<SidebarNav user={auth.user} />
</div>
<main className="w-full">
{page !== '' && (
<Breadcrumb
className="bg-gray-200 py-3 px-5 mb-2 dark:bg-gray-700"
onClick={() =>
setShowingNavigationDropdown(
(previousState) => !previousState
)
parent === null
? router.visit(route('dashboard'))
: router.visit(parent)
}
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"
>
<HiMenu />
</button>
</div>
</div>
</div>
</nav>
<div className="flex-1 flex flex-row">
<div
className={`w-fit ${
showingNavigationDropdown
? 'absolute h-screen z-10'
: 'md:block hidden'
}`}
>
<SidebarNav user={auth.user} />
<Breadcrumb.Item icon={HiHome}>
<p className="mt-0.5">{page}</p>
</Breadcrumb.Item>
{typeof action === 'string' && (
<Breadcrumb.Item>{action}</Breadcrumb.Item>
)}
{typeof action === 'object' &&
action.map((item) => (
<Breadcrumb.Item key={item}>
{item}
</Breadcrumb.Item>
))}
</Breadcrumb>
)}
<div className="py-4">{children}</div>
</main>
</div>
<main className="w-full">
{page !== '' && (
<Breadcrumb
className="bg-gray-200 py-3 px-5 mb-2 dark:bg-gray-700"
onClick={() =>
parent === null
? router.visit(route('dashboard'))
: router.visit(parent)
}
>
<Breadcrumb.Item icon={HiHome}>
<p className="mt-0.5">{page}</p>
</Breadcrumb.Item>
{typeof action === 'string' && (
<Breadcrumb.Item>{action}</Breadcrumb.Item>
)}
{typeof action === 'object' &&
action.map((item) => (
<Breadcrumb.Item key={item}>
{item}
</Breadcrumb.Item>
))}
</Breadcrumb>
)}
<div className="py-4">{children}</div>
</main>
<ToastContainer />
</div>
<ToastContainer />
</div>
</Flowbite>
)
}

@ -9,11 +9,11 @@ const Item = ({ item, children }) => {
const Icon = () =>
item.icon({
className:
'w-6 h-6 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white',
'w-6 h-6 text-gray-500 transition duration-75 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white',
})
const isActive = route().current(item.active)
const className = `flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 ${
isActive && 'bg-gray-200'
isActive && 'bg-gray-200 dark:bg-gray-700'
}`
return (
@ -32,7 +32,7 @@ const SubItem = ({ item, children }) => {
})
const isActive = route().current(item.active)
const className = `flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700 ${
isActive && 'bg-gray-200'
isActive && 'bg-gray-200 dark:bg-gray-700'
}`
return (

@ -14,6 +14,7 @@ import {
HiArchiveBox,
HiBanknotes,
HiCheckBadge,
HiClipboardDocumentList,
HiCog8Tooth,
HiCreditCard,
HiCurrencyDollar,
@ -115,7 +116,7 @@ export default [
{
name: 'List',
show: true,
icon: HiUserCircle,
icon: HiClipboardDocumentList,
route: route('customer.index'),
active: 'customer.*',
permission: 'view-customer',
@ -146,6 +147,45 @@ export default [
},
],
},
{
name: 'Mitra WBB',
show: true,
icon: HiUserCircle,
items: [
{
name: 'List', //daftar mitra dan stats
show: true,
icon: HiClipboardDocumentList,
route: route('customer.index'),
active: 'customer.*',
permission: 'view-customer',
},
{
name: 'Pembayaran Hutang', // daftar pembayaran hutang yang perlu di konfirmasi , dan ada tombol add untuk pembayaran hutang oleh admin
show: true,
icon: HiCash,
route: route('setting.affilate'),
active: 'setting.affilate',
permission: 'view-setting-affilate',
},
{
name: 'Tambah Limit', // form tambah limit, pilih mitra/customer dan limit
show: true,
icon: HiArrowCircleUp,
route: route('customer-level.index'),
active: 'customer-level.*',
permission: 'view-customer-level',
},
{
name: 'Tambah Tenor', // form tambah limit , pilih mitra dengan penambahan tenor
show: true,
icon: HiArrowCircleUp,
route: route('setting.affilate'),
active: 'setting.affilate',
permission: 'view-setting-affilate',
},
],
},
{
name: 'Admin',
show: true,

@ -42,7 +42,9 @@ export default function Account(props) {
<div className="flex justify-between">
{canCreate && (
<Link href={route('account.create')}>
<Button size="sm">Tambah</Button>
<Button color="primary" size="sm">
Tambah
</Button>
</Link>
)}
</div>
@ -109,6 +111,7 @@ export default function Account(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -1,8 +1,9 @@
import React, { useEffect } from 'react'
import { Head, useForm } from '@inertiajs/react'
import { Button, TextInput, Label, Checkbox, Spinner } from 'flowbite-react'
import GuestLayout from '@/Layouts/GuestLayout'
import InputError from '@/Components/Defaults/InputError'
import { Head, Link, useForm } from '@inertiajs/react'
import { Button, TextInput, Label, Checkbox, Spinner } from 'flowbite-react'
export default function Login({ status }) {
const { data, setData, post, processing, errors, reset } = useForm({
@ -93,7 +94,11 @@ export default function Login({ status }) {
</div>
<div className="flex items-center justify-end mt-4">
<Button onClick={submit} disabled={processing}>
<Button
onClick={submit}
disabled={processing}
color="primary"
>
{processing ? <Spinner /> : 'Log in'}
</Button>
</div>

@ -1,6 +1,5 @@
import React, { useEffect, useState } from 'react'
import React 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'
@ -44,7 +43,9 @@ export default function Info(props) {
<div className="flex justify-between">
{canCreate && (
<Link href={route('banner.create')}>
<Button size="sm">Tambah</Button>
<Button size="sm" color="primary">
Tambah
</Button>
</Link>
)}
</div>
@ -79,6 +80,7 @@ export default function Info(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -1,245 +1,10 @@
import React, { useEffect, Suspense } from 'react'
import { isEmpty } from 'lodash'
import React from 'react'
import { Head } from '@inertiajs/react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import { Head, useForm, usePage } from '@inertiajs/react'
import FormFile from '@/Components/FormFile'
import FormInputWith from '@/Components/FormInputWith'
import TextArea from '@/Components/TextArea'
const Profile = () => {
const {
props: { customer, statuses },
} = usePage()
const { data, setData, post, processing, errors } = useForm({
username: '',
password: '',
name: '',
fullname: '',
address: '',
phone: '',
image: '',
image_url: '',
status: 0,
})
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('customer.update', customer))
return
}
post(route('customer.store'))
}
useEffect(() => {
if (isEmpty(customer) === false) {
setData({
username: customer.username,
password: customer.password,
name: customer.name,
fullname: customer.fullname,
address: customer.address,
phone: customer.phone,
image_url: customer.image_url,
status: customer.status,
})
}
}, [customer])
return (
<div className="flex flex-col p-4 border rounded border-gray-200">
<div className="pb-4 font-bold">Profile</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="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"
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={'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="mt-1">
<Button onClick={handleSubmit} processing={processing}>
Simpan
</Button>
</div>
</div>
)
}
const Identity = () => {
const {
props: { customer },
} = usePage()
if (isEmpty(customer?.identity_image)) {
return
}
return (
<div className="mt-2 flex flex-col p-4 border rounded border-gray-200">
<div className="pb-4 font-bold">KTP</div>
<img
alt="identity"
src={customer?.identity_image_url}
className="w-full object-fill h-96"
loading="lazy"
/>
</div>
)
}
const Paylater = () => {
const {
props: { customer, levels },
} = usePage()
const { data, setData, post, processing, errors } = useForm({
level: customer?.level.key,
paylater_limit: +customer?.paylater?.limit,
})
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const handleSubmit = () => {
post(route('customer.update_level', customer))
}
if (isEmpty(customer)) {
return
}
return (
<div className="mt-2 flex flex-col p-4 border rounded border-gray-200">
<div className="mt-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"
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>
<div className="mb-4 mt-2">
<FormInput
type="number"
label="Limit Hutang"
name="paylater_limit"
onChange={handleOnChange}
value={data.paylater_limit}
error={errors.paylater_limit}
/>
</div>
<div className="flex items-center">
<Button onClick={handleSubmit} processing={processing}>
Simpan
</Button>
</div>
</div>
)
}
import Profile from './FormPartials/Profile'
import Paylater from './FormPartials/Paylater'
import Partner from './FormPartials/Partner'
export default function Form(props) {
const { customer } = props
@ -254,8 +19,8 @@ export default function Form(props) {
<Profile />
{customer !== null && (
<>
<Identity />
<Paylater />
<Partner />
</>
)}
</div>

@ -0,0 +1,331 @@
''
import React, { useEffect, useRef, useState } from 'react'
import { usePage, useForm } from '@inertiajs/react'
import { isEmpty } from 'lodash'
import Button from '@/Components/Button'
import FormInput from '@/Components/FormInput'
import FormFile from '@/Components/FormFile'
import { Spinner } from 'flowbite-react'
import { HiXCircle } from 'react-icons/hi2'
export default function Partner() {
const {
props: { customer, csrf_token },
} = usePage()
const [uploadIndex, setUploadIndex] = useState(null)
const [loading, setLoading] = useState(false)
const generalUploadRef = useRef()
const { data, setData, post, processing, errors } = useForm({
job: '',
image_selfie: null,
image_selfie_url: '',
file_statement: null,
file_statement_url: '',
file_agreement: null,
file_agreement_url: '',
items: [],
})
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 = () => {
post(route('customer.update_partner', customer))
}
useEffect(() => {
if (!isEmpty(customer?.partner)) {
let items = JSON.parse(customer.partner.additional_json)
if (isEmpty(items)) {
items = []
}
setData({
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])
if (isEmpty(customer)) {
return
}
return (
<div className="mt-2 flex flex-col p-4 border rounded border-gray-200">
<input
ref={generalUploadRef}
type="file"
onChange={handleFileUpload}
className="hidden"
/>
<div className="font-semibold ">Data Mitra</div>
<div className="mt-4">
<FormInput
name="job"
value={data.job}
onChange={handleOnChange}
label="Pekerjaan"
error={errors.job}
/>
</div>
<div className="">
<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 className="">
<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 className="">
<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="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"
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-4 flex items-center">
<Button onClick={handleSubmit} processing={processing}>
Simpan
</Button>
</div>
</div>
)
}

@ -0,0 +1,87 @@
import React from 'react'
import { usePage, useForm } from '@inertiajs/react'
import { isEmpty } from 'lodash'
import Button from '@/Components/Button'
import FormInputNumeric from '@/Components/FormInputNumeric'
export default function Paylater() {
const {
props: { customer, levels },
} = usePage()
const { data, setData, post, processing, errors } = useForm({
level: customer?.level.key,
paylater_limit: +customer?.paylater?.limit,
})
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const handleSubmit = () => {
post(route('customer.update_level', customer))
}
if (isEmpty(customer)) {
return
}
return (
<div className="mt-2 flex flex-col p-4 border rounded border-gray-200">
<div className="mt-4">
{!isEmpty(customer.identity_image_url) && (
<div>
<div className="pb-4 font-bold">KTP</div>
<img
alt="identity"
src={customer?.identity_image_url}
className="w-full object-fill h-96"
loading="lazy"
/>
</div>
)}
<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"
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>
<div className="mb-4 mt-2">
<FormInputNumeric
type="number"
label="Limit Hutang"
name="paylater_limit"
onChange={handleOnChange}
value={data.paylater_limit}
error={errors.paylater_limit}
/>
</div>
<div className="flex items-center">
<Button onClick={handleSubmit} processing={processing}>
Simpan
</Button>
</div>
</div>
)
}

@ -0,0 +1,149 @@
import React, { useEffect } from 'react'
import { usePage, useForm } from '@inertiajs/react'
import { isEmpty } from 'lodash'
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'
export default function Profile() {
const {
props: { customer, statuses },
} = usePage()
const { data, setData, post, processing, errors } = useForm({
username: '',
password: '',
name: '',
fullname: '',
address: '',
phone: '',
image: '',
image_url: '',
status: 0,
})
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('customer.update', customer))
return
}
post(route('customer.store'))
}
useEffect(() => {
if (isEmpty(customer) === false) {
setData({
username: customer.username,
password: customer.password,
name: customer.name,
fullname: customer.fullname,
address: customer.address,
phone: customer.phone,
image_url: customer.image_url,
status: customer.status,
})
}
}, [customer])
return (
<div className="flex flex-col p-4 border rounded border-gray-200">
<div className="pb-4 font-bold">Profile</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="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"
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={'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="mt-1">
<Button onClick={handleSubmit} processing={processing}>
Simpan
</Button>
</div>
</div>
)
}

@ -126,7 +126,9 @@ export default function Customer(props) {
<div className="flex justify-between">
{canCreate && (
<Link href={route('customer.create')}>
<Button size="sm">Tambah</Button>
<Button color="primary" size="sm">
Tambah
</Button>
</Link>
)}
<div className="flex flex-col gap-1 items-end">
@ -329,6 +331,7 @@ export default function Customer(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -99,8 +99,8 @@ export default function Dashboard(props) {
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="w-full flex flex-row mt-4 space-x-2 border rounded-md shadow">
<div className="flex-1 overflow-auto bg-white p-4">
<div className="w-full flex flex-row mt-4 space-x-2 border dark:border-gray-900 rounded-md shadow">
<div className="flex-1 overflow-auto bg-white dark:bg-gray-800 p-4 rounded-md">
<div className="w-full flex flex-col md:flex-row justify-between mb-4">
<div className="text-gray-500 text-xl pb-4">
Penjualan
@ -134,12 +134,12 @@ export default function Dashboard(props) {
/>
</div>
</div>
<div className="bg-white rounded-md shadow border mt-4 w-full">
<div className="bg-white dark:bg-gray-800 rounded-md shadow border dark:border-gray-800 mt-4 w-full">
<div className="text-gray-500 text-xl px-3 py-4">
Deposit Hari Ini
</div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4 px-2">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-800 dark:text-gray-400">
<tr>
<th scope="col" className="py-3 px-6">
#
@ -175,7 +175,7 @@ export default function Dashboard(props) {
</tbody>
</table>
</div>
<div className="bg-white rounded-md shadow border mt-2">
<div className="bg-white dark:bg-gray-800 dark:border-gray-800 rounded-md shadow border mt-2">
<div className="text-gray-500 text-xl px-3 py-4">
Penjualan Hari Ini
</div>

@ -1,12 +1,11 @@
import React, { useEffect } from 'react'
import { Link, useForm } from '@inertiajs/react'
import { isEmpty } from 'lodash'
import { STATUS_APPROVE, STATUS_REJECT } from '@/constant'
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'
import { STATUS_APPROVE, STATUS_REJECT } from '@/constant'
export default function FormModal(props) {
const { modalState } = props
@ -26,8 +25,7 @@ export default function FormModal(props) {
is_valid: 0,
status_text: '',
text_color: '',
customer_name: '',
customer_phone: '',
customer: '',
description: '',
reject_reason: '',
})
@ -72,9 +70,7 @@ export default function FormModal(props) {
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
} )`,
customer: deposit.customer,
description: deposit.description,
reject_reason: deposit.note,
deposit_location: deposit.deposit_location,
@ -94,7 +90,16 @@ export default function FormModal(props) {
<tr>
<td className="font-bold">Customer</td>
<td>:</td>
<td>{data.customer_name}</td>
<td>
<Link
href={route('customer.edit', {
customer: data.customer,
})}
className="hover:underline"
>
{data.customer.name}
</Link>
</td>
</tr>
<tr>
<td className="font-bold">Deskripsi</td>

@ -45,7 +45,9 @@ export default function DespositLocation(props) {
<div className="flex justify-between">
{canCreate && (
<Link href={route('deposit-location.create')}>
<Button size="sm">Tambah</Button>
<Button size="sm" color="primary">
Tambah
</Button>
</Link>
)}
</div>
@ -122,6 +124,7 @@ export default function DespositLocation(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -43,7 +43,9 @@ export default function Info(props) {
<div className="flex justify-between">
{canCreate && (
<Link href={route('info.create')}>
<Button size="sm">Tambah</Button>
<Button size="sm" color="primary">
Tambah
</Button>
</Link>
)}
</div>
@ -89,6 +91,7 @@ export default function Info(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}
@ -97,7 +100,13 @@ export default function Info(props) {
>
{canUpdate && (
<Dropdown.Item>
<Link href={route('info.edit', info)} className="flex space-x-1 items-center">
<Link
href={route(
'info.edit',
info
)}
className="flex space-x-1 items-center"
>
<HiPencil />
<div>
Ubah

@ -68,12 +68,15 @@ export default function Index(props) {
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="flex flex-col lg:flex-row gap-1 justify-between">
{canCreate && (
<Button
size="sm"
onClick={() => toggleFormModal()}
>
Tambah
</Button>
<div className="flex flex-row space-x-2">
<Button
color="primary"
size="sm"
onClick={() => toggleFormModal()}
>
Tambah
</Button>
</div>
)}
<div className="flex flex-col md:flex-row gap-1 items-center">
<div className="w-full max-w-md">
@ -126,6 +129,7 @@ export default function Index(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -69,7 +69,9 @@ export default function Index(props) {
<Link
href={route('location-profile.create')}
>
<Button size="sm">Tambah</Button>
<Button size="sm" color="primary">
Tambah
</Button>
</Link>
</div>
)}
@ -158,6 +160,7 @@ export default function Index(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -50,6 +50,7 @@ export default function Info(props) {
<div className="flex justify-between">
{canCreate && (
<Button
color="primary"
size="sm"
onClick={() => toggleFormModal()}
>
@ -110,6 +111,7 @@ export default function Info(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -62,7 +62,9 @@ export default function Product(props) {
<div className="flex justify-between">
{canCreate && (
<Link href={route('roles.create')}>
<Button size="sm">Tambah</Button>
<Button color="primary" size="sm">
Tambah
</Button>
</Link>
)}
@ -116,7 +118,7 @@ export default function Product(props) {
key={
user.id
}
className="px-2 py-1 bg-blue-600 text-white border rounded-full border-blue-900"
className="underline"
onClick={() =>
router.visit(
route(
@ -127,6 +129,7 @@ export default function Product(props) {
}
>
{user.name}
{' ,'}
</div>
)
)}
@ -134,6 +137,7 @@ export default function Product(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -1,16 +1,12 @@
import React, { useEffect, Suspense } from 'react'
import { isEmpty } from 'lodash'
import React from 'react'
import { Head, Link } from '@inertiajs/react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import { Head, useForm } from '@inertiajs/react'
import FormFile from '@/Components/FormFile'
import { formatIDR } from '@/utils'
const SaleItem = ({ item, index }) => {
const { voucher } = JSON.parse(item.additional_info_json)
console.log(voucher)
return (
<>
<td
@ -56,7 +52,17 @@ export default function Detail(props) {
<tr>
<td className="font-bold">Customer</td>
<td>:</td>
<td>{sale.customer.name}</td>
<td>
<Link
href={route(
'customer.edit',
sale.customer
)}
className="hover:underline"
>
{sale.customer.name}
</Link>
</td>
</tr>
<tr>
<td className="font-bold">

@ -1,12 +1,12 @@
import React, { useState, useEffect } from 'react'
import { Head, router } from '@inertiajs/react'
import { Head, Link, router } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { HiEye } from 'react-icons/hi'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import SearchInput from '@/Components/SearchInput'
import { formatIDR } from '@/utils'
import { usePrevious } from 'react-use'
export default function Info(props) {
const {
@ -38,11 +38,6 @@ export default function Info(props) {
<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">
{/* {canCreate && (
<Link href={route('banner.create')}>
<Button size="sm">Tambah</Button>
</Link>
)} */}
<div className="flex items-center">
<SearchInput
onChange={(e) => setSearch(e.target.value)}
@ -103,8 +98,15 @@ export default function Info(props) {
>
{sale.code}
</td>
<td className="py-4 px-6">
{sale.customer.name}
<td className="py-4 px-6 hover:underline">
<Link
href={route(
'customer.edit',
sale.customer
)}
>
{sale.customer.name}
</Link>
</td>
<td className="py-4 px-6">
{sale.format_created_at}

@ -61,7 +61,9 @@ export default function User(props) {
<div className="flex justify-between">
{canCreate && (
<Link href={route('user.create')}>
<Button size="sm">Tambah</Button>
<Button size="sm" color="primary">
Tambah
</Button>
</Link>
)}
<div className="flex items-center">
@ -160,6 +162,7 @@ export default function User(props) {
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -151,10 +151,16 @@ export default function Index(props) {
{canCreate && (
<div className="flex flex-row space-x-2">
<Link href={route('voucher.create')}>
<Button size="sm">Tambah</Button>
<Button size="sm" color="primary">
Tambah
</Button>
</Link>
<Link href={route('voucher.import')}>
<Button size="sm" outline>
<Button
size="sm"
outline
color="primary"
>
Import
</Button>
</Link>
@ -325,6 +331,7 @@ export default function Index(props) {
<td className="py-4 px-6 flex justify-end">
<Dropdown
color="primary"
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}

@ -45,10 +45,16 @@ export default function Index(props) {
{canCreate && (
<div className="flex flex-row space-x-2">
<Link href={route('voucher.create')}>
<Button size="sm">Tambah</Button>
<Button color="primary" size="sm">
Tambah
</Button>
</Link>
<Link href={route('voucher.import')}>
<Button size="sm" outline>
<Button
size="sm"
outline
color="primary"
>
Import
</Button>
</Link>

@ -101,10 +101,16 @@ export default function Index(props) {
{canCreate && (
<div className="flex flex-row space-x-2">
<Link href={route('voucher.create')}>
<Button size="sm">Tambah</Button>
<Button color="primary" size="sm">
Tambah
</Button>
</Link>
<Link href={route('voucher.import')}>
<Button size="sm" outline>
<Button
color="primary"
size="sm"
outline
>
Import
</Button>
</Link>

@ -1,22 +1,26 @@
import './bootstrap';
import '../css/app.css';
import 'flowbite';
import 'react-toastify/dist/ReactToastify.css';
import './bootstrap'
import '../css/app.css'
import 'react-toastify/dist/ReactToastify.css'
import React from 'react';
import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import React from 'react'
import { createRoot } from 'react-dom/client'
import { createInertiaApp } from '@inertiajs/react'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
const appName =
window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel'
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx')),
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.jsx`,
import.meta.glob('./Pages/**/*.jsx')
),
setup({ el, App, props }) {
const root = createRoot(el);
const root = createRoot(el)
root.render(<App {...props} />);
root.render(<App {...props} />)
},
progress: { color: '#003bf1' , showSpinner: true, includeCSS: true},
});
progress: { color: '#003bf1', showSpinner: true, includeCSS: true },
})

@ -7,7 +7,8 @@
<title inertia>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="stylesheet" href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap">
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Scripts -->
@routes

@ -6,11 +6,11 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="description" content="web shop online aplikasi jual voucher wifi online">
<title inertia>{{ config('app.name', 'Voucher') }}</title>
<!-- Fonts -->
<link rel="stylesheet" href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap">
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Scripts -->
@routes

@ -117,6 +117,7 @@ Route::middleware(['http_secure_aware', 'inertia.admin'])
Route::post('/customers/{customer}', [CustomerController::class, 'update'])->name('customer.update');
Route::delete('/customers/{customer}', [CustomerController::class, 'destroy'])->name('customer.destroy');
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');
// voucher
Route::get('/vouchers/import', [VoucherController::class, 'form_import'])->name('voucher.form_import');

@ -1,51 +1,50 @@
const defaultTheme = require('tailwindcss/defaultTheme')
import defaultTheme from 'tailwindcss/defaultTheme'
import forms from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
module.exports = {
export default {
darkMode: 'class',
content: [
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
'./resources/views/**/*.blade.php',
'./app/Models/*.php',
'./resources/js/**/*.jsx',
'./node_modules/flowbite/**/*.js',
'./node_modules/flowbite-react/**/*.{js,jsx,ts,tsx}',
'node_modules/flowbite-react/**/*.{js,jsx,ts,tsx}',
],
theme: {
extend: {
fontFamily: {
sans: ['Nunito', ...defaultTheme.fontFamily.sans],
},
},
colors: {
primary: {
50: '#edf7ff',
100: '#d6ebff',
200: '#b6deff',
300: '#84cbff',
400: '#4aaeff',
500: '#2089ff',
600: '#0867ff',
700: '#024ff3',
800: '#0940c4',
900: '#0e3995', // default
950: '#0e255d',
sans: ['Figtree', ...defaultTheme.fontFamily.sans],
},
secondary: {
50: '#fffceb',
100: '#fdf4c8',
200: '#fbe88c',
300: '#f9d650',
400: '#f7c328',
500: '#f1a410', //default
600: '#d57d0a',
700: '#b1580c',
800: '#904510',
900: '#763911',
950: '#441c04',
colors: {
primary: {
50: '#edf7ff',
100: '#d6ebff',
200: '#b6deff',
300: '#84cbff',
400: '#4aaeff',
500: '#2089ff',
600: '#0867ff',
700: '#024ff3',
800: '#0940c4',
900: '#0e3995',
950: '#0e255d',
},
secondary: {
50: '#fffceb',
100: '#fdf4c8',
200: '#fbe88c',
300: '#f9d650',
400: '#f7c328',
500: '#f1a410',
600: '#d57d0a',
700: '#b1580c',
800: '#904510',
900: '#763911',
950: '#441c04',
},
},
},
plugins: [forms, require('flowbite/plugin')],
},
plugins: [require('@tailwindcss/forms'), require('flowbite/plugin')],
}

Loading…
Cancel
Save